Network Access Control
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 /sandboxesthe top-level egress master switch isallow_internet_access(snake_case, withallowInternetAccessalso accepted), while fields inside thenetworkobject are all lower camelCase (network.allowPublicTraffic,network.allowOut,network.denyOut,network.maskRequestHost); the egress master switch inPUT /sandboxes/{id}/networkisallowInternetAccess(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
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) |
egressProxyis not yet supported; passing it has no effect. For more information aboutnetwork.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 thee2b-traffic-access-tokenheader, whose value is thetrafficAccessTokenreturned when the sandbox was created; requests without the token or with an invalid token return403.
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 the200(already running) and201(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 FCSessionRecordstable 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}) andSandbox.list()go through the query path, and their response bodies do not includetrafficAccessToken—theSandboxDetailstructure 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),
connectstill 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"todenyOut, establishing a "deny all by default" baseline, not an unconditional hard switch. BecauseallowOuttakes priority overdenyOut(see the Priority and Matching Rules section below), it forbids all egress only when used alone (withoutallowOut); once combined withallowOut, targets matched byallowOutare 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"](orallowInternetAccess: false), otherwise it has no effect. -
Only covers HTTP:80 and TLS:443: domain matching relies on the
Hostheader 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.8to keep domain resolution working. -
Wildcards
*.example.comare 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:
-
Allow takes priority over deny:
allowOutalways takes priority overdenyOut. If a target matches both the allow and deny lists, it is allowed. -
allowInternetAccess: falsemerely appends["0.0.0.0/0"]todenyOut: it establishes a "deny all by default" baseline, but is not a hard kill switch. Because allow beats deny, targets matched byallowOutstill punch through this baseline and are allowed—soallowInternetAccess: false+allowOutmeans "deny all + allowlist", and it truly forbids all egress only when noallowOutis set. -
A domain allowlist requires an explicit
denyOutfor 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:
-
updateNetworkis a full replacement, not a merge with existing rules; passing an empty objectupdateNetwork({})clears all allow / deny rules set at creation. -
The egress-related
allowInternetAccess/allowOut/denyOut/network.rulescan be updated.allowPublicTrafficandmaskRequestHostare creation-time-locked parameters and cannot be modified after creation. For more information about how to configurenetwork.rules, see Outbound request header transformation.
Limits
-
egressProxyis not yet supported; passing it has no effect. -
trafficAccessTokenis returned on create / connect / resume; it is not returned ongetInfo/list. Persist it for simpler reuse. -
allowPublicTrafficcan only be set at creation and cannot be modified afterward. -
denyOutsupports 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 anallowOutallowlist as needed. -
When exposing a service externally with sensitive data, set
allowPublicTraffic: false(and setsecure: true), and save thetrafficAccessTokenat 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
trafficAccessTokenorenvdAccessTokeninto logs or command output.