隔离与放置
四个彼此独立的控制,各管一件事。把它们混为一谈,多租户部署就会出现"某个租户的沙箱 跑在另一个租户机器上"。
一、放置按租户限定
每个 worker 带一个工作区,每次创建带一个租户 —— 租户来自发起请求的凭据,不是 请求体。
沙箱(租户=A) → worker(租户=A) ✓
沙箱(租户=A) → worker(租户=B) ✗ 永不调度
沙箱(租户=A) → worker(租户="") ✓ 自托管机队,故意不限定这条规则在调度器里,所以对每一条创建路径都生效:原生 API、E2B 面,以及崩溃后恢复的 重新投递。
二、worker 角色只能做两件事
worker 用 join token(jn_…)认证,拿到的角色只够到两条路由:隧道握手,和它自己的 心跳。它不能列机队、不能读沙箱、不能签发任何东西,其它一律 403。
这很重要,因为 join token 会发到一台你未必完全信任的机器上 —— 客户的 VM、外包的 笔记本。被攻陷的 worker 能跑沙箱,但看不到你部署的其余部分。
三、出网是策略,不是指望
沙箱可以带白名单创建。在 Docker worker 上,这是结构性执行:每个沙箱一个没有出口 的内部网络,加一个挂在普通网络上的出网代理 sidecar。沙箱只挂在内部网络上,所以除了 代理它没有别的路。
json
{"allowed_domains": ["pypi.org", "github.com"]}策略在容器创建时生效。对运行中的沙箱设置,只是记录下来给下一次创建用,无法就地 改造网络。细节与权衡见出网策略。
四、权限闸门(agent 与员工电脑模式)
在 agent 模式下,沙箱是某个真人的电脑,问题就变了:不是"这个容器能连到哪",而是 "这个智能体能对我的文件做什么"。那是一套六分支的决策引擎 —— 硬锁、强制拒绝、工作 目录、永久授权、会话授权、然后询问 —— 每次决策都写进审计日志,底下还可以叠一层操作 系统沙箱(Linux 的 bwrap、macOS 的 seatbelt),在内核层面执行同样的答案。
它是可选的(-permission-mode prompt|strict),因为在一次性的沙箱上加它只会多出 一堆弹窗。
平台会拒绝什么
拒绝是特性。下面每条都是故意的:
| 请求 | 回答 | 为什么 |
|---|---|---|
| 在无机队具备出网能力时请求策略 | 409 worker capability unavailable | 另一种做法是给你一个"号称被墙住、其实没有"的沙箱 |
| 超过工作区并发沙箱上限的创建 | 准入闸门返回 403 | 闸门在创建的唯一入口上,任何入口都绕不过 |
| member 想删 worker 或签发 key | 403 owner_required | 花钱和授权属于工作区 owner |
| 用名字访问别的租户的沙箱 | 404 | 与"不存在"不可区分,这是故意的 |
隔离做不到什么
- 不是虚拟机边界。 同一 worker 上的两个沙箱共享内核。需要硬件级隔离,就给工作区 分配专属 worker(或专属云主机)。
- 不是资源保证。
cpu_cores和memory_mb是调度提示;容器限额是 Docker 的, 一台繁忙的 worker 会拖慢上面的每个沙箱。 - 挡不住 worker 本身。 worker 跑 Docker,能看到它承载的每个容器。这就是 join token 被限定作用域和时效、以及"客户自己的硬件是最强租户边界"的原因。
下一步
- 出网策略 —— 策略格式与执行方式。
- Worker 与隧道 —— 能力位与租户字段。