Network Access Control

Updated at:

A sandbox's network configuration controls two independent behaviors: public access authentication (inbound) and egress access control (outbound / egress).

Feature introduction

  • Public access authentication (inbound): whether the public URL that the sandbox exposes requires a token to be accessed.

  • Egress access control (outbound / egress): which destinations are allowed or denied when the sandbox actively accesses external networks.

Network configuration is passed in when creating the sandbox through the top-level allowInternetAccess and network objects.

Field naming is not fully consistent across the three interfaces, so distinguish them carefully:

  • Python SDK: all snake_case, e.g. allow_internet_access, allow_public_traffic, allow_out, deny_out.

  • JavaScript / TypeScript SDK: all lower camelCase, e.g. allowInternetAccess, allowPublicTraffic, allowOut, denyOut.

  • HTTP API: mixed style—in POST /sandboxes the top-level egress master switch is allow_internet_access (snake_case, with allowInternetAccess also accepted), while fields inside the network object are all lower camelCase (network.allowPublicTraffic, network.allowOut, network.denyOut, network.maskRequestHost); the egress master switch in PUT /sandboxes/{id}/network is allowInternetAccess (camelCase).

The examples below are provided in both Node.js and Python; choose the style that matches the SDK you use.

Prerequisites

The examples below are provided in both Node.js and Python, and read connection information from environment variables. Before running, install the SDK and configure the following environment variables (fill in E2B_API_URL / E2B_DOMAIN for your target region, where <region> is e.g. cn-shenzhen):

# Connection info (replace with your actual values)
export E2B_API_KEY="<your-api-key>"
export E2B_API_URL="https://api.<region>.e2b.fc.aliyuncs.com"
export E2B_DOMAIN="<region>.e2b.fc.aliyuncs.com"

Node.js (the example is written in TypeScript; save it as demo.ts):

npm install e2b@^2.31.0
npx tsx demo.ts

Python (save the example as demo.py, requires Python ≥ 3.10):

python3 --version        # confirm ≥ 3.10
pip install e2b==2.31.0 httpx   # httpx is only used by the public auth example
python demo.py
Note

Runtime updates (update_network / updateNetwork) require the Python SDK ≥ 2.11, and SDK ≥ 2.11 requires Python ≥ 3.10. On Python 3.9, installing e2b without a version pin installs only the last compatible version (2.10.x), and calling update_network will raise AttributeError: 'Sandbox' object has no attribute 'update_network'. In that case, upgrade to Python ≥ 3.10 and reinstall, or use the REST endpoint PUT /sandboxes/{id}/network.

Parameter Overview

Parameter (SDK / API) Purpose Supported Modifiable after creation
allowInternetAccess / allow_internet_access Master egress switch; false is equivalent to denyOut: ["0.0.0.0/0"] Yes Yes (updateNetwork)
network.allowPublicTraffic Whether the public URL requires token authentication; defaults to true Yes No (can only be set at creation)
network.allowOut Egress allow list; supports IP / CIDR / domain Yes Yes (updateNetwork)
network.denyOut Egress deny list; supports IP / CIDR / domain Yes Yes (updateNetwork)
network.egressProxy SOCKS5 egress proxy Not yet supported —
network.maskRequestHost Rewrite the Host header of the sandbox's requests Yes No (can only be set at creation)
network.rules Transform egress requests by domain (e.g. inject HTTP headers) Yes Yes (updateNetwork)

egressProxy is not yet supported; passing it has no effect. For more information about network.rules, see Outbound request header transformation.

Public Access Authentication

Services started inside the sandbox can be exposed externally through a public URL. By default, anyone who knows the URL can access it; for sensitive workloads, you can use allowPublicTraffic to force callers to authenticate first.

  • allowPublicTraffic: true (default): the public URL can be accessed without authentication.

  • allowPublicTraffic: false: every request to the public URL must carry the e2b-traffic-access-token header, whose value is the trafficAccessToken returned when the sandbox was created; requests without the token or with an invalid token return 403.

Note

When setting allowPublicTraffic: false, you must also set secure: true. Otherwise even the SDK's own access to the sandbox control plane (envd) will be blocked by the public token gate, and operations such as commands / files will return 403. secure: true enables control-plane authentication (envdAccessToken, header X-Access-Token, carried automatically by the SDK), which is a separate token from the public inbound trafficAccessToken.

Node.js

import { Sandbox } from "e2b";

