fix(sandbox): don't denyWrite read-only mounts; fix verify service label#21
Open
kskim9 wants to merge 2 commits into
Open
fix(sandbox): don't denyWrite read-only mounts; fix verify service label#21kskim9 wants to merge 2 commits into
kskim9 wants to merge 2 commits into
Conversation
@anthropic-ai/sandbox-runtime documents denyWrite as taking precedence over allowWrite. The previous implementation pushed every read-only mount to both allowRead and denyWrite, intending to "enforce read-only at srt level". But because denyWrite wins on conflict, this blocked writes to *any* subdirectory of a read-only mount — including the group's own workspace. In the main-group default config, projectRoot is mounted read-only and groupDir (groups/<folder>) is mounted read-write inside it. The agent crashed on first run with `EPERM: mkdir 'groups/<folder>/memory'` because denyWrite=projectRoot overrode allowWrite=groupDir. Fix: only push readonly mounts to allowRead, not denyWrite. The sandbox is allow-list-based — paths absent from allowWrite are implicitly denied, which already enforces read-only on the parent mount without blocking nested writable subdirs. Reproduced on macOS Seatbelt via @anthropic-ai/sandbox-runtime 0.0.42.
setup/service.ts derives the launchd/systemd service label from basename(process.cwd()), but setup/verify.ts was using basename(DATA_DIR) to look up the same service. Since DATA_DIR resolves to <STATE_ROOT>/data, the two scripts computed different labels: setup/service.ts: com.claudeclaw.<dirname> (e.g. .claudeclaw) setup/verify.ts: com.claudeclaw.data (always) Result: `verify` always reported SERVICE: not_found even when the service was running, causing STATUS: failed on the final setup check. Fix: import and use STATE_ROOT (= the project/data root) instead of DATA_DIR (= <root>/data) so verify and service agree on the label.
Zimondata
pushed a commit
to Zimondata/claudeclaw
that referenced
this pull request
Jun 5, 2026
CRITICAL sbusso#1: StopFailure hook replaced with Stop hook (agent/runner) HIGH sbusso#9: Added 10s TTL cache for loadSenderAllowlist (perf hotspot) HIGH sbusso#10: Async auth retry instead of execFileSync (non-blocking) MEDIUM sbusso#16: Transactional deleteTask (crash safety) MEDIUM sbusso#19: Added indexes on agent_runs table (query perf) MEDIUM sbusso#20: Error handling for extension loading MEDIUM sbusso#21: Real GroupQueue.shutdown() implementation All changes compiled and tested. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two bugs surfaced during a fresh setup with
RUNTIME=sandboxon macOS (Seatbelt via@anthropic-ai/sandbox-runtime0.0.42).1. Sandbox: read-only mounts blocked nested writable subdirs (P0)
@anthropic-ai/sandbox-runtime's schema documentsdenyWriteas "taking precedence overallowWrite" (also visible in the installed copy atnode_modules/@anthropic-ai/sandbox-runtime/dist/sandbox/sandbox-config.js:109).The previous code pushed every read-only mount to both
allowReadanddenyWrite, intending to "enforce read-only at srt level". But becausedenyWritewins on conflict, this blocked writes to any subdirectory of a read-only mount — including the group's own workspace.In the main-group default config:
projectRoot(e.g.~/claudeclaw) is mounted read-onlygroupDir(e.g.~/claudeclaw/groups/slack_main) is mounted read-write inside itThe agent crashed on first run with:
…because
denyWrite=projectRootoverrodeallowWrite=groupDir. The message loop retried 5 times then gave up — silent fail from the user's perspective.Fix: drop the
denyWrite.push()for read-only mounts. The sandbox is allow-list-based; paths absent fromallowWriteare already implicitly denied, which enforces read-only on the parent without blocking nested writable subdirs.This bug means any user of the sandbox runtime with the default
is_main=truegroup hits it the moment the agent tries to create its memory dir.2. setup:
verifyandservicedisagreed on the service labelsetup/service.tsderives the launchd/systemd label frombasename(process.cwd()), butsetup/verify.tsusedbasename(DATA_DIR). SinceDATA_DIRresolves to<STATE_ROOT>/data, the two scripts computed different labels:setup/service.tscom.claudeclaw.<project-dirname>setup/verify.tscom.claudeclaw.data(always)Result:
verifyalways reportedSERVICE: not_foundeven when the service was running, producingSTATUS: failedon the final setup step.Fix: use
STATE_ROOTinstead ofDATA_DIRinverify.tsso the two scripts agree.Test plan
RUNTIME=sandboxon macOS, single Slack DM as main group — confirmedEPERMreproduces onmainand is gone after the patchdata/sandbox-settings/*.jsonto verifydenyWriteno longer contains the project rootgroups/<folder>/memory/, completes a query, and posts to Slacknpx tsx setup/index.ts --step verifynow reportsSERVICE: running/STATUS: successagainst a running launchd servicebuildSandboxSettingsso no behavior change is expected)