Skip to content

fix(agent-guard): see through env/command; refuse a quoted ~ and the eval wrapper's other spellings - #1691

Merged
potiuk merged 2 commits into
apache:mainfrom
potiuk:fix/agent-guard-prefixes-1679
Oct 17, 2026
Merged

potiuk merged 2 commits into
apache:mainfrom
potiuk:fix/agent-guard-prefixes-1679

Conversation

@potiuk

@potiuk potiuk commented Oct 17, 2026

Copy link
Copy Markdown
Member

Summary

Closes #1679, plus one gap found while running the evals for #1683.

  • Prefix wrappers. Every guard now looks through the wrappers that run their argument as the command: env (GNU/BSD options, including clustered short flags such as -iu NAME, -uNAME and -iS'…', the long forms, the signal options, and NAME=value assignments, which still count as inline overrides) and command (-p). Before, env git commit … or command gh pr comment … reached no guard. command -v git is a lookup and is left alone.
  • Fail closed. A wrapper the guards cannot parse makes dispatch refuse the line (agent-guard[wrapper]). That covers an undocumented option, an option missing its value, a -S string that does not tokenise, and nesting deeper than a generous limit.
  • Quoted ~. vetted-ops-spelling accepts a ~ base only when the raw line carries it bare and space-separated. A quoted or escaped ~ ("~/…", ~"/…", \~/…) and the --project=~/… form are not expanded by the shell, so they would run inside the sandbox and fail.
  • Wrapped excluded commands. The sandbox exclusion matches a command line by its start, so the right spelling behind env / command still runs sandboxed. This is now refused, with the command to run instead.
  • The eval wrapper. magpie-run-evals.sh launched by anything but a bare ~/.claude/scripts/magpie-run-evals.sh at the start of the line is refused: an expanded home, $HOME, a quoted ~, a repository copy, or a wrapper. Each of those runs inside the sandbox, where every case errors without being graded, which happened while running the fix(security-tracker): harden the local and Jira backends after review #1683 evals. Override: MAGPIE_ALLOW_SCRIPT_PATH=1.
  • AGENTS.md's golden rule and the agent-guard README cover both.

Type of change

  • Skill change
  • Tool / bridge contract (agent-guard guard set)
  • Python package (tools/agent-guard engine + plugin copy)
  • Groovy reference impl
  • Cross-cutting (AGENTS.md golden rule)
  • Documentation
  • Project template
  • CI / dev loop

Test plan

  • prek run --all-files passes
  • tools/agent-guard tests. New cases cover:
    • every guard family through each wrapper form, including clustered and attached option values and -S;
    • an inline override after env;
    • six unparsable wrappers, each refused;
    • quoted, escaped and =-form ~;
    • wrapped vetted launches;
    • eight wrong spellings of the eval wrapper, plus its override.
  • roborev claude-code security review of the branch: pass. The first commit's security review findings are fixed in the second.

Notes for reviewers

  • Still not unwrapped, and unchanged by this PR: other wrappers that run their argument (nice, nohup, time, timeout, bash -c, exec, GNU env -), and shell syntax the guards' shlex tokeniser reads differently from the shell (a newline as a command separator, $'…' quoting, command substitution). These are engine-wide parser differentials; they bypass every guard today and are worth their own issue. The guards stay defence in depth, not the sandbox.

Gen-AI disclosure: prepared with the assistance of Claude Code (Claude Opus 5); reviewed by the author.

🤖 Generated with Claude Code

…eval wrapper's other spellings

Closes the gaps tracked in apache#1679, plus one found while running evals.

- Every guard now looks through the prefix wrappers that run their
  argument: `env` (with -i, -u NAME, -S "...", and NAME=value
  assignments, which still count as inline overrides) and `command`
  (-p). `env git commit ...` used to reach no guard at all.
  `command -v git` is a lookup and is left alone.