const sandbox = await Sandbox.create({
  apiKey: process.env.E2B_API_KEY,
  apiUrl: process.env.E2B_API_URL,
  domain: process.env.E2B_DOMAIN,
  secure: true, // required when allowPublicTraffic is false, otherwise the SDK cannot reach the control plane
  network: {
    allowPublicTraffic: false,
  },
});
console.log(`[create] sandbox created: ${sandbox.sandboxId}`);

try {
  // Grab the token right after creation and save it; getInfo / list will not return it later, but you can retrieve it again via connect (see below)
  // The token is sensitive; do not print it directly. Here we only note it was obtained; store the actual value securely.
  console.log(`[token] trafficAccessToken obtained (sensitive; do not log)`);

  // After starting a service inside the sandbox, use getHost to get the external hostname
  await sandbox.commands.run("python3 -m http.server 8080", { background: true });
  const url = `https://${sandbox.getHost(8080)}`;
  console.log(`[serve] service started, public address: ${url}`);

  // Without token: 403
  const r1 = await fetch(url);
  console.log(`[probe] access without token -> ${r1.status}`); // 403

  // With token: 200
  const r2 = await fetch(url, {
    headers: { "e2b-traffic-access-token": sandbox.trafficAccessToken },
  });
  console.log(`[probe] access with token -> ${r2.status}`); // 200
} finally {
  // Destroy the sandbox after finishing to avoid leftover usage and billing
  await sandbox.kill();
  console.log("[cleanup] sandbox destroyed");
}

Python

import os

import httpx

from e2b import Sandbox

sandbox = Sandbox.create(
    api_key=os.environ["E2B_API_KEY"],
    api_url=os.environ["E2B_API_URL"],
    domain=os.environ["E2B_DOMAIN"],
    secure=True,  # required when allow_public_traffic is False, otherwise the SDK cannot reach the control plane
    network={"allow_public_traffic": False},
)
print(f"[create] sandbox created: {sandbox.sandbox_id}")

try:
    # Grab the token right after creation and save it; getInfo / list will not return it later, but you can retrieve it again via connect (see below)
    # The token is sensitive; do not print it directly. Here we only note it was obtained; store the actual value securely.
    print("[token] traffic_access_token obtained (sensitive; do not log)")

    # After starting a service inside the sandbox, use get_host to get the external hostname
    sandbox.commands.run("python3 -m http.server 8080", background=True)
    url = f"https://{sandbox.get_host(8080)}"
    print(f"[serve] service started, public address: {url}")

    # Without token: 403
    r1 = httpx.get(url)
    print(f"[probe] access without token -> {r1.status_code}")  # 403

    # With token: 200
    r2 = httpx.get(url, headers={"e2b-traffic-access-token": sandbox.traffic_access_token})
    print(f"[probe] access with token -> {r2.status_code}")  # 200
finally:
    # Destroy the sandbox after finishing to avoid leftover usage and billing
    sandbox.kill()
    print("[cleanup] sandbox destroyed")

When the Token Is Returned

The sandbox returns trafficAccessToken on create, connect, and resume:

  • create (POST /sandboxes): the token comes from the FC session creation response.

  • connect (POST /sandboxes/{id}/connect): both the 200 (already running) and 201 (resumed) responses carry the token.

    • Target paused → triggers FC Resume, and the token comes from the resume response;

    • Target already running / resuming → does not trigger FC Resume; the gateway reads it back from the FC SessionRecords table by session ID and fills it in, keeping the connect contract consistent.

  • resume (the deprecated POST /sandboxes/{id}/resume): also returns the token.

Points to note:

  • sandbox.getInfo() (GET /sandboxes/{id}) and Sandbox.list() go through the query path, and their response bodies do not include trafficAccessToken—the SandboxDetail structure has no such field.

  • In rare degraded cases (the gateway has no token reader configured, the sandbox has no session ID, or it is a public sandbox with no token at all), connect still returns normally, just without a token (connectivity is unaffected).

Recommendation: persist trafficAccessToken together with sandboxId for easy reuse. getInfo / list do not return the token; even if it is lost, you can usually retrieve it again through connect (except in the rare degraded scenarios noted above, where connect may not include it).

Difference from envdAccessToken

There are two easily confused tokens in the sandbox, with different purposes, headers, and issuance timing:

Item trafficAccessToken envdAccessToken
Purpose Access the sandbox's public URL (inbound traffic authentication) Authenticate the SDK's access to the sandbox control plane (envd)
Switch Takes effect when allowPublicTraffic: false Controlled by secure (secure access)
Header e2b-traffic-access-token X-Access-Token
Issuance timing Returned on create / connect / resume (not on getInfo or list) Returned when creating and fetching sandbox details

