Skip to content

feat(shell): argv + safe-subset engine, increment 1 (#34)#38

Merged
hartsock merged 1 commit into
mainfrom
issue-34/argv-safe-subset-engine
Jun 25, 2026
Merged

feat(shell): argv + safe-subset engine, increment 1 (#34)#38
hartsock merged 1 commit into
mainfrom
issue-34/argv-safe-subset-engine

Conversation

@hartsock

Copy link
Copy Markdown
Member

Implements increment 1 of #34 (ADR 0005 D3): replaces the fail-closed shell stub with the argv + safe-subset engine. agent-bridle is the exec funnel — it parses the request itself, checks the leash, and spawns directly. L2 convenience; the L3 boundary stays deferred (#35), so runs are honestly advisory (sandbox_kind: none, ADR 0005 D1).

What

  • parse.rs — the safe-subset classifier: quote-aware tokenizer → a single command's argv. Refuses $(...)/backticks/subshells by design (Dynamic); refuses |, &&, ||, ;, <, >, globs, $VAR as Unsupported (later increments); Malformed for bad input.
  • shell_tool.rsprogram+args or cmd; exec/cwd leash checks; direct spawn (std::process in spawn_blocking) with capture + timeout; structured denied envelopes.
  • lib.rs / Cargo.toml — de-stubbed; documented as the engine; brush kept as the deferred reversible engine (brush-bridle-core: optional full-bash shell engine (deferred, reversible — not the path to a usable shell) #20).
  • mcp consumer tests — restored from stub-shims to assert real behavior (success + in-band denial).

Scope (this increment)

Single command with quoted args. Pipelines / redirections / && / globbing follow as separate increments on #34.

Test plan

just check green (fmt + clippy all-features & no-default-features + workspace tests). Security keystones covered: echo "a|b" is a literal arg; $(...) is refused and never runs; a refused pipeline means the downstream rm never executes; out-of-scope exec → structured denial; MCP boundary surfaces denials in-band.

Part of #34.

🤖 Generated with Claude Code

WHAT: Replace the fail-closed shell stub with the argv + safe-subset engine (ADR 0005 D3). New parse.rs is the exec-funnel classifier: a quote-aware tokenizer that resolves a cmd string to a single command's argv, refusing dynamic constructs by design ($(...), backticks, subshells = Dynamic) and the not-yet-implemented operators (|, &&, ||, ;, <, >, globs, $VAR = Unsupported), distinct from Malformed. shell_tool.rs now accepts program+args or cmd, checks the exec/cwd leash, spawns directly (std::process in spawn_blocking) with capture + timeout, and returns structured denied envelopes; sandbox_kind is reported honestly (None — L2 advisory until L3, ADR 0005 D1).

WHY: Per ADR 0005 the boundary is L3 and the engine is L2 convenience. This un-stubs a usable, honestly-advisory confined shell without the brush fork or upstream dependency, and structurally excludes the dynamic attack surface. Increment 1 = single command; pipelines/redirects/&&/globs follow (#34). brush-bridle-core stays the deferred reversible engine (#20).

TEST: 26 crate tests incl. the security keystones — quoted metacharacter is a literal arg (echo "a|b" prints a|b), command substitution is refused and never runs, a refused pipeline means the downstream rm never executes. just check green (fmt + clippy all-features & no-default-features + workspace tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01HMGPEApE4XfwgMhgFbRn6c
@hartsock hartsock added the risk:low Low-risk change label Jun 25, 2026
@hartsock hartsock merged commit cabd72f into main Jun 25, 2026
1 check passed
@hartsock hartsock deleted the issue-34/argv-safe-subset-engine branch June 25, 2026 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:low Low-risk change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant