feat(agent-core-v2): add OS-level sandbox for Bash and file tools#1934
feat(agent-core-v2): add OS-level sandbox for Bash and file tools#1934xuwenhao wants to merge 1 commit into
Conversation
Add an optional [sandbox] config section that wraps Bash command execution in an OS-level sandbox (bubblewrap on Linux, sandbox-exec on macOS) and tightens file-tool path policies through the permission chain: - New session/sandbox domain (L3): config section, policy resolution, bwrap/seatbelt backends, and a session-scoped ISandboxService that decides per command (sandboxed / excluded / unsandboxed / blocked). - Bash tool wraps its shell argv when sandboxed and appends an explanatory hint when the output shows sandbox-denial signatures. - Three new permission policies: sandboxed Bash auto-approve (auto_allow_sandboxed_bash), sandbox filesystem hard denies (deny_read / deny_write / read-only mode / sensitive files), and outside-workspace ask. - Fail-open with a one-time warning when no backend is available; require = true switches to fail-closed. - Network domain allowlisting config is reserved for a follow-up (Phase 3); setting allowed_domains emits a warning for now. - Docs: docs/en|zh/configuration/sandbox.md.
🦋 Changeset detectedLatest commit: 1a2404e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a2404e78d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return { | ||
| mode, | ||
| writableRoots: dedupe(writableRoots.map(expand)), | ||
| denyRead: dedupe((config.filesystem?.denyRead ?? []).map(expand)), |
There was a problem hiding this comment.
Mask sensitive paths before auto-approving Bash
When sandbox.enabled is on with the default auto_allow_sandboxed_bash = true, SandboxedBashApprovePermissionPolicyService approves sandboxed Bash without prompting, but Bash does not declare file accesses for SandboxFsDenyPermissionPolicyService to inspect. Because denyRead defaults to only the user-specified list here, a sandboxed command such as cat ~/.ssh/id_rsa or cat .env is still readable through the backend's read-only / bind unless the user preconfigures filesystem.deny_read, so the advertised sensitive-file protection does not apply to Bash.
Useful? React with 👍 / 👎.
| const additionalDirs = | ||
| cwd === workDir ? this.workspace.additionalDirs : [cwd, ...this.workspace.additionalDirs]; |
There was a problem hiding this comment.
Keep arbitrary Bash cwd out of writable roots
When the model supplies a cwd outside the session workspace, this prepends that path to additionalDirs; resolveSandboxPolicy then turns every additional dir into a writable root, and the sandboxed Bash auto-approval policy skips the prompt. For example, Bash({ cwd: "/home/alice", command: "touch .pwn" }) gets writable access to /home/alice even though it is outside the configured workspace, defeating the workspace-write boundary; only include cwd when it is already inside an allowed root, or force the normal approval path.
Useful? React with 👍 / 👎.
Summary
Add an optional OS-level sandbox (comparable to claude-code's sandbox-runtime and codex's workspace-write mode) configured through a new
[sandbox]section inconfig.toml. It addresses two risks: the agent reading sensitive local files (ssh keys,.env, credentials) and accidental destructive writes/deletes outside the workspace.session/sandboxdomain (L3): config section + schema, policy resolution,bwrap(Linux) andseatbelt(macOS) backends, and a session-scopedISandboxServicethat decides per command (sandboxed/excluded/unsandboxed/blocked)./is bound read-only, workspace roots + tmpdir are writable,deny_readpaths are masked (tmpfs over dirs,/dev/nullover files),deny_writesubtrees are re-bound read-only, and the network is off by default (--unshare-net). Denial signatures in the output get an explanatory hint appended.auto_allow_sandboxed_bash, default on); sandbox filesystem hard denies (deny_read/deny_write/ read-only-mode writes / sensitive files, promoted from ask to deny) evaluated before auto/yolo approvals; file tools accessing paths outside the writable roots fall back to an ask prompt.bwrapnot installed, or Windows) the CLI warns once and runs unsandboxed;require = trueswitches to fail-closed. Commands matchingexcluded_commandsbypass the sandbox and go through the normal permission chain.allowed_domains/allow_unix_sockets) is reserved in the schema for a follow-up phase (proxy-based, or adopting@anthropic-ai/sandbox-runtime) and currently emits a one-time warning.Example config:
Full reference:
docs/en/configuration/sandbox.md(+ zh translation).Test plan
bwrapintegration tests (skipped whenbwrapis absent): workspace write OK,$HOMEwrite denied,deny_readfile/dir masked,deny_writesubtree read-only, network blockednode scripts/check-domain-layers.mjsOK;tsc --noEmitOKagent-core-v2suite: 3694/3696 pass — the 2skillCatalogfailures reproduce on a clean HEAD and are unrelatedNotes / follow-ups
rgsubprocess are intentionally not sandboxed (documented); hook parity with claude-code