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

  1. Drop network: true - sandboxed tools run with no network by design
  2. Move network-dependent work to an explicitly approved host-mode tool outside the required sandbox
  3. Fetch data before the sandboxed call and pass it in as staged input files
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/73065b7e849bcda4. Report an issue: GitHub.