Network Access

Control which hosts a sandbox can reach over the network. Egress rules are defined in SandboxConfig.egress and evaluated at request time, the rules are an ordered list, and the first matching rule wins.

Default behavior

egress is optional. When you omit it entirely, the client applies an allow-all policy, the sandbox can reach any host. This is convenient for getting started, but for anything running untrusted code you should lock it down.

The moment you provide any rules, the model flips to allow-list: a request is permitted only if a rule matches it, and anything that matches no rule is denied. There is no implicit fallthrough.

index.ts
import { getOrCreateSandbox } from "@hiver.sh/client";

// No egress specified -> allow-all (open)
const open = await getOrCreateSandbox("open-sandbox", { image: "python" });

// Any rules specified -> only matching requests are allowed, everything else denied
const locked = await getOrCreateSandbox("locked-sandbox", {
  image: "python",
  egress: [{ access: "allow", host: "api.github.com" }],
});

Allowing specific hosts

Add an entry to egress for each destination you want to permit. Narrow rules further by port, HTTP method, or path, and end with an explicit deny catch-all to make the intent obvious:

index.ts
egress: [
  { access: "allow", host: "api.anthropic.com" },
  { access: "allow", host: "pypi.org", ports: [443] },
  { access: "allow", host: "api.github.com", methods: ["GET", "POST"] },
  { access: "allow", host: "api.example.com", paths: ["/v1/*"] },
  { access: "deny", host: "*" }, // explicit catch-all (optional; unmatched is denied anyway)
]
FieldTypeDescription
access"allow" | "deny"Outcome for a matching request.
hoststringHostname to match. Exact (api.github.com) or wildcard suffix (*.pypi.org).
portsnumber[]Restrict to specific destination ports. Omit to match any port.
methodsstring[]Restrict to specific HTTP methods. Omit to match any method.
pathsstring[]Restrict to specific URL paths (git-style globs). Omit to match any path.

Path globs

Path matching is segment-by-segment on /. * matches within a single segment (/users/* matches /users/42 but not /users/42/posts); ** matches across segments (/repos/** matches /repos, /repos/foo, and /repos/foo/bar).

Disabling TLS interception (mitm)

By default (SandboxConfig.mitm: true), outbound TLS connections are intercepted so egress rules can inspect and enforce methods/paths/override/override_script.

Set mitm: false to turn interception off entirely. Egress rules still match on host (read from the TLS SNI, before any bytes are decrypted) and ports; methods, paths, override, and override_script are no longer enforced, and the encrypted byte stream is forwarded end-to-end unmodified. Use this for upstreams whose TLS fingerprint or certificate pinning rejects an intercepted connection. Plain HTTP egress is unaffected either way, and egress.request/egress.response/egress.chunk events still fire, but without method/path/header/body detail for TLS traffic.

index.ts
const sandbox = await getOrCreateSandbox("pinned-upstream", {
  mitm: false,
  egress: [{ access: "allow", host: "pinned.example.com" }],
});

Installing packages

The TypeScript and Python clients ship helpers that generate the exact egress rules a package installer needs, and nothing more, so pip/npm install for any package you didn't list is blocked.

index.ts
import { allowedPythonPackages, allowedNpmPackages } from "@hiver.sh/client";

const sandbox = await getOrCreateSandbox("my-sandbox", {
  egress: [
    ...allowedPythonPackages("numpy", "pandas"),
    ...allowedNpmPackages("typescript"),
  ],
});

Injecting credentials with override

Use override to attach headers (or query params) to every outbound request that matches a rule. The agent inside the sandbox cannot read the injected values back, they're applied transparently by the proxy after the request leaves the agent. This is how you give an agent access to a third-party API without ever putting the key in its context or environment.

index.ts
const sandbox = await getOrCreateSandbox("my-sandbox", {
  egress: [{
    access: "allow",
    host: "api.finnhub.io",
    override: { headers: { "X-Finnhub-Token": process.env.FINNHUB_API_KEY! } },
  }],
});

See Overrides for headers, query params, body rewriting, and upstream re-routing.

Updating egress at runtime

applyConfig replaces the egress rules on a running sandbox. Read the current config first to preserve other settings. A useful pattern is to start open, do trusted setup, then lock down before handing control to the agent:

index.ts
await sandbox.applyConfig({
  ...(await sandbox.getConfig()),
  egress: [{ access: "allow", host: "api.anthropic.com" }],
});

The new rules take effect immediately. In-flight requests that were already allowed are not interrupted.

Just-in-time approval with a deny grace period

By default a request that matches no allow rule is denied immediately. Set egress_deny_wait to a number of seconds to instead hold a would-be-denied request for that long before erroring — a window in which you can widen the policy and let the request through, rather than have the agent see a failure and retry.

While a request is paused, every policy update is evaluated against it immediately: the moment a rule you add allows the destination, the request proceeds normally, as if it had been allowed all along. If the window elapses with the request still unmatched, the usual deny error is returned. 0 (the default) disables the wait.

Crucially, the egress.request deny event fires immediately — the moment the request is held, not when the wait ends — so you actually have the full window to react. This turns a denial into an approval hook: watch the event stream for denied requests, decide whether to allow the host, and call applyConfig — all before the paused request times out.

index.ts
// Hold denied requests for up to 30s so we can approve new hosts on the fly.
const sandbox = await getOrCreateSandbox("approval-gated", {
  image: "python",
  egress_deny_wait: 30,
  egress: [{ access: "allow", host: "api.github.com" }],
});

// Elsewhere: when a request to a new host is blocked, allow it in time.
for await (const event of sandbox.getEventsStream()) {
  if (event.type === "egress.request" && event.access === "denied") {
    if (await approve(event.host)) {
      const cfg = await sandbox.getConfig();
      await sandbox.applyConfig({
        ...cfg,
        egress: [...(cfg.egress ?? []), { access: "allow", host: event.host }],
      });
    }
  }
}

Observing blocked requests

Every outbound request shows up on the event stream as an egress.request event with access set to "allowed" or "denied". Filter for denials to see exactly what your policy is blocking:

index.ts
for await (const event of sandbox.getEventsStream()) {
  if (event.type === "egress.request" && event.access === "denied") {
    console.log("blocked:", event.host, event.method, event.path);
  }
}

See Events for the full set of event types.


Next: Overrides