allowPublicTraffic governs inbound access from "external → services inside the sandbox"; secure / envdAccessToken govern communication from "SDK → sandbox control plane". These are capabilities at different layers.

Egress Access Control

Egress access control constrains the range of destinations the sandbox can actively access on external networks, determined jointly by allowInternetAccess, allowOut, and denyOut.

Master Switch: allowInternetAccess

  • true (default): allows access to the public internet.

  • false: equivalent to appending "0.0.0.0/0" to denyOut, establishing a "deny all by default" baseline, not an unconditional hard switch. Because allowOut takes priority over denyOut (see the Priority and Matching Rules section below), it forbids all egress only when used alone (without allowOut); once combined with allowOut, targets matched by allowOut are still allowed, giving you "deny all + allowlist".

Node.js

import { Sandbox } from "e2b";

// Fully isolate from external networks
const sandbox = await Sandbox.create({
  apiKey: process.env.E2B_API_KEY,
  apiUrl: process.env.E2B_API_URL,
  domain: process.env.E2B_DOMAIN,
  allowInternetAccess: false,
});
console.log(`[create] sandbox created (network isolated): ${sandbox.sandboxId}`);

try {
  // Egress is forbidden; rely on the application-layer result; use || echo as a fallback to avoid a non-zero exit code raising an exception
  const probe = await sandbox.commands.run(
    'curl -sS -o /dev/null -w "%{http_code}" -m 5 https://api.github.com || echo blocked'
  );
  console.log(`[probe] access api.github.com -> ${probe.stdout.trim()}`); // blocked
} finally {
  // Destroy the sandbox after finishing to avoid leftover usage and billing
  await sandbox.kill();
  console.log("[cleanup] sandbox destroyed");
}

Python

import os

from e2b import Sandbox

# Fully isolate from external networks
sandbox = Sandbox.create(
    api_key=os.environ["E2B_API_KEY"],
    api_url=os.environ["E2B_API_URL"],
    domain=os.environ["E2B_DOMAIN"],
    allow_internet_access=False,
)
print(f"[create] sandbox created (network isolated): {sandbox.sandbox_id}")

try:
    # Egress is forbidden; rely on the application-layer result; use || echo as a fallback to avoid a non-zero exit code raising an exception
    probe = sandbox.commands.run(
        'curl -sS -o /dev/null -w "%{http_code}" -m 5 https://api.github.com || echo blocked'
    )
    print(f"[probe] access api.github.com -> {probe.stdout.strip()}")  # blocked
finally:
    # Destroy the sandbox after finishing to avoid leftover usage and billing
    sandbox.kill()
    print("[cleanup] sandbox destroyed")

Allow List allowOut and Deny List denyOut

Each entry in allowOut can be a CIDR / bare IP / domain ("example.com", "*.example.com"); denyOut also supports IP / CIDR / domain.

Node.js

import { Sandbox } from "e2b";

// Allowlist mode: deny everything, allow only the specified targets
const sandbox = await Sandbox.create({
  apiKey: process.env.E2B_API_KEY,
  apiUrl: process.env.E2B_API_URL,
  domain: process.env.E2B_DOMAIN,
  network: {
    denyOut: ["0.0.0.0/0"],
    allowOut: ["8.8.8.8", "8.8.8.0/24", "api.example.com", "*.github.com"],
  },
});
console.log(`[create] sandbox created (allowlisted egress): ${sandbox.sandboxId}`);

try {
  // Matches *.github.com, allowed
  const allowed = await sandbox.commands.run(
    'curl -sS -o /dev/null -w "%{http_code}" -m 5 https://api.github.com || echo blocked'
  );
  console.log(`[probe] api.github.com (in allowlist) -> ${allowed.stdout.trim()}`);

  // Not in the allowlist, denied
  const denied = await sandbox.commands.run(
    'curl -sS -o /dev/null -w "%{http_code}" -m 5 https://pypi.org || echo blocked'
  );
  console.log(`[probe] pypi.org (not in allowlist) -> ${denied.stdout.trim()}`); // blocked
} finally {
  // Destroy the sandbox after finishing to avoid leftover usage and billing
  await sandbox.kill();
  console.log("[cleanup] sandbox destroyed");
}

Python

import os

from e2b import Sandbox

sandbox = Sandbox.create(
    api_key=os.environ["E2B_API_KEY"],
    api_url=os.environ["E2B_API_URL"],
    domain=os.environ["E2B_DOMAIN"],
    network={
        "deny_out": ["0.0.0.0/0"],
        "allow_out": ["8.8.8.8", "8.8.8.0/24", "api.example.com", "*.github.com"],
    },
)
print(f"[create] sandbox created (allowlisted egress): {sandbox.sandbox_id}")

