{"record":{"id":"c0184cfe41575098","repo":"JuliusBrussee/caveman","slug":"cave-sandbox-child-process-containment-unavailable","errorCode":"cave_sandbox_child_process_containment_unavailable","errorMessage":"cave_sandbox_child_process_containment_unavailable","messagePattern":"cave_sandbox_child_process_containment_unavailable","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":4736,"sourceCode":"    ledger.reservedUsd = Math.max(0, ledger.reservedUsd - ceilingUsd);\n  }\n}\n\nasync function executeSandboxedTool(\n  entryPath: string,\n  sourceFiles: readonly string[],\n  stagingRoot: string,\n  toolName: string,\n  params: unknown,\n  timeoutMs: number,\n  allowSideEffects: boolean,\n  profile: RunOptions[\"sandboxProfile\"],\n  executionContext: InternalExecutionContext,\n  toolDefinitionSha256: string,\n  signal?: AbortSignal,\n): Promise<unknown> {\n  if (profile?.childProcess === true) {\n    throw new Error(\"cave_sandbox_child_process_containment_unavailable\");\n  }\n  // `network: true` used to skip the OS network namespace entirely, granting the\n  // tool UNRESTRICTED egress while credentials sit in its env — an exfiltration\n  // hole, not a feature. There is no scoped-egress mechanism\n  // yet (a parent-owned CONNECT proxy bound to an allow-list is the tracked\n  // follow-up), so unbounded egress fails closed rather than being granted. Every\n  // sandboxed tool now runs under the OS boundary below.\n  if (profile?.network === true) {\n    throw new Error(\"cave_sandbox_network_egress_unbounded\");\n  }\n  const requestedCredentialEnv = profile?.credentialEnv ?? [];\n  const childEnv = buildSandboxToolEnv(requestedCredentialEnv);\n  // Validate every collapsed grant before allocating per-call state. Refused\n  // roots must fail without leaving a caveman-agent-tool-* workspace behind.\n  const sourceReadFlags = sandboxSourceReadFlags(sourceFiles, stagingRoot);\n  const workspace = await realpath(await mkdtemp(`${tmpdir()}/caveman-agent-tool-`));\n  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));\n  const worker = fileURLToPath(new URL(\"./tool-worker.js\", import.meta.url));","sourceCodeStart":4718,"sourceCodeEnd":4754,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L4718-L4754","documentation":"The sandbox runner only supports child-process containment when the underlying OS boundary can provide it; if a tool's sandbox profile requests profile.childProcess === true in an environment where that containment cannot be provided, the runtime fails closed with this error instead of running the tool with processes escaping the sandbox. It is a hard safety refusal, not a transient failure.","triggerScenarios":"Executing a sandboxed tool whose RunOptions.sandboxProfile sets { childProcess: true } (runtime.ts:4736) on a platform or runtime build where child-process containment is unavailable (e.g. no namespace/seccomp support, unsupported OS).","commonSituations":"Running on macOS or Windows where the Linux namespace-based child-process containment is absent; running inside containers without the required kernel features; upgrading to a version that removed a weaker childProcess implementation and now fails closed.","solutions":["Set sandboxProfile.childProcess to false (or omit it) if the tool does not strictly need to spawn subprocesses.","Run on Linux with the kernel features (namespaces/seccomp) that provide child-process containment.","Run the agent outside restrictive containers (or with --privileged equivalents) so the OS boundary is available.","If the tool must spawn processes, restructure it to do its work in-process or via the tool host rather than inside the sandboxed tool."],"exampleFix":"// before\nawait runTool(parser, { sandboxProfile: { childProcess: true } });\n// after: let the tool run without child-process spawns\nawait runTool(parser, { sandboxProfile: { childProcess: false } });","handlingStrategy":"validation","validationCode":"if (profile?.childProcess === true && !platformSupportsChildProcessContainment()) {\n  throw new Error('childProcess containment unavailable on this platform; disable the flag');\n}","typeGuard":"const childProcessContainable = (profile) =>\n  !profile?.childProcess || (process.platform === 'linux' && kernelNamespacesAvailable());","tryCatchPattern":"try {\n  await runTool(tool, { sandboxProfile: profile });\n} catch (e) {\n  if (e.message === 'cave_sandbox_child_process_containment_unavailable') {\n    await runTool(tool, { sandboxProfile: { ...profile, childProcess: false } });\n  } else throw e;\n}","preventionTips":["Only set childProcess: true on Linux hosts with namespace support.","Design tools to avoid spawning subprocesses.","Test sandbox profiles on the target platform before deploying."],"tags":["sandbox","security","child-process","fail-closed"],"backgroundTag":"unsupported-platform","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}