网络访问控制

更新时间:
复制 MD 格式

云沙箱的网络配置用于控制公网访问鉴权(入站)和出网访问控制(出站)两类相互独立的行为。

功能介绍

  • 公网访问鉴权(入站):沙箱对外暴露的公网 URL 是否需要携带令牌才能访问。

  • 出网访问控制(出站 / egress):沙箱主动访问外部网络时,允许或拒绝哪些目标。

网络配置在创建沙箱时通过顶层的 allowInternetAccessnetwork 对象传入。

字段命名在三套接口间不完全一致,注意区分

  • Python SDK:全部 snake_case,如 allow_internet_accessallow_public_trafficallow_outdeny_out

  • JavaScript / TypeScript SDK:全部小驼峰,如 allowInternetAccessallowPublicTrafficallowOutdenyOut

  • HTTP API:混合风格——POST /sandboxes 的顶层出网总开关为 allow_internet_access(下划线,同时兼容 allowInternetAccess),而 network 对象内字段一律小驼峰(network.allowPublicTrafficnetwork.allowOutnetwork.denyOutnetwork.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 后重装,或改用 REST PUT /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 等操作会返回 403secure: 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

  • createPOST /sandboxes):token 来自 FC 会话创建响应。

  • connectPOST /sandboxes/{id}/connect):200(已 running)与 201(已 resume)两种响应都携带 token。

    • 目标 paused → 触发 FC Resume,token 来自 resume 响应;

    • 目标已 running / resuming → 不触发 FC Resume,网关从 FC SessionRecords 表按会话 ID 读回后回填,保证 connect 契约一致。

  • resume(已废弃的 POST /sandboxes/{id}/resume):同样返回 token。

需要注意:

  • sandbox.getInfo()GET /sandboxes/{id})与 Sandbox.list() 走查询路径,响应体不含 trafficAccessToken——SandboxDetail 结构无此字段。

  • 极少数降级情况下(网关未配置令牌读取器、沙箱无会话 ID、或为公开沙箱本就无 token),connect 仍会正常返回,只是不带 token(不影响连接)。

建议:把 trafficAccessTokensandboxId 一起持久化保存,便于随时使用。getInfo / list 不返回该令牌;即使丢失,通常也可通过 connect 重新取回(仅上述极少数降级场景可能不带)。

与 envdAccessToken 的区别

云沙箱里有两个容易混淆的令牌,用途、请求头、下发时机都不同:

对比项 trafficAccessToken envdAccessToken
用途 访问沙箱公网 URL(入站流量鉴权) SDK 访问沙箱控制面(envd)的鉴权
开关 allowPublicTraffic: false 时生效 secure 控制(安全访问)
请求头 e2b-traffic-access-token X-Access-Token
下发时机 create / connect / resume 均返回(getInfolist 不返回) 创建及获取沙箱详情时返回

allowPublicTraffic 管的是“外部 → 沙箱内服务”的入站访问;secure / envdAccessToken 管的是“SDK → 沙箱控制面”的通信,两者是不同层面的能力。

出网访问控制

出网访问控制约束沙箱主动访问外部网络的目标范围,由 allowInternetAccessallowOutdenyOut 共同决定。

总开关:allowInternetAccess

  • true(默认):允许访问公网。

  • false:等价于在 denyOut 中追加 "0.0.0.0/0",建立“默认拒绝全部”的基线,而不是不可覆盖的硬开关。由于 allowOut 优先级高于 denyOut(见下文“优先级与匹配规则”),只有单独使用(不配 allowOut)时才等于禁止一切出网;一旦配合 allowOutallowOut 命中的目标仍会放行,即可实现“默认拒绝 + 白名单”。

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 匹配所有子域名。

优先级与匹配规则

多个参数同时存在时,按下述规则判定一次出网请求是否放行:

  1. 允许优先于拒绝allowOut 始终优先于 denyOut。若同一目标同时命中允许和拒绝清单,则放行

  2. allowInternetAccess: false 只是等价于 denyOut += ["0.0.0.0/0"]:它建立“默认拒绝全部”的基线,但不是硬关闭。因为 allow 优先于 deny,allowOut 命中的目标仍会穿透该基线被放行——因此 allowInternetAccess: false + allowOut 就是“默认拒绝 + 白名单”,只有在不配 allowOut 时才真正禁止一切出网。

  3. 域名白名单必须显式 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 可更新。allowPublicTrafficmaskRequestHost 属于创建时锁定参数,创建后不可修改。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 端口。

  • 不要把 trafficAccessTokenenvdAccessToken 写入日志或命令输出。