- vetted-ops-spelling refuses a quoted or escaped `~` in the
  --project/--from value: the shell does not expand it, so uv gets a
  directory named `~` and the exclusion does not match.
- A sandbox exclusion matches a command line by its start, so the right
  spelling behind `env` / `command` still runs sandboxed: refused, with
  the command to run instead.
- `magpie-run-evals.sh` launched by any path but the literal
  `~/.claude/scripts/magpie-run-evals.sh` (an expanded home, $HOME, a
  quoted `~`, a repository copy, a wrapper) is refused: it would run
  inside the sandbox and every eval case would error ungraded.
  Override: MAGPIE_ALLOW_SCRIPT_PATH=1.

AGENTS.md's golden rule and the agent-guard README cover both.

Generated-by: Claude Opus 5
… spelling

Closes the bypasses a security review found in the previous commit:

- Unwrapping stopped after eight nested wrappers and left `env` as the
  head, so nine wrappers reached no guard. It now runs until no wrapper
  is left; a line deeper than a generous limit, an `env` option that is
  not documented, a `command` flag other than -p, or a -S string that
  does not tokenise marks the segment unparsed, and dispatch refuses it.
- `env` options are parsed as GNU / BSD env documents them: clustered
  short flags (`-iu NAME`, `-uNAME`, `-iCdir`, `-iS'...'`), long forms
  with `=` or a separate value, and the signal options.
- A `~` spelling is accepted only when the raw line carries it bare and
  space-separated: a quote after the `~` (`~"/..."`) or the
  `--project=~/...` form leaves the `~` unexpanded as surely as a
  leading quote does.

Generated-by: Claude Opus 5
@github-actions github-actions Bot added family:setup setup-* skills family:tools tools/* family:docs Docs, MISSION.md, READMEs capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) substrate:action-guard Tool substrate: deterministic pre-tool-use command guards labels Oct 17, 2026
@potiuk
potiuk merged commit b00d4b5 into apache:main Oct 17, 2026
11 checks passed
potiuk added a commit that referenced this pull request Oct 19, 2026
Hand-sync of tools/spec-loop/specs/ with the functionality that landed
between 43e4c6e and ccb53a3.

Specs changed:
- adapters: Gmail drafting through the gmail-plaintext MCP tools, not the
  CLI (93051d5, #1681); the Jira CLI's explicit --deployment and the
  tracker backends shipped as generated plugin copies (1cf5cee, #1683);
  local-tracker report files and folders created 0600 / 0700 (68ec4a3,
  #1700).
- agent-isolation-sandbox: agent-guard unwrapping env / command and
  refusing env -S (b00d4b5, #1691; 4aea234, #1694); the macOS touch
  overlay shown whether or not activation is granted (0d9c168, #1697);
  the sandbox-lint workflow refusing a tracked .claude/settings.local.json
  (51a610b, #1692) and sandbox-lint refusing the Gmail token path in
  allowRead (93051d5, #1681).
- vetted-command-surface: the spelling guard refusing a quoted ~, the right
  spelling behind env / command, and other spellings of the eval wrapper
  (b00d4b5, #1691).
- adoption-and-setup: the tracker-backend question on every first setup
  and the Jira label mapping and doctor gate (1cf5cee, #1683).
- .last-sync bumped to ccb53a3.

No spec change needed (specs already updated in the commit itself):
ccb53a3, 9a8ec5f, 9955d7d, c9f34cc, 76cc6e8, 1d773a8,
16014a2.

No functional change for the specs: d4b7dc3 (review-body wording),
77e8145 (test timing), 8d606d6 (eval fixtures), a04891f (the
previous sync).

Generated-by: Claude Opus 5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) family:docs Docs, MISSION.md, READMEs family:setup setup-* skills family:tools tools/* substrate:action-guard Tool substrate: deterministic pre-tool-use command guards

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agent-guard: env/command prefixes and a quoted ~ slip past vetted-ops-spelling

1 participant