Network egress rules

Every sandbox has an egress policy: the outbound internet it may reach. Set it when you create the sandbox, change it while the sandbox runs, and read what the sandbox tried to reach. A sandbox-side proxy enforces the policy, backed by a host firewall that routes all sandbox traffic through it.

ModeThe sandbox can reach
openAny host. The default for a personal sandbox.
strictThe hosts in allowDomains, plus the model endpoint and providers the platform provisioned into the sandbox. Nothing else: no package registry, no source host, unless you list it.
blockedNo outbound internet.

A team sandbox created without a policy inherits the team's default policy.

Lock a build to the hosts it needs

This sandbox can install npm packages and read GitHub's API, and nothing else.

ts
import { Sandbox } from "@tangle-network/sandbox";

const client = new Sandbox({
  apiKey: process.env.TANGLE_API_KEY!,
  baseUrl: "https://sandbox.tangle.tools",
});

const box = await client.create({
  name: "locked-build",
  egressPolicy: {
    mode: "strict",
    allowDomains: ["registry.npmjs.org", "api.github.com"],
  },
  maxLifetimeSeconds: 3600,
});

try {
  await box.waitFor("running");
  await box.exec("git clone --depth 1 https://github.com/vercel/ms /workspace/ms");
  const build = await box.exec("cd /workspace/ms && npm ci && npm test");
  console.log(build.exitCode, build.stdout.slice(-400));
} finally {
  await box.delete();
}

The git clone fails here: github.com is not api.github.com. The next recipe shows how to find every host a build needs instead of guessing.

Discover the allowlist from denials

A refused request gets a bodyless 403, so code inside the sandbox cannot tell a policy refusal from a broken network. box.egress.denials() tells you which hosts were refused and how often. Start strict with nothing allowed, run the build once, and read the refusals:

ts
const box = await client.create({
  egressPolicy: { mode: "strict", allowDomains: [] },
});
await box.waitFor("running");
await box.exec(
  "git clone --depth 1 https://github.com/vercel/ms /workspace/ms && cd /workspace/ms && npm ci",
);

for (const denial of await box.egress.denials({ limit: 50 })) {
  console.log(`${denial.host}  refused ${denial.count}x by ${denial.rejectedBy}`);
}
// github.com  refused 1x by …
// registry.npmjs.org  refused 3x by …

Add the hosts you trust and apply them to the running sandbox:

ts
await box.egress.update({
  mode: "strict",
  allowDomains: ["github.com", "codeload.github.com", "registry.npmjs.org"],
});

update() restarts the sandbox's proxy to apply the policy, which interrupts outbound connections for a few seconds. The restart also starts a new activity window, so read denials() and activity() before you change the policy.

Install, then cut the network

Untrusted code should not run with network access. Install dependencies under a strict policy, then switch to blocked before the code runs:

ts
await box.exec("cd /workspace/app && npm ci");
await box.egress.update({ mode: "blocked" });
const result = await box.exec("cd /workspace/app && npm test");

Prove what a sandbox reached

denials() is best effort: an empty list can mean nothing was refused or that the proxy log could not be read. When you need evidence, for an audit or a benchmark control arm, use activity(). It lists allowed and refused hosts and says whether the record is complete.

ts
const activity = await box.egress.activity({ limit: 500 });
if (!activity.complete) {
  throw new Error(`incomplete record: ${activity.incompleteReasons.join(", ")}`);
}
for (const destination of activity.destinations) {
  console.log(destination.action, destination.host, destination.count);
}

An empty destinations list with complete: true is evidence the sandbox reached nothing. With complete: false it proves nothing.

Domain patterns

PatternMatches
api.github.comThat host only.
*.githubusercontent.comgithubusercontent.com and every subdomain at any depth.
**.githubusercontent.comSubdomains only, not githubusercontent.com itself.

includeImplicitDomains: true merges the platform's default developer list into a strict policy, including github.com, gist.github.com, and gitlab.com. That makes any public repository readable and a gist an allowed destination, so use it only for trusted development sandboxes.

Cloud agents behind a strict policy

A coding agent's model calls go to the endpoint provisioned into the sandbox, which strict mode allows. When the agent uses your own subscription or a coding plan endpoint, list that provider's host so the policy states it explicitly:

ts
egressPolicy: {
  mode: "strict",
  allowDomains: ["api.anthropic.com", "registry.npmjs.org", "github.com"],
}

See Start a cloud agent for the agent side.

From HTTP or the CLI

The CLI has no egress flag yet. Use the HTTP API, which merges the body into the current policy:

bash
curl -X PATCH "https://sandbox.tangle.tools/v1/sandboxes/$SANDBOX_ID/egress" \
  -H "Authorization: Bearer $TANGLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mode":"strict","allowDomains":["registry.npmjs.org"]}'
RoutePurpose
PATCH /v1/sandboxes/{id}/egressChange the policy on a running sandbox.
GET /v1/sandboxes/{id}/egress/denialsRefused hosts, newest first.
GET /v1/sandboxes/{id}/egress/activityAllowed and refused hosts, with completeness.
GET /v1/sandboxes/{id}/egress/usageThis month's egress bytes and charge.
PUT /v1/teams/{id}/egressTeam default policy (team owners and admins).

Egress pricing is published at /v1/pricing/egress, and box.egress.usage() returns this sandbox's bytes and charge for the month.