JuliusBrussee/caveman · error
cave_sandbox_network_egress_unbounded
cave_sandbox_network_egress_unbounded
Error message
cave_sandbox_network_egress_unbounded
What it means
A sandbox profile requesting network: true is refused. This flag historically skipped the OS network namespace entirely, granting the tool unrestricted egress while credentials sat in its environment - an exfiltration hole. No scoped-egress mechanism exists yet (a parent-owned CONNECT proxy bound to an allow-list is the tracked follow-up), so unbounded egress fails closed and every sandboxed tool runs under the OS network boundary instead.
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 766dce6b13)
Solutions
- Drop network: true - sandboxed tools run with no network by design
- Move network-dependent work to an explicitly approved host-mode tool outside the required sandbox
- Fetch data before the sandboxed call and pass it in as staged input files
- Watch for the scoped-egress CONNECT-proxy feature instead of re-enabling the flag
Example fix
// before
tool({ sandboxProfile: { network: true }, run: async (p) => fetch(url) });
// after: fetch outside, stage the payload, compute inside
tool({ sandboxProfile: {}, run: async (p) => analyze(p.stagedData) }); Defensive patterns
Strategy: type-guard
Validate before calling
// Remove the legacy network grant before running the tool
function stripNetworkGrant(profile) {
const { network, ...rest } = profile ?? {};
if (network) logger.warn('network:true is refused; sandboxed tools run without network');
return rest;
} Type guard
function isSupportedSandboxProfile(profile: unknown): boolean {
if (profile === undefined || profile === null) return true;
if (typeof profile !== 'object') return false;
const p = profile as Record<string, unknown>;
return p.network !== true; // network:true always throws cave_sandbox_network_egress_unbounded
} Try / catch
try {
return await runSandboxedTool(params);
} catch (error) {
if (error instanceof Error && error.message === 'cave_sandbox_network_egress_unavailable' ||
error.message === 'cave_sandbox_network_egress_unbounded') {
return fetchOutsideThenRunSandboxed(params); // fetch first, stage payload, compute inside
}
throw error;
} Prevention
- Treat sandboxed tools as network-less by design; drop network: true from all profiles
- Fetch required data before the sandboxed call and pass it in as staged input
- Audit configs migrated from older versions for the removed network grant
- Track the scoped-egress CONNECT-proxy feature rather than re-enabling the flag
When it happens
Trigger: sandboxProfile: { network: true } on any tool run through the sandbox executor; configs migrated from an older version where the flag granted raw network access; tools that call HTTP APIs from inside the sandbox.
Common situations: Upgrading from a version that honored network: true; porting API-calling tools into the sandbox; assuming an allow-list exists like in firewalled container sandboxes.
Related errors
- cave_live_eval_sandbox_profile_escapes_root
- cave_host_sandbox_nested_under_required
- cave_sandbox_credential_env_not_allowlisted
- cave_sandbox_child_process_containment_unavailable
- cave_internal_run_option
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/73065b7e849bcda4.
Report an issue: GitHub.