Network policy

Control which hosts a sandbox may reach — set an allow/deny policy at creation, or change it at runtime with sb.egress.*.

By default a sandbox has ordinary, unrestricted outbound network access. Pass a networkPolicy to client.sandbox() and an egress sidecar is attached to the container: every DNS query and (in dns+nft mode) every raw-IP connection is checked against your rules before it leaves.

const sb = await client.sandbox({
  image: "ubuntu:22.04",
  resources: { cpu: "500m", memory: "256Mi" },
  networkPolicy: {
    defaultAction: "deny",
    egress: [
      { action: "allow", target: "api.github.com" },
      { action: "allow", target: "*.npmjs.org" },
    ],
  },
});

Server requirement

The egress sidecar needs the OpenSandbox server to have egress.image configured. alineo init sets this up by default (opensandbox/egress:v1.1.7, mode = "dns+nft"). On an older config or a uvx opensandbox-server host, add an [egress] section to ~/.config/alineo/server.toml (or ~/.sandbox.toml) and restart the server. Omit networkPolicy entirely and none of this applies — no sidecar is attached.

NetworkPolicy

FieldTypeDescription
defaultAction"allow" | "deny"What to do when no rule matches. Defaults to "deny" server-side if omitted.
egressNetworkRule[]Ordered allow/deny rules.

Each NetworkRule is { action: "allow" | "deny"; target: string }.

Rule targets

target is one of:

  • An FQDN"api.github.com". Matched against the DNS query name.
  • A wildcard domain"*.openai.com". The *. prefix is the sidecar's only wildcard form (it matches one or more leading labels).
  • A bare IPv4/IPv6 address"10.0.0.5", "2606:4700::1111".
  • A CIDR block"10.0.0.0/8", "fd00::/8".

IP and CIDR rules are enforced at the nftables layer, so they only take effect when the server runs egress.mode = "dns+nft", and they gate raw-IP egress only. A CIDR rule does not authorize resolving a domain that happens to point into that range — for reach-by-name you still need a domain rule.

A malformed target (a URL, whitespace, a space-containing string) throws SandboxClientError locally before any server round-trip. The same check is exported as isValidEgressTarget from @alineo-labs/opensandbox.

Changing the policy at runtime

sb.egress.* adjusts a running sandbox's policy through its sidecar. The change applies immediately — no restart, no new sandbox.

// Allow a host on a sandbox that's already running:
await sb.egress.patch([{ action: "allow", target: "example.com" }]);

// Revoke it — the host is blocked again:
await sb.egress.delete(["example.com"]);

// Read back the live policy from the sidecar:
const { policy } = await sb.egress.get();
MethodBehavior
sb.egress.patch(rules)Merge rules in. An incoming rule replaces any existing rule with the same target; every other rule and defaultAction are untouched.
sb.egress.delete(targets)Remove rules by target. Unknown targets are silently ignored.
sb.egress.get()Returns the sidecar's status envelope: { status?, mode?, enforcementMode?, policy? }.

These only work on a sandbox created with a networkPolicy — without one there is no sidecar and the calls error.

Persistence across resume and fork

Runtime sb.egress.* changes are sidecar-local: they don't survive the sidecar restarting and OpenSandbox's snapshot/checkpoint doesn't capture them. Every patch / delete is written to the ledger (EgressRuleAdded / EgressRuleRemoved), and Sandbox.resume() folds whatever is still live back into the resumed sandbox's boot policy — so a still-wanted allowance is re-applied automatically.

sb.fork() does not carry runtime egress rules. A fork is a fresh branch and starts with a wide-open defaultAction: "allow" policy.

Relationship to credential injection

networkPolicy (which hosts are reachable) and credentialProxy (transparent credential injection) are separate concerns that ride the same sidecar. credentialProxy: true requires networkPolicy to also be set. See Credentials.

For an agent that should hold a specific host behind a human decision before it's reachable, see Permission gate.