try:
    # Matches *.github.com, allowed
    allowed = sandbox.commands.run(
        'curl -sS -o /dev/null -w "%{http_code}" -m 5 https://api.github.com || echo blocked'
    )
    print(f"[probe] api.github.com (in allowlist) -> {allowed.stdout.strip()}")

    # Not in the allowlist, denied
    denied = sandbox.commands.run(
        'curl -sS -o /dev/null -w "%{http_code}" -m 5 https://pypi.org || echo blocked'
    )
    print(f"[probe] pypi.org (not in allowlist) -> {denied.stdout.strip()}")  # blocked
finally:
    # Destroy the sandbox after finishing to avoid leftover usage and billing
    sandbox.kill()
    print("[cleanup] sandbox destroyed")

In the SDK, you can also express "all traffic" using a selector callback, which is the officially recommended style (the ALL_TRAFFIC constant is still kept for backward compatibility):

Node.js

import { Sandbox } from "e2b";

const sandbox = await Sandbox.create({
  apiKey: process.env.E2B_API_KEY,
  apiUrl: process.env.E2B_API_URL,
  domain: process.env.E2B_DOMAIN,
  network: {
    denyOut: ({ allTraffic }) => [allTraffic], // allTraffic === "0.0.0.0/0"
    allowOut: ["1.1.1.1", "8.8.8.0/24"],
  },
});
console.log(`[create] sandbox created: ${sandbox.sandboxId}`);

try {
  // ... business logic ...
} finally {
  // Destroy the sandbox after finishing to avoid leftover usage and billing
  await sandbox.kill();
  console.log("[cleanup] sandbox destroyed");
}

Python

import os

from e2b import Sandbox

sandbox = Sandbox.create(
    api_key=os.environ["E2B_API_KEY"],
    api_url=os.environ["E2B_API_URL"],
    domain=os.environ["E2B_DOMAIN"],
    network={
        "deny_out": lambda ctx: [ctx.all_traffic],  # ctx.all_traffic == "0.0.0.0/0"
        "allow_out": ["1.1.1.1", "8.8.8.0/24"],
    },
)
print(f"[create] sandbox created: {sandbox.sandbox_id}")

try:
    # ... business logic ...
    pass
finally:
    # Destroy the sandbox after finishing to avoid leftover usage and billing
    sandbox.kill()
    print("[cleanup] sandbox destroyed")

Scope and Limitations of Domain Filtering

When filtering by domain, there are several behaviors you must keep in mind:

  • You must explicitly deny the rest of the traffic: a domain allowlist must be combined with denyOut: ["0.0.0.0/0"] (or allowInternetAccess: false), otherwise it has no effect.

  • Only covers HTTP:80 and TLS:443: domain matching relies on the Host header on port 80 and the TLS SNI on port 443. Other ports can only be filtered by IP / CIDR; UDP protocols such as QUIC / HTTP/3 do not support domain filtering.

  • DNS is automatically allowed: as long as domains are used, the system automatically allows the default DNS server 8.8.8.8 to keep domain resolution working.

  • Wildcards *.example.com are supported to match all subdomains.

Priority and Matching Rules

When multiple parameters are present at the same time, whether a given egress request is allowed is decided by the following rules:

  1. Allow takes priority over deny: allowOut always takes priority over denyOut. If a target matches both the allow and deny lists, it is allowed.

  2. allowInternetAccess: false merely appends ["0.0.0.0/0"] to denyOut: it establishes a "deny all by default" baseline, but is not a hard kill switch. Because allow beats deny, targets matched by allowOut still punch through this baseline and are allowed—so allowInternetAccess: false + allowOut means "deny all + allowlist", and it truly forbids all egress only when no allowOut is set.

  3. A domain allowlist requires an explicit denyOut for all traffic: see the previous section.

Effects of common combinations:

Configuration Effect
No egress parameters configured Allows access to the entire public internet (default)
allowInternetAccess: false (without allowOut) Forbids all egress (equivalent to denyOut: ["0.0.0.0/0"])
allowInternetAccess: false + allowOut: ["8.8.8.8"] Deny all by default, but allow 8.8.8.8 matched by allowOut (allow wins; same as denyOut: ["0.0.0.0/0"] + allowOut)
denyOut: ["10.0.0.0/8"] Denies only that CIDR block, allows the rest (denylist)
denyOut: ["0.0.0.0/0"] + allowOut: ["8.8.8.8"] Allows only 8.8.8.8, denies everything else (allowlist)
allowOut: ["example.com"] (without denyOut) The domain has no effect and the rest of the traffic is still allowed—you must add denyOut: ["0.0.0.0/0"]

Behavior of Denied Connections

Because of how the egress firewall is implemented, a denied TCP connection may appear to "succeed" from inside the sandbox: the firewall must accept the connection first before it can decide whether the target is allowed, so the socket may show as established, while no packets actually reach the target.

To determine whether traffic actually reaches the target, check the application-layer response (HTTP status code, TLS handshake, expected protocol bytes, etc.), rather than relying on whether the TCP connection is established.

Updating Egress Configuration at Runtime

After creation, you can use updateNetwork (TypeScript) / update_network (Python) to adjust egress rules without rebuilding the sandbox:

Node.js

import { Sandbox } from "e2b";

const sandbox = await Sandbox.create({
  apiKey: process.env.E2B_API_KEY,
  apiUrl: process.env.E2B_API_URL,
  domain: process.env.E2B_DOMAIN,
});
console.log(`[create] sandbox created: ${sandbox.sandboxId}`);

try {
  // Tighten: block a single IP
  await sandbox.updateNetwork({ denyOut: ["8.8.8.8"] });
  console.log("[update] blocked 8.8.8.8");

  // Replace with a pure allowlist
  await sandbox.updateNetwork({
    denyOut: ({ allTraffic }) => [allTraffic],
    allowOut: ["api.example.com"],
  });
  console.log("[update] switched to allowlist: only api.example.com allowed");

  // Directly toggle the master egress switch
  await sandbox.updateNetwork({ allowInternetAccess: false });
  console.log("[update] master egress switch turned off");
} finally {
  // Destroy the sandbox after finishing to avoid leftover usage and billing
  await sandbox.kill();
  console.log("[cleanup] sandbox destroyed");
}

Python

import os

from e2b import Sandbox

sandbox = Sandbox.create(
    api_key=os.environ["E2B_API_KEY"],
    api_url=os.environ["E2B_API_URL"],
    domain=os.environ["E2B_DOMAIN"],
)
print(f"[create] sandbox created: {sandbox.sandbox_id}")

try:
    # Tighten: block a single IP
    sandbox.update_network({"deny_out": ["8.8.8.8"]})
    print("[update] blocked 8.8.8.8")

    # Replace with a pure allowlist
    sandbox.update_network({
        "deny_out": lambda ctx: [ctx.all_traffic],
        "allow_out": ["api.example.com"],
    })
    print("[update] switched to allowlist: only api.example.com allowed")

    # Directly toggle the master egress switch
    sandbox.update_network({"allow_internet_access": False})
    print("[update] master egress switch turned off")
finally:
    # Destroy the sandbox after finishing to avoid leftover usage and billing
    sandbox.kill()
    print("[cleanup] sandbox destroyed")

Points to note:

  • updateNetwork is a full replacement, not a merge with existing rules; passing an empty object updateNetwork({}) clears all allow / deny rules set at creation.

  • The egress-related allowInternetAccess / allowOut / denyOut / network.rules can be updated. allowPublicTraffic and maskRequestHost are creation-time-locked parameters and cannot be modified after creation. For more information about how to configure network.rules, see Outbound request header transformation.

Limits

  • egressProxy is not yet supported; passing it has no effect.

  • trafficAccessToken is returned on create / connect / resume; it is not returned on getInfo / list. Persist it for simpler reuse.

  • allowPublicTraffic can only be set at creation and cannot be modified afterward.

  • denyOut supports IP / CIDR / domain.

  • Domain filtering only covers HTTP:80 and TLS:443; other ports are filtered by IP / CIDR, and UDP (QUIC / HTTP/3) does not support domain filtering.

  • Denied connections may appear as "connected successfully" inside the sandbox; rely on the application-layer response.

  • Egress control (allowOut / denyOut) and public inbound authentication (allowPublicTraffic) are independent of each other.

Recommendations

  • For untrusted code or third-party tasks, tighten egress by default: denyOut: ["0.0.0.0/0"] plus an allowOut allowlist as needed.

  • When exposing a service externally with sensitive data, set allowPublicTraffic: false (and set secure: true), and save the trafficAccessToken at creation time for your application to pass along securely.

  • When using a domain allowlist, always add denyOut: ["0.0.0.0/0"], and confirm the target uses port 80/443.

  • Do not write trafficAccessToken or envdAccessToken into logs or command output.