云沙箱的网络配置用于控制公网访问鉴权(入站)和出网访问控制(出站)两类相互独立的行为。
功能介绍
-
公网访问鉴权(入站):沙箱对外暴露的公网 URL 是否需要携带令牌才能访问。
-
出网访问控制(出站 / egress):沙箱主动访问外部网络时,允许或拒绝哪些目标。
网络配置在创建沙箱时通过顶层的 allowInternetAccess 和 network 对象传入。
字段命名在三套接口间不完全一致,注意区分:
Python SDK:全部 snake_case,如
allow_internet_access、allow_public_traffic、allow_out、deny_out。JavaScript / TypeScript SDK:全部小驼峰,如
allowInternetAccess、allowPublicTraffic、allowOut、denyOut。HTTP API:混合风格——
POST /sandboxes的顶层出网总开关为allow_internet_access(下划线,同时兼容allowInternetAccess),而network对象内字段一律小驼峰(network.allowPublicTraffic、network.allowOut、network.denyOut、network.maskRequestHost);PUT /sandboxes/{id}/network的出网总开关为allowInternetAccess(小驼峰)。下文示例分别给出 Node.js 与 Python 两种写法,请按所用 SDK 选择对应风格。
运行准备
下文示例提供 Node.js 与 Python 两种写法,代码从环境变量读取连接信息。运行前先安装 SDK 并配置以下环境变量(E2B_API_URL / E2B_DOMAIN 按目标地域填写,<region> 如 cn-shenzhen):
# 连接信息(替换成你的实际值)
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(示例以 TypeScript 编写,保存为 demo.ts):
npm install e2b@^2.31.0
npx tsx demo.ts
Python(将示例保存为 demo.py,要求 Python ≥ 3.10):
python3 --version # 确认 ≥ 3.10
pip install e2b==2.31.0 httpx # httpx 仅公网鉴权示例用到
python demo.py
注意:运行时更新(
update_network/updateNetwork)需 Python SDK ≥ 2.11,而 SDK ≥ 2.11 要求 Python ≥ 3.10。在 Python 3.9 上不指定版本安装e2b时,只会装到最后一个兼容版本(2.10.x),调用update_network会报AttributeError: 'Sandbox' object has no attribute 'update_network'。此时请升级到 Python ≥ 3.10 后重装,或改用 RESTPUT /sandboxes/{id}/network。
参数总览
| 参数(SDK / API) | 作用 | 是否支持 | 创建后可否修改 |
allowInternetAccess / allow_internet_access |
出网总开关,false 等价于 denyOut: ["0.0.0.0/0"] |
支持 | 可(updateNetwork) |
network.allowPublicTraffic |
公网 URL 是否需要令牌鉴权,默认 true |
支持 | 否(仅创建时可设) |
network.allowOut |
出网允许清单,支持 IP / CIDR / 域名 | 支持 | 可(updateNetwork) |
network.denyOut |
出网拒绝清单,支持 IP / CIDR / 域名 | 支持 | 可(updateNetwork) |
network.egressProxy |
出网 SOCKS5 代理 | 暂不支持 | — |
network.maskRequestHost |
改写沙箱请求的 Host 头 |
支持 | 否(仅创建时可设) |
network.rules |
按域名对出网请求做转换(如注入 HTTP 头) | 支持 | 可(updateNetwork) |
egressProxy暂不支持,传入不会生效。network.rules的详细用法,请参见出网请求头转换。
公网访问鉴权
沙箱内启动的服务可以通过公网 URL 暴露给外部。默认情况下,只要知道 URL 的人都能访问;对敏感业务,可用 allowPublicTraffic 强制调用方先鉴权。
-
allowPublicTraffic: true(默认):公网 URL 无需鉴权即可访问。 -
allowPublicTraffic: false:所有对公网 URL 的请求必须携带e2b-traffic-access-token请求头,其值为创建沙箱时返回的trafficAccessToken;缺失或错误时请求返回403。
注意:设置
allowPublicTraffic: false时必须同时设置secure: true。否则连 SDK 自身对沙箱控制面(envd)的访问也会被公网令牌门禁拦截,commands/files等操作会返回403。secure: true会启用控制面鉴权(envdAccessToken,请求头X-Access-Token,由 SDK 自动携带),与公网入站的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, // allowPublicTraffic 为 false 时必须开启,否则 SDK 无法访问控制面
network: {
allowPublicTraffic: false,
},
});
console.log(`[create] 沙箱已创建: ${sandbox.sandboxId}`);
try {
// 创建后立刻拿到令牌,建议自行保存;后续 getInfo / list 不返回,但可通过 connect 重新取回(见下文)
// 令牌为敏感信息,不要直接打印;此处仅提示已获取,实际值请安全保存
console.log(`[token] trafficAccessToken 已获取(敏感信息,勿写入日志)`);
// 在沙箱内启动服务后,用 getHost 拿到对外主机名
await sandbox.commands.run("python3 -m http.server 8080", { background: true });
const url = `https://${sandbox.getHost(8080)}`;
console.log(`[serve] 服务已启动,公网地址: ${url}`);
// 不带令牌:403
const r1 = await fetch(url);
console.log(`[probe] 不带令牌访问 -> ${r1.status}`); // 403
// 带令牌:200
const r2 = await fetch(url, {
headers: { "e2b-traffic-access-token": sandbox.trafficAccessToken },
});
console.log(`[probe] 携带令牌访问 -> ${r2.status}`); // 200
} finally {
// 执行完成后主动销毁沙箱,避免残留占用与计费
await sandbox.kill();
console.log("[cleanup] 沙箱已销毁");
}
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, # allow_public_traffic 为 False 时必须开启,否则 SDK 无法访问控制面
network={"allow_public_traffic": False},
)
print(f"[create] 沙箱已创建: {sandbox.sandbox_id}")
try:
# 创建后立刻拿到令牌,建议自行保存;后续 getInfo / list 不返回,但可通过 connect 重新取回(见下文)
# 令牌为敏感信息,不要直接打印;此处仅提示已获取,实际值请安全保存
print("[token] traffic_access_token 已获取(敏感信息,勿写入日志)")
# 在沙箱内启动服务后,用 get_host 拿到对外主机名
sandbox.commands.run("python3 -m http.server 8080", background=True)
url = f"https://{sandbox.get_host(8080)}"
print(f"[serve] 服务已启动,公网地址: {url}")
# 不带令牌:403
r1 = httpx.get(url)
print(f"[probe] 不带令牌访问 -> {r1.status_code}") # 403
# 带令牌:200
r2 = httpx.get(url, headers={"e2b-traffic-access-token": sandbox.traffic_access_token})
print(f"[probe] 携带令牌访问 -> {r2.status_code}") # 200
finally:
# 执行完成后主动销毁沙箱,避免残留占用与计费
sandbox.kill()
print("[cleanup] 沙箱已销毁")
令牌何时返回
云沙箱在 create、connect、resume 时都会返回 trafficAccessToken:
-
create(
POST /sandboxes):token 来自 FC 会话创建响应。 -
connect(
POST /sandboxes/{id}/connect):200(已 running)与201(已 resume)两种响应都携带 token。-
目标
paused→ 触发 FC Resume,token 来自 resume 响应; -
目标已
running/resuming→ 不触发 FC Resume,网关从 FCSessionRecords表按会话 ID 读回后回填,保证 connect 契约一致。
-
-
resume(已废弃的
POST /sandboxes/{id}/resume):同样返回 token。
需要注意:
-
sandbox.getInfo()(GET /sandboxes/{id})与Sandbox.list()走查询路径,响应体不含trafficAccessToken——SandboxDetail结构无此字段。 -
极少数降级情况下(网关未配置令牌读取器、沙箱无会话 ID、或为公开沙箱本就无 token),
connect仍会正常返回,只是不带 token(不影响连接)。
建议:把 trafficAccessToken 随 sandboxId 一起持久化保存,便于随时使用。getInfo / list 不返回该令牌;即使丢失,通常也可通过 connect 重新取回(仅上述极少数降级场景可能不带)。
与 envdAccessToken 的区别
云沙箱里有两个容易混淆的令牌,用途、请求头、下发时机都不同:
| 对比项 | trafficAccessToken |
envdAccessToken |
| 用途 | 访问沙箱公网 URL(入站流量鉴权) | SDK 访问沙箱控制面(envd)的鉴权 |
| 开关 | allowPublicTraffic: false 时生效 |
由 secure 控制(安全访问) |
| 请求头 | e2b-traffic-access-token |
X-Access-Token |
| 下发时机 | create / connect / resume 均返回(getInfo、list 不返回) |
创建及获取沙箱详情时返回 |
allowPublicTraffic 管的是“外部 → 沙箱内服务”的入站访问;secure / envdAccessToken 管的是“SDK → 沙箱控制面”的通信,两者是不同层面的能力。
出网访问控制
出网访问控制约束沙箱主动访问外部网络的目标范围,由 allowInternetAccess、allowOut、denyOut 共同决定。
总开关:allowInternetAccess
-
true(默认):允许访问公网。 -
false:等价于在denyOut中追加"0.0.0.0/0",建立“默认拒绝全部”的基线,而不是不可覆盖的硬开关。由于allowOut优先级高于denyOut(见下文“优先级与匹配规则”),只有单独使用(不配allowOut)时才等于禁止一切出网;一旦配合allowOut,allowOut命中的目标仍会放行,即可实现“默认拒绝 + 白名单”。
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,
allowInternetAccess: false,
});
console.log(`[create] 沙箱已创建(已隔离外网): ${sandbox.sandboxId}`);
try {
// 出网已被禁止,以应用层结果为准;用 || echo 兜底避免非零退出码抛异常
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] 访问 api.github.com -> ${probe.stdout.trim()}`); // blocked
} finally {
// 执行完成后主动销毁沙箱,避免残留占用与计费
await sandbox.kill();
console.log("[cleanup] 沙箱已销毁");
}
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"],
allow_internet_access=False,
)
print(f"[create] 沙箱已创建(已隔离外网): {sandbox.sandbox_id}")
try:
# 出网已被禁止,以应用层结果为准;用 || echo 兜底避免非零退出码抛异常
probe = 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 -> {probe.stdout.strip()}") # blocked
finally:
# 执行完成后主动销毁沙箱,避免残留占用与计费
sandbox.kill()
print("[cleanup] 沙箱已销毁")
允许清单 allowOut 与拒绝清单 denyOut
allowOut 每个条目可以是 CIDR / 裸 IP / 域名("example.com"、"*.example.com");denyOut 同样支持 IP / CIDR / 域名。
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: ["0.0.0.0/0"],
allowOut: ["8.8.8.8", "8.8.8.0/24", "api.example.com", "*.github.com"],
},
});
console.log(`[create] 沙箱已创建(白名单出网): ${sandbox.sandboxId}`);
try {
// 命中 *.github.com,放行
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(白名单内)-> ${allowed.stdout.trim()}`);
// 不在白名单,拒绝
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(白名单外)-> ${denied.stdout.trim()}`); // blocked
} finally {
// 执行完成后主动销毁沙箱,避免残留占用与计费
await sandbox.kill();
console.log("[cleanup] 沙箱已销毁");
}
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.sandbox_id}")
try:
# 命中 *.github.com,放行
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(白名单内)-> {allowed.stdout.strip()}")
# 不在白名单,拒绝
denied = sandbox.commands.run(
'curl -sS -o /dev/null -w "%{http_code}" -m 5 https://pypi.org || echo blocked'
)
print(f"[probe] pypi.org(白名单外)-> {denied.stdout.strip()}") # blocked
finally:
# 执行完成后主动销毁沙箱,避免残留占用与计费
sandbox.kill()
print("[cleanup] 沙箱已销毁")
在 SDK 里,也可以用选择器回调表达“全部流量”,这是官方推荐写法(ALL_TRAFFIC 常量仍保留用于向后兼容):
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.sandboxId}`);
try {
// ... 业务逻辑 ...
} finally {
// 执行完成后主动销毁沙箱,避免残留占用与计费
await sandbox.kill();
console.log("[cleanup] 沙箱已销毁");
}
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.sandbox_id}")
try:
# ... 业务逻辑 ...
pass
finally:
# 执行完成后主动销毁沙箱,避免残留占用与计费
sandbox.kill()
print("[cleanup] 沙箱已销毁")
域名过滤的范围与限制
使用域名做过滤时,有几个必须注意的行为:
-
域名白名单必须显式拒绝其余流量:必须配合
denyOut: ["0.0.0.0/0"](或allowInternetAccess: false),否则不生效。 -
仅覆盖 HTTP(80 端口)与 TLS(443 端口):域名匹配依赖 80 端口的
Host头和 443 端口的 TLS SNI。其他端口只能按 IP / CIDR 过滤;QUIC / HTTP/3 等 UDP 协议不支持域名过滤。 -
DNS 自动放行:只要用到域名,系统会自动放行默认 DNS 服务器
8.8.8.8,以保证域名解析可用。 -
支持通配符
*.example.com匹配所有子域名。
优先级与匹配规则
多个参数同时存在时,按下述规则判定一次出网请求是否放行:
-
允许优先于拒绝:
allowOut始终优先于denyOut。若同一目标同时命中允许和拒绝清单,则放行。 -
allowInternetAccess: false只是等价于denyOut += ["0.0.0.0/0"]:它建立“默认拒绝全部”的基线,但不是硬关闭。因为 allow 优先于 deny,allowOut命中的目标仍会穿透该基线被放行——因此allowInternetAccess: false+allowOut就是“默认拒绝 + 白名单”,只有在不配allowOut时才真正禁止一切出网。 -
域名白名单必须显式
denyOut全部:见上一节。
常见组合的效果:
| 配置 | 效果 |
| 不配置任何出网参数 | 允许访问全部公网(默认) |
allowInternetAccess: false(不配 allowOut) |
禁止一切出网(等价 denyOut: ["0.0.0.0/0"]) |
allowInternetAccess: false + allowOut: ["8.8.8.8"] |
默认拒绝全部,但放行 allowOut 命中的 8.8.8.8(allow 优先,等同 denyOut: ["0.0.0.0/0"] + allowOut) |
denyOut: ["10.0.0.0/8"] |
仅拒绝该网段,其余放行(黑名单) |
denyOut: ["0.0.0.0/0"] + allowOut: ["8.8.8.8"] |
仅放行 8.8.8.8,其余全部拒绝(白名单) |
allowOut: ["example.com"](不配 denyOut) |
域名不会生效,其余流量仍放行——需补 denyOut: ["0.0.0.0/0"] |
被拒绝的连接行为
受出网防火墙的实现方式影响,被拒绝的 TCP 连接从沙箱内部看可能“连接成功”:防火墙需要先接受连接才能判断目标是否被允许,因此 socket 可能显示为已建立,但实际没有任何报文到达目标。
判断流量是否真正到达目标,应检查应用层响应(HTTP 状态码、TLS 握手、预期的协议字节等),而不要以 TCP 连接是否建立为准。
运行时更新出网配置
创建后可以用 updateNetwork(TypeScript)/ update_network(Python)在不重建沙箱的情况下调整出网规则:
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.sandboxId}`);
try {
// 收紧:屏蔽单个 IP
await sandbox.updateNetwork({ denyOut: ["8.8.8.8"] });
console.log("[update] 已屏蔽 8.8.8.8");
// 替换为纯白名单
await sandbox.updateNetwork({
denyOut: ({ allTraffic }) => [allTraffic],
allowOut: ["api.example.com"],
});
console.log("[update] 已切换为白名单:仅放行 api.example.com");
// 直接切换出网总开关
await sandbox.updateNetwork({ allowInternetAccess: false });
console.log("[update] 已关闭出网总开关");
} finally {
// 执行完成后主动销毁沙箱,避免残留占用与计费
await sandbox.kill();
console.log("[cleanup] 沙箱已销毁");
}
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.sandbox_id}")
try:
# 收紧:屏蔽单个 IP
sandbox.update_network({"deny_out": ["8.8.8.8"]})
print("[update] 已屏蔽 8.8.8.8")
# 替换为纯白名单
sandbox.update_network({
"deny_out": lambda ctx: [ctx.all_traffic],
"allow_out": ["api.example.com"],
})
print("[update] 已切换为白名单:仅放行 api.example.com")
# 直接切换出网总开关
sandbox.update_network({"allow_internet_access": False})
print("[update] 已关闭出网总开关")
finally:
# 执行完成后主动销毁沙箱,避免残留占用与计费
sandbox.kill()
print("[cleanup] 沙箱已销毁")
注意:
-
updateNetwork是整体替换,不与已有规则合并;传入空对象updateNetwork({})会清空创建时设置的全部 allow / deny 规则。 -
出网相关的
allowInternetAccess/allowOut/denyOut/network.rules可更新。allowPublicTraffic与maskRequestHost属于创建时锁定参数,创建后不可修改。network.rules的具体配置方式,请参见出网请求头转换。
使用限制
-
egressProxy暂不支持,传入不会生效。 -
trafficAccessToken在 create / connect / resume 时均返回;getInfo/list不返回。建议持久化保存以简化使用。 -
allowPublicTraffic仅创建时可设,创建后不可修改。 -
denyOut支持 IP / CIDR / 域名。 -
域名过滤仅覆盖 HTTP(80 端口)与 TLS(443 端口),其余端口按 IP / CIDR 过滤,UDP(QUIC、HTTP/3)不支持域名过滤。
-
被拒绝的连接可能在沙箱内表现为“连接成功”,应以应用层响应为准。
-
出网控制(
allowOut/denyOut)与公网入站鉴权(allowPublicTraffic)相互独立。
使用建议
-
面向不可信代码或第三方任务,默认收紧出网:
denyOut: ["0.0.0.0/0"]+ 按需allowOut白名单。 -
对外暴露服务且数据敏感时,设置
allowPublicTraffic: false(同时设置secure: true),并在创建时保存trafficAccessToken,由业务侧妥善传递。 -
使用域名白名单时,务必补上
denyOut: ["0.0.0.0/0"],且确认目标走的是 80/443 端口。 -
不要把
trafficAccessToken、envdAccessToken写入日志或命令输出。