Repository navigation
Run trusted commands outside the sandbox, as Codex's allow rules do - #309
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f31142a7b7
ℹ️ 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".
25ce6bc to
63c2ac5
Compare
|
Generated by Claude Code |
A tool with a daemon of its own, such as mcpx, can't run in a bot's sandbox: every call opens its lock file under ~/.local/state, and a daemon started there would keep the sandbox's limits (on Linux it dies with the command). Codex answers this with execpolicy rules that allow a command prefix and run it unsandboxed; Claude Code with excludedCommands. Bots now take `unsandboxed`, command prefixes (`mcpx`, `docker compose`) that run outside their sandbox without asking. Only a plain script matches, as Codex parses one: words, quoted or not, joined by &&, ||, ;, | or newlines, every command a match. Expansions, substitutions, redirections, subshells, background jobs and comments stay sandboxed, as does a script with any other command. `create` and `configure` take it with the sandbox, `agent run --unsandboxed` sets it, and a bot a sandboxed bot makes keeps only what its creator has. Sonny reads `[sonny] unsandboxed = ["mcpx"]` from ~/.agent/config.toml and gives it to every confined lead and task; a lead whose list differs is given it again when Sonny opens it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHN4UHiWFxnggwKHoV
In `full` an agent has no gate, so a confined task's call that asks to leave its sandbox was always refused (`escalation_refused`), and agents worked around it through panes. As under Codex's full access, a task now runs unconfined in `full`: start_task reads the lead's mode, and an /approvals pick sets the tasks' sandboxes along with their gates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHN4UHiWFxnggwKHoV
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHN4UHiWFxnggwKHoV
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGHN4UHiWFxnggwKHoV
#318's restamp still read the definition through Places::plist, which #344 renamed to Places::job, so main did not compile. The store id it rewrites now has a unit form too, and restamp runs at start on Linux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013jkdGF2ShRt6WjfWgiPdZF
63c2ac5 to
a0727fa
Compare
|
Following up on the comment above: I rebased onto main cc10fb8 and ported #351's commit as a0727fa, so CI can go green without waiting for it. Once #351 merges, the ported commit becomes a no-op. Locally, before the port, Generated by Claude Code |
Requested by George · project thread
Why
mcpx, and any tool that keeps a daemon of its own, fails in a sandboxed bot. The socket isn't the blocker: on macOS a Sonny bot with the network on can already connect to AF_UNIX sockets. Every mcpx call first opens
~/.local/state/mcpx/daemon.lockfor writing, which the sandbox refuses even when the daemon is already running. A daemon started from inside the sandbox would also keep the sandbox's limits (its MCP servers couldn't write their caches), and on Linux it would die with the command's pid namespace.I reproduced this here with a real mcpx build and a scripted bot:
What Codex does
Codex in
workspace-writeblocks this too. On macOS it doesn't allow AF_UNIX sockets unless[permissions.network] unix_socketslists them, and it doesn't let tools like this run inside the sandbox. It gets them out in one of two ways:on-requestand the auto-reviewer, there's no prompt the person sees.prefix_rule(pattern=[...], decision="allow")makes a matching command skip approval and bypass the sandbox (bypass_sandbox: trueincore/src/exec_policy.rs), but only when every command in the parsed script matches.Claude Code's equivalent of the rule is
sandbox.excludedCommands. Codex's full access has no sandbox at all.Change
Daemon
unsandboxed: a list of command prefixes such asmcpxordocker compose. Nothing is tool-specific, and nothing is in the list by default.&&,||,;,|or newlines.$, backquotes, redirections, parentheses, a lone&and comments. So does a script containing any other command (mcpx … | jq), which can still escalate.API and CLI
createandconfiguretakeunsandboxedtogether with the sandbox. It is refused without a read-only or workspace-write sandbox, and capped at 64 entries of up to 1 KiB each.agent run --unsandboxed CMDis repeatable.configureitself through its own socket.Sonny
[sonny] unsandboxed = [...]in~/.agent/config.tomlgoes to every confined lead and task. A lead whose list differs gets the new one when Sonny next opens it.fullmode, tasks now run unconfined, as their lead does and as Codex's full access does. Infullan agent has no gate, so a confined task's request to leave the sandbox was always refused withescalation_refused, and agents worked around it through panes.start_taskreads the lead's mode, and an/approvalspick sets the tasks' sandboxes along with their gates.Docs: RUST_PROTOTYPE (settings table, configure, sandbox section), CLI.md, sonny/README.
Known edge
The command is resolved on the daemon's
PATH, so an entry is only as trusted as the folders on it. The same holds for Codex's rules. This is noted in the docs, and per the "good enough" safety stance it isn't built further.Daemon cost
When
unsandboxedis empty,Bot::unsandboxedreturns on aVec::is_emptycheck before parsing anything. It adds no store work and no per-bot state.Tests
sandbox::tests::only_a_plain_script_of_allowed_commands_leaves_the_sandboxcovers the matcher: 6 scripts that match and 23 that don't.test_approvals.test_a_command_its_client_trusts_runs_outside_the_sandbox_without_asking: under a manual gate, a trusted command writes outside its roots without an approval, both alone and in a parallel batch, while a mixed script stays confined. It also checks configure validation and inheritance.test_served.test_a_lead_starts_a_task_in_a_worktree_of_its_own: a task of anautolead is confined, and a task of afulllead is not.approvalstests coverregatesetting a task's sandbox with its mode, and clientconfigtests cover the new key.cargo test --workspace, clippy with-D warnings,cargo fmt --check, and the full runtime suite (tests.run --jobs 4, AGENT_TEST_SANDBOX=1 with bubblewrap).🤖 Generated with Claude Code
https://claude.ai/code/session_013jkdGHN4UHiWFxnggwKHoV