JuliusBrussee/caveman · error
cave_sandbox_network_egress_unbounded
cave_sandbox_network_egress_unbounded
Error message
cave_sandbox_network_egress_unbounded
What it means
Requesting unrestricted network access (profile.network === true) for a sandboxed tool is refused by design: previously that flag skipped the network namespace entirely, handing the tool unbounded egress while credentials were present in its environment — an exfiltration hole. Until a scoped-egress proxy exists, unbounded egress fails closed with this error; every sandboxed tool runs under the OS network boundary.
Solutions
- Set sandboxProfile.network to false (or omit it) and perform outbound calls outside the sandbox (host-side) if connectivity is required.
- Wait for/adopt the scoped-egress mechanism (parent-owned CONNECT proxy with an allow-list) once available, instead of raw egress.
- Move the network-dependent portion of the work into a non-sandboxed step that returns data to the sandboxed tool.
- Audit tool profiles for network: true left over from older versions and remove the flag.
Example fix
// before
await runTool(fetcher, { sandboxProfile: { network: true } });
// after: fetch outside the sandbox, pass results in
const data = await hostFetch('https://api.example.com');
await runTool(processor, { sandboxProfile: {}, params: { data } }); Defensive patterns
Strategy: validation
Validate before calling
if (profile?.network === true) {
throw new Error('network: true is refused by the sandbox; fetch outside and pass results in');
} Type guard
const sandboxProfileIsSafe = (profile) => profile?.network !== true;
Try / catch
try {
await runTool(tool, { sandboxProfile: profile });
} catch (e) {
if (e.message === 'cave_sandbox_network_egress_unbounded') {
const data = await hostFetch(tool.needsUrl);
await runTool(tool, { sandboxProfile: {}, params: { data } });
} else throw e;
} Prevention
- Never set network: true in sandbox profiles; the runtime refuses it.
- Perform outbound HTTP host-side and inject results as params.
- Grep profiles for network: true when upgrading from older versions.
When it happens
Trigger: Executing a sandboxed tool with RunOptions.sandboxProfile set to { network: true } (runtime.ts:4745), or any code path that passes allowNetwork: profile?.network === true into the sandbox execution with network enabled.
Common situations: Migrating tools from an older caveman-agent version where network: true was permitted; copying sandbox profiles from other frameworks that allow network flags; a tool that legitimately needs an API call being run with raw egress enabled instead of a scoped proxy or host-side fetch.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- cave_sandbox_child_process_containment_unavailable
- cave_sandbox_os_network_isolation_unavailable
- cave_host_sandbox_nested_under_required
- cave_live_eval_sandbox_profile_escapes_root
- cave_sandbox_conformance_failed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/73065b7e849bcda4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/runtime.ts:4745
params: unknown,
timeoutMs: number,
allowSideEffects: boolean,
profile: RunOptions["sandboxProfile"],
executionContext: InternalExecutionContext,
toolDefinitionSha256: string,
signal?: AbortSignal,
): Promise<unknown> {
if (profile?.childProcess === true) {
throw new Error("cave_sandbox_child_process_containment_unavailable");
}
// `network: true` used to skip the OS network namespace entirely, granting the
// tool UNRESTRICTED egress while credentials sit in its env — an exfiltration
// hole, not a feature. There is no scoped-egress mechanism
// yet (a parent-owned CONNECT proxy bound to an allow-list is the tracked
// follow-up), so unbounded egress fails closed rather than being granted. Every
// sandboxed tool now runs under the OS boundary below.
if (profile?.network === true) {
throw new Error("cave_sandbox_network_egress_unbounded");
}
const requestedCredentialEnv = profile?.credentialEnv ?? [];
const childEnv = buildSandboxToolEnv(requestedCredentialEnv);
// Validate every collapsed grant before allocating per-call state. Refused
// roots must fail without leaving a caveman-agent-tool-* workspace behind.
const sourceReadFlags = sandboxSourceReadFlags(sourceFiles, stagingRoot);
const workspace = await realpath(await mkdtemp(`${tmpdir()}/caveman-agent-tool-`));
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const worker = fileURLToPath(new URL("./tool-worker.js", import.meta.url));
const timeout = AbortSignal.timeout(timeoutMs);
const combined = signal ? AbortSignal.any([signal, timeout]) : timeout;
const args = [
"--permission",
// One declared source file, framework runtime, dependencies, and ephemeral
// workspace only. Never grant tool code a project-root read capability:
// repositories commonly contain .env files, credentials, and local traces.
...sourceReadFlags,
`--allow-fs-read=${packageRoot}`,View on GitHub (pinned to 3ee70a1026)