Skip to content

test(runner): check the Claude Code sandbox's boundary nightly - #543

Merged
kixelated merged 2 commits into
mainfrom
quest/m0/kix/sandbox/claude-boundary
Nov 7, 2026
Merged

kixelated merged 2 commits into
mainfrom
quest/m0/kix/sandbox/claude-boundary

Conversation

@kixelated

@kixelated kixelated commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m0/kix/sandbox/claude-boundary.md: Claude Code jobs get the same checked-in hostile-repository boundary test opencode jobs got in moq-dev/quest#404.

Summary

  • test/live/runner-sandbox-claude.test.ts: a scripted Anthropic Messages endpoint, set as the account's endpoint in $CLAUDE_CONFIG_DIR/settings.json (env.ANTHROPIC_BASE_URL), drives the pinned Claude Code through Claude.launch, with the runner's real openSandbox policy, in a checkout that tries to:
    • read a credential planted in the account's home, by path and through a committed symlink (Bash, and the Read tool);
    • write the runner's state, a shared temp directory, and a hook into the bare clone (Bash, and the Write tool);
    • get a committed .claude/settings.json hook to run (SessionStart and PreToolUse) and a committed CLAUDE.md to reach the model;
    • connect to the Nix daemon's socket and a host Unix socket;
    • reach the model's server through the sandbox's proxy and directly.
      It also checks that a command's HOME is the attempt's, its TMPDIR is inside the attempt's private directory and writable, its environment holds neither the credential nor the kix tools' token, it inherits no socket beyond its stdio, and nothing is left in the checkout. Each shell attempt first escapes unconfined, and the test fails rather than skips on Linux when the sandbox can't start.
  • flake.nix: the devShell sets QUEST_RUNNER_CLAUDE to the runner package's pinned Claude Code (Linux only, as for opencode and srt), so just runner-sandbox and the nightly sandbox.yml run it. nixpkgs' Claude Code already wraps bubblewrap and socat on its PATH, so the workflow needs no change beyond its comment.
  • README, justfile, workflow comment, and the epic README say the boundary tests cover Claude Code too.

Public API and wire impact: none (tests, devShell, docs).

Choices made without asking

  • New file, not an extension of runner-sandbox.test.ts, per the coordinator (a sibling quest owns that file). Its small helpers (shell, listen, listenUnix, git) are copied rather than moved to a shared module, also per the coordinator.
  • The descriptor check lists ls -l /proc/self/fd/, not the shell's own fds. Claude Code wraps a command without its own stdin redirect in eval '…' < /dev/null, so bash keeps a close-on-exec copy of its original stdin (Claude Code's stdin socketpair) on fd 10. That copy is stdio, not a new channel, and no exec'd command inherits it; listing an exec'd program's fds checks exactly what a command inherits.
  • No pin on the offered tool list, unlike opencode's test. Claude Code runs in dontAsk with an allowlist (Bash, Edit(//<checkout>/**), WebSearch, mcp__kix), so a new tool is refused unless the runner allows it. Pinning its ~25 tools would fail every bump without guarding anything.
  • The fake API key sits in settings env, which Claude Code passes to commands, so the "unreadable secret" is a planted .credentials.json in the account home, as the quest planned.
  • Bash refusals must come from the sandbox: the test fails if Claude Code's permission check, rather than the sandbox, refused an attempt, since that would leave the sandbox untested.

Validation

  • just check, just test: pass.
  • This host can't run the sandbox directly: Ubuntu's bwrap-userns-restrict AppArmor profile breaks bwrap's loopback setup (bwrap: loopback: Failed RTM_NEWADDR), for opencode's test on main too. The nightly workflow sets the sysctl instead. I ran just runner-sandbox's vitest command in a privileged rootless podman container (ubuntu:24.04, host /nix mounted): the Claude test passed 3 of 3 runs.
  • Mutation: with sandbox.enabled: false in claudeSettings, the test fails (reads the account's credentials by absolute path).
  • By hand: the planted .claude/settings.json hook does fire when Claude Code loads project settings (--setting-sources user,project), so its refusal under --setting-sources user is not vacuous.
  • Unix sockets: inside the sandbox, socat reports socket(1, 1, 0): Operation not permitted, so the seccomp filter, not chance, refuses them.

Suggested follow-ups

  • Plan: the nightly Sandbox run on main failed today: opencode now offers eight more tools (opencode_session_move, opencode_session_rename, opencode_worktree_create/list/remove, opencode_models, MCP resource tools), most likely from the nixpkgs bump in moq-dev/quest#502. Session move/rename in the shared account database is exactly what opencode-data assumes jobs can't reach, so it needs a review and a deny in jobConfig, not just an updated list. No open PR fixes it.

Models

Size: M
Implementer: Claude Code, claude-opus-5-5, medium effort

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1b768-c8d8-7d92-bc52-9f663ee838c9, round 1
  • Head: 7d38907
  • Base: origin/main at d29a0d2
  • Score: 9/10, Code review found no actionable defects; boundary coverage and nightly integration look consistent, but runtime validation remains unverified because builds and tests were not run.
  • Findings: none

@pullfrog

pullfrog Bot commented Nov 7, 2026

Copy link
Copy Markdown

Pullfrog  | Review this ➔

A scripted Anthropic Messages endpoint, set as the account's endpoint in
its settings, drives the pinned Claude Code through Claude.launch in a
hostile checkout: credential reads by path and symlink, writes outside the
checkout, a committed project hook and CLAUDE.md, the Nix daemon's and a
host Unix socket, the network directly and through the proxy, and the
file tools outside the checkout. The devShell names the pinned Claude Code
so `just runner-sandbox` and the nightly workflow run it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title test: Claude Code sandbox boundary test runs nightly test(runner): check the Claude Code sandbox's boundary nightly Nov 7, 2026
@kixelated
kixelated marked this pull request as ready for review November 7, 2026 19:08
@coderabbitai

coderabbitai Bot commented Nov 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 40 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: dcae2250-e06b-492e-8aff-92751bb11f90
Commits

Reviewing files that changed from the base of the PR and between 1c3cf6a and 7d38907.

Files selected for processing: 7

  • .github/workflows/sandbox.yml
  • README.md
  • flake.nix
  • justfile
  • quest/m0/kix/sandbox/README.md
  • quest/m0/kix/sandbox/claude-boundary.md
  • test/live/runner-sandbox-claude.test.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed head 7d38907 against base 1c3cf6a.

No actionable findings. The scripted endpoint exercises the real Claude launch path, checks returned tool results and host-side effects, and pairs the shell escape attempts with unconfined controls. The Linux Nix shell supplies the pinned Claude package and its sandbox dependencies; the existing live-test selection includes this file without adding it to the merge gate. Direction: the focused nightly regression test fits the planned boundary check; no revision suggested.

Verification: GitHub-only static inspection of the changed files, Claude adapter, sandbox policy, test selection and pinned Nix packaging. No Nix build, live sandbox test or security exploit was executed, and the reported container/mutation runs were not independently reproduced. quest context and quest guide were read from base-revision sources rather than run. This is not a comprehensive security audit.

(Written by review (OpenAI))

@kixelated
kixelated added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 4b3a874 Nov 7, 2026
14 checks passed
@kixelated
kixelated deleted the quest/m0/kix/sandbox/claude-boundary branch November 7, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant