fix: prove Linux Agent sandbox boundary

This commit is contained in:
2026-07-10 17:59:56 +08:00
parent f4968d6657
commit f07f280b8f
19 changed files with 381 additions and 55 deletions
+9 -5
View File
@@ -19,11 +19,12 @@
*
* ADR-0018: the agent execution surface is bounded by the run's workspace.
* The SDK sandbox (bubblewrap on Linux, seatbelt on macOS) confines the Claude
* Code process and its subprocesses at the OS level — writes are confined to
* the workspace, host reads are denied by default and re-opened only for the
* workspace plus named system runtimes, and `failIfUnavailable` hard-fails if
* the sandbox can't start. The subprocess gets a minimal environment and SDK
* credential protection removes provider secrets from Bash. This upholds `AgentFileOp.Authorized`
* Code process and its subprocesses at the OS level — writes, including the
* SDK's Unix-socket scratch, are confined to the workspace. Host reads are
* denied by default and re-opened only for the workspace plus named system
* runtimes, and `failIfUnavailable` hard-fails if the sandbox can't start. The
* subprocess gets a minimal environment and SDK credential protection removes
* provider secrets from Bash. This upholds `AgentFileOp.Authorized`
* (ADR-0018 / `Spec.System.AgentSurface`) without re-implementing the
* `workspace.ts` `confine()` path validator as a tool wrapper — the OS sandbox
* is the mechanism, the contract pins the invariant.
@@ -70,6 +71,8 @@ export interface RunRequest {
readonly sessionId: string;
readonly prisma: PrismaClient;
readonly onStream?: StreamCallback;
/** Optional diagnostic sink for Claude SDK subprocess stderr. */
readonly onSdkStderr?: ((data: string) => void) | undefined;
/**
* Abort controller for the run. Aborting the signal interrupts the SDK query
* (it stops the agent loop and cleans up); the run resolves with status
@@ -153,6 +156,7 @@ export async function runAgent(req: RunRequest): Promise<RunResult> {
if (req.resumeSessionId !== undefined) options.resume = req.resumeSessionId;
if (req.mcpServers !== undefined) options.mcpServers = req.mcpServers;
if (req.abortController !== undefined) options.abortController = req.abortController;
if (req.onSdkStderr !== undefined) options.stderr = req.onSdkStderr;
const conversation = query({
prompt: req.prompt,