Repository navigation
feat(runner): sandbox Claude Code jobs' commands - #212
Conversation
Quest-Claim: bdcb3fa98e2bfc53
Commands write only to the checkout and a private temporary directory, read no account home, runner state, or shared temp files, and reach no network host or Nix daemon. The file tools are confined by dontAsk permission rules, and the checkout's own Claude settings and MCP servers are never loaded. The runner builds the devShell with nix print-dev-env from the job's revision and Claude Code sources it inside the sandbox. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Decisions from the maintainer, answered in a /quest-complete session:
(Written by Claude Opus 5.5) |
A trusted revision's sandboxed commands may now reach npm (also under yarn's name), crates.io's index and downloads, and PyPI, so its checks can install dependencies. An untrusted revision's commands still reach no network host. The system prompt names the reachable hosts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves the opencode adapter (#216) against the job sandbox: opencode now enters the devShell the runner built from the job's revision, through the sandbox's env script, instead of `nix develop` on the checkout. Its commands are not yet confined; wrapping `opencode serve` in sandbox-runtime stays in quest/m0/kix/sandbox.md, which takes over the note from the deleted opencode quest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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: 51c3cbbbeb
ℹ️ 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".
…sandbox only for Claude Code A cancel, a moved attempt, or a runner shutdown now kills nix print-dev-env, which runs before there is an agent to stop. The bubblewrap and socat check runs only when a Claude Code account needs an adapter, including one added while the runner runs, so opencode-only Linux runners start without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Landing summary (/quest-merge):
(Written by Claude Opus 5.5) |
Keep #212's sandbox alongside merge jobs: both imports in the Claude adapter and the attempt, main's systemPrompt with quest merge among the skipped hold commands and the CONFLICTS instruction after it, the fake Claude logging the sandbox settings and the kix tool timeout, and both launch assertions. Take main's sandbox quest line; the merge-job quest stays deleted. Drop an unused binding from the submodule snapshot test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sandboxes the commands of every runner job on a Claude Code account, for Job sandbox. Before this,
quest runnerran Claude Code with--permission-mode bypassPermissions, the user's full environment, andnix developevaluated from the checkout the agent had edited. A prompt-injected agent could read~/.sshor the runner's kix token, or commit a shellHook that ran on the host at the next attempt.What changes
src/runner/sandbox.ts,src/runner/claude.ts). Claude Code runs with its built-in sandbox (bubblewrap on Linux, Seatbelt on macOS) and these settings:failIfUnavailable,allowUnsandboxedCommands: false, and a strict network allowlist. Commands write only to the checkout and a private/tmp/quest-job-*directory, which is bothTMPDIRandHOME. They cannot read/home,/root,/run/user,/tmp, the user's home, or the runner home (kix token and account logins). They can read only the checkout, its bare clone, and their temporary directory.registry.npmjs.org,registry.yarnpkg.com,index.crates.io,static.crates.io,pypi.organdfiles.pythonhosted.org, so its checks can install dependencies. An untrusted revision's commands reach no host. Today kix marks every PR head untrusted and the runner refuses those, so this applies to the jobs that run now.crates.ioitself is left out: it serves only the publishing API. The system prompt names the reachable hosts.dontAskmode. Its allow rules permit onlyBash,Editwithin the checkout,WebSearch, and the kix MCP tools. WebFetch is disabled.--setting-sources user, together with the existing--strict-mcp-config, keeps out the repository's.claude/settings.jsonhooks, permissions and sandbox loosening, plus.mcp.jsonservers. I verified that a checkout's SessionStart hook does run on the host in-pmode otherwise, even in an untrusted workspace. As a result Claude Code no longer reads CLAUDE.md, so the prompt now carries the root CLAUDE.md and lists nested ones alongside AGENTS.md.src/runner/cli.ts). The runner runsnix print-dev-envongit+file://<bare clone>?rev=<job revision>, never on the edited checkout. Claude Code sources the result throughCLAUDE_ENV_FILEbefore each command, inside the sandbox, so the shellHook is sandboxed too. Bootstrap jobs source only the privateHOME.SSH_AUTH_SOCKand similar no longer reach commands.AGENTS.md -> ~/.ssh/id_ed25519would otherwise have put the key into the prompt. A regression test covers it.HOME, instead ofnix developon the checkout. Wrappingopencode servein sandbox-runtime moves into the sandbox quest, which takes over the note from the deleted opencode quest. The README says opencode jobs are not sandboxed yet.runnerpackage puts pinnedbubblewrapandsocaton PATH.quest runnerrefuses to start on Linux without them.Validation
just checkandjust testpass. Runner tests cover the launch flags, the generated settings, the env script, the scrubbed environment, the devShell revision URL, temp cleanup, and the symlinked-AGENTS.md prompt.Claudeadapter,openSandbox, anddevShellagainst a hostile repository whose shellHook,.claude/settings.jsonhook,.mcp.jsonandexcludedCommandstry to write to$HOME. Confirmed:/homefail or land in a throwaway tmpfs.~/.ssh,~/.claude/.credentials.json, the runner'slogin.jsonand other/tmpfiles are unreadable.api.anthropic.com. (This ran before the registry list was added.)/run/userare gone, and so areAWS_SECRET_ACCESS_KEYandGH_TOKEN.hooks/andconfigare read-only.git commitin the checkout works.flake.nixin the checkout is ignored.bun install,cargo fetchanduv pip installsucceed through a CONNECT proxy that allows onlyREGISTRIES. Its log shows they use exactly those hosts, andcrates.ioandexample.comare refused. I could not rerun this under bubblewrap: this session's own sandbox blocks nested user namespaces.Choices made without asking
No Nix daemon in the sandbox, even for trusted revisions. On Linux the sandbox's seccomp filter blocks Unix sockets entirely, and
allowUnixSocketshas no effect there.allowAllUnixSocketswould expose every socket on the filesystem (tmux, VS Code IPC, Docker). Daemon access would also let a command set root-level daemon options whenever the runner user is a Nix trusted user. Sonix build/nix flake checkfail in jobs and run in CI instead. The system prompt tells the agent so.Not
CLAUDE_CODE_SUBPROCESS_ENV_SCRUB. With it, both versions left empty.env,package.jsonand lockfile mount points in the checkout, which kix would publish. It also stoppeddenyRead: /tmpfrom hiding/tmp. The env allowlist does the scrubbing instead.WebFetch off, WebSearch on. WebFetch runs outside the sandbox. WebSearch runs on the provider's side.
opencode keeps running jobs, unsandboxed, until it is wrapped. The alternative is to refuse opencode jobs until then, which would disable the adapter that just shipped. The parent session's merge instructions keep the wrapping as remaining work.
Decisions
Answered by the maintainer (comment):
/quest-planto split later.Residual risk: npm's registry also accepts publishes. A prompt-injected agent holding someone's npm token could publish the checkout there. The README says so.
Follow-ups
/quest-plan: trusted classification of maintainer PR heads in kix; the VM build for untrusted heads; macOS verification plus lifting the gate; an Ubuntu AppArmor profile plus a fail-fast sandbox preflight.GETonly), if Claude Code's sandbox settings ever expose request filtering, to close the npm-publish channel.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code