fix: restore bounded agent sandbox execution

This commit is contained in:
2026-07-11 02:27:05 +08:00
parent 035c264179
commit 7fcb57013e
14 changed files with 137 additions and 82 deletions
@@ -93,23 +93,26 @@ The boundary is enforced by the Claude Code SDK's built-in sandbox
its Bash subprocesses run sandboxed; if the sandbox can't start, `query()`
emits an error and exits rather than running unsandboxed.
- `sandbox.allowUnsandboxedCommands: false` — a tool cannot opt out with the
SDK's `dangerouslyDisableSandbox` input.
SDK's `dangerouslyDisableSandbox` input. Claude Code 2.1.202 does not enforce
that option reliably, so a host-side `PreToolUse` hook also denies every
Bash request whose input explicitly sets `dangerouslyDisableSandbox: true`
before a process can start.
- `sandbox.filesystem.denyRead: ["/"]` with `allowRead` for the canonical
current workspace and a small named system-runtime set — normal reads stay
in the run's workspace while `/bin`, shared libraries, CA certificates,
fonts and the configured `cph` executable remain available as the external
tool exception described above.
- `sandbox.filesystem.allowWrite` confines deliverable writes to the canonical
ADR-0007 workspace and opens one service-owned SDK scratch directory under
`/tmp/cph-agent-<uid>/<project-hash>`. Config/cache/home stay beneath
- `sandbox.filesystem.allowWrite: [workspaceDir]` confines persistent host
effects to the canonical ADR-0007 workspace. Config/cache/home stay beneath
`.cph/agent-runtime/`; `TMPDIR`, `TMP`, `TEMP`, and `CLAUDE_CODE_TMPDIR` all
point at the project-keyed scratch directory. Its canonical prefix is capped
at 64 bytes so the SDK can append randomized `socat` bridge socket names
without exceeding Linux `sockaddr_un.sun_path`. The per-Organization OS UID,
project hash, mode `0700`, symlink rejection, and exact sandbox allowlist keep
that scratch isolated; sibling scratch paths and ordinary `/tmp` remain
denied. Root is denied for writes and only the workspace plus that exact
internal scratch directory are re-opened.
point at the workspace-local `.cph/t`. The workspace allocator uses stable
compact Organization/Project path segments, deployment requires a short
workspace root, and the canonical temp prefix fails fast above 56 bytes so
the SDK can append randomized `socat` bridge socket names without exceeding
Linux `sockaddr_un.sun_path`. Bubblewrap shadows non-allowlisted host trees
with disposable tmpfs mounts: a shell write there may succeed inside that
private namespace, but it cannot mutate the corresponding host path. The
Linux proof checks host state after the sandbox exits.
- The SDK subprocess environment replaces rather than spreads `process.env`.
Only provider protocol variables and non-secret runtime variables cross the
boundary; database, Feishu and Hub session credentials never enter it.