Skip to content

fix(sandbox): let vetted-op writes run outside the sandbox, on the discovered policy only - #1956

Merged
potiuk merged 3 commits into
apache:mainfrom
potiuk:fix/vetted-op-sandbox-exclusion
Nov 1, 2026
Merged

potiuk merged 3 commits into
apache:mainfrom
potiuk:fix/vetted-op-sandbox-exclusion

Conversation

@potiuk

@potiuk potiuk commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Summary

Closes #1952.

  • On the Claude Code secure setup every vetted-op write failed before it started ("Project directory ~/.claude/magpie/vetted-ops does not exist"): the sandbox cannot read ~/.claude/magpie, and a sandboxed gh could not verify TLS anyway. Skills fell back to bare gh writes, bypassing the catalogue. Both launcher forms of vetted-op are now in sandbox.excludedCommands, as vetted-op-tracker already was, and both stay in permissions.ask.
  • Excluding it is safe because each operation is a fixed gh argv with no shell, parameters that may not start with -, and a body read only from the owner-only workspace — less reach than the bare gh * exclusion — and every write still prompts. sandbox-lint now fails an excluded or allowed write dispatcher that no ask rule matches.
  • Security review of the first commit found that --config undid that bound: an agent-written policy could set workspace = "~/.ssh" (yours, mode 0700, so it passes the workspace check) and post a key from it to a repository it chose. vetted-op and vetted-op-tracker now refuse --config whenever an operation runs and act only on the discovered, write-denied policy. A dry run, policy and list-ops keep it, as does vetted-op-read, which refuses writes before reading the policy. This also closes the same gap vetted-op-tracker already had on main.
  • A follow-up review found discovery itself starts from the cwd, so a policy planted under any directory and run from there would bypass the --config refusal (the same was true for vetted-op-tracker on main). The policy path is now write-denied everywhere with Edit(//**/.apache-magpie-overrides/tools/vetted-ops/**), enforced by sandbox-lint, and a symlink anywhere on the committed policy's path is refused, as it already was for the personal layers.
  • A third review found that a directory renamed into place gets past any deny glob, since the agent picks the working directory, and that the policy was checked and then opened in a separate step. The write dispatchers now act only on a discovered policy that is byte-for-byte a copy the user placed in ~/.config/apache-magpie/vetted-ops/, which no sandboxed command or agent file tool can write. Every policy file is read with O_NOFOLLOW component by component, and the home directory comes from the password database, not $HOME.
  • The old "never exclude vetted-op" rule is dropped from the setup guide, the isolated-setup-install / isolated-setup-verify checklists, the vetted-ops README, the sandbox spec and the threat model (C-E3).

Type of change

  • Python package (tools/sandbox-lint, tools/vetted-ops)
  • Settings (.claude/settings.json, tools/sandbox-lint/expected.json)
  • Docs / skill detail files (isolated-setup-install, isolated-setup-verify)

Test plan

  • prek run --all-files --skip workspace-pytest passes
  • pytest passes for tools/sandbox-lint and tools/vetted-ops (run outside the sandbox: three personal-policy tests write fixtures under a .git/**/apache-magpie/tools/vetted-ops/ path the sandbox write-denies)
  • New tests: both write dispatchers excluded and asked, never allowed; an excluded write dispatcher without ask, and an allowed one, are invariant errors; vetted-op * does not catch vetted-op-read; both write entry points refuse an agent-written --config before anything runs (the ~/.ssh workspace shape); dry run and vetted-op-read keep --config
  • setup-isolated-setup-verify eval, step-1-classify/case-7 (fixture updated): PASS
  • Full setup-isolated-setup-verify eval suite — not run

RFC-AI-0004 compliance

  • HITL — every vetted-op / vetted-op-tracker write keeps its ask prompt; sandbox-lint enforces it
  • Sandbox — the new exclusion reaches less than the existing gh * one, and the write entry points no longer act on a policy the agent can write
  • Write-access discipline — nothing new is posted without confirmation

Notes for reviewers

  • Adopters on the secure setup need the two vetted-op entries in sandbox.excludedCommands and permissions.ask (isolated-setup-update will report the drift).
  • Action needed after merge: every maintainer and adopter approves their project's policy once, in their own terminal, and again after each change to it: mkdir -p ~/.config/apache-magpie/vetted-ops && cp .apache-magpie-overrides/tools/vetted-ops/config.toml ~/.config/apache-magpie/vetted-ops/<project>.toml. Until then, vetted-op / vetted-op-tracker writes are refused with the command to run. Reads are unaffected.

🤖 Generated with Claude Code

On the Claude Code secure setup every vetted-op write failed before it
started: the sandbox cannot read ~/.claude/magpie, so uv reported the
project directory missing, and a sandboxed gh could not verify TLS
anyway. Skills fell back to bare gh writes, bypassing the catalogue.

Exclude both launcher forms of vetted-op from the sandbox, as
vetted-op-tracker already is, and keep both in permissions.ask. Each
operation is a fixed gh argv with no shell and a workspace-only body,
so the exclusion reaches less than the bare `gh *` one; the prompt
stays the gate on every write.

sandbox-lint now fails an excluded or allowed write dispatcher that no
ask rule matches. The setup guide, install and verify skills, the
vetted-ops README and the sandbox spec drop the old "never exclude
vetted-op" rule.

Closes apache#1952

Generated-by: Claude Opus 5
vetted-op and vetted-op-tracker run outside the sandbox, but accepted
--config naming any policy file. An agent-written policy could set its
workspace to a private directory such as ~/.ssh (owned by the user and
mode 0700, so it passes the workspace check) and post a key from it as
a comment on a repository the policy chose; only the ask prompt stood
in the way.

Both now refuse --config whenever an operation runs and use only the
discovered policy (the committed one, else an untampered personal
copy), which the secure setup write-denies. A dry run, `policy` and
`list-ops` still take it, as does vetted-op-read, which refuses writes
before it reads the policy. No skill passes --config to a write
dispatcher. Tests that execute writes now install their policy at the
discovered path.

Generated-by: Claude Opus 5
@potiuk potiuk added family:setup setup-* skills family:tools tools/* substrate:sandbox Tool substrate: agent isolation, egress control, settings audit labels Nov 1, 2026
@github-actions github-actions Bot added family:security security-* skills family:docs Docs, MISSION.md, READMEs capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) family:dev Repo dev: .asf.yaml, spec-loop specs, Node manifests, .claude/settings.json labels Nov 1, 2026
The write dispatchers discover their policy from the directory they run
in, but only the project's own `.apache-magpie-overrides/tools/vetted-ops`
was write-denied. An agent could plant a policy (say, a workspace of
~/.ssh) under any directory it can write and run vetted-op from there,
getting past the --config refusal. vetted-op-tracker had the same gap.

Deny `//**/.apache-magpie-overrides/tools/vetted-ops/**` everywhere,
required by sandbox-lint, and refuse a committed policy with a symlink
anywhere on its path, as the personal layers already were. The
isolated-setup-update skill no longer reports an excluded vetted-op as
a must-fix.

Generated-by: Claude Opus 5
@potiuk
potiuk merged commit 601af25 into apache:main Nov 1, 2026
15 checks passed
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:dev Repo dev: .asf.yaml, spec-loop specs, Node manifests, .claude/settings.json family:docs Docs, MISSION.md, READMEs family:security security-* skills family:setup setup-* skills family:tools tools/* substrate:sandbox Tool substrate: agent isolation, egress control, settings audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vetted-op (write dispatcher) cannot run under the Claude Code sandbox

1 participant