在 DESIGN §7.5 末尾沉淀 Stage C 沙盒实施硬协议

把 §7.5 的原则具化成 6 条硬协议,Stage C 实施时按表对账而非凭记忆:
① 网络 blocklist 硬编码段(cloud metadata 169.254/16 / loopback / 内网三段 /
   CGNAT,PG IP 单独再 block 一遍 belt-and-suspenders);
② egress proxy 模型(容器 HTTP_PROXY env + iptables DROP except proxy
   端口防 SDK 绕 env;宿主侧 proxy 做域名 allowlist + 字节计量 +
   network_audit 审计日志;allowlist 初始集列出 PyPI/GitHub/npm 等);
③ 进程组清理协议(docker exec 走 setsid + kill -- -PGID,防 nohup & /
   disown 跨 exec 持久化破"同 user 不内隔离"残留风险假设);
④ 磁盘配额硬化时点(外部开放前必须升 xfs/ext4 project quota 或 zfs
   dataset quota,否则扫描间隙打满共享 fs 拖死同节点其他 user);
⑤ Executor 接口走 backend driver + ZCBOT_SANDBOX_RUNTIME config 注入
   (未来切 gVisor/Firecracker/e2b 应用层零改动,避免 Docker API 形状
   泄漏到接口层);
⑥ 工具按信任域二分 dispatch — host in-process: read/write/edit/glob/
   grep/load_skill/web_search/web_fetch(原本就在 host 持凭据 / 走
   paths.py 校验,塞容器无收益付 200ms × N);container exec:
   shell/run_python(执行任意代码必隔离)。

同时写死 gVisor / Firecracker-e2b / 容器内 tool-runner 三档升级触发信号
+ 不升级的反向兜底理由,防凭审美决策。

PROGRESS:状态表 §7 SaaS 行 + 下一步候选 #2 加锚指向新清单;时间估值
2-3 天 → 3-5 天;补 5 项 Stage C 完成 DoD 红队回归用例(metadata IP
timeout / PG IP block / nohup 残留 docker top 不见 / 跨 user 容器互访
阻断 / 非 allowlist 域名 403)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-05-26 08:58:38 +08:00
parent 588b9e1070
commit a6ae6c4647
2 changed files with 43 additions and 3 deletions

View File

@ -380,6 +380,42 @@ create index on usage_events (model_profile, created_at);
**网络**:容器默认 deny outbound 更安全;搜索和网页抓取走宿主后端受控工具。确需安装依赖时走受控 PyPI 镜像或 HTTP proxy,并计量下载量;不要让容器自由 `curl` 外网 / 内网 / cloud metadata。
**选型**:起步 Docker;流量起来后视情况换 gVisor / Firecracker / e2b。
**落地清单(Stage C 实施硬协议,与 PROGRESS Stage C DoD 锚定)** — 原则到代码的具化,实施时按此对账,避免靠记忆:
1. **网络 blocklist 硬编码段**(容器 iptables 启动脚本必含,**任一缺失视为 Stage C 未完成**):
- `169.254.0.0/16`(cloud metadata,AWS/GCP/Azure 通用 SSRF 攻击向量,Capital One 2019 同款)
- `127.0.0.0/8` / `::1`(loopback,防容器回头打宿主端口)
- `10.0.0.0/8` / `172.16.0.0/12` / `192.168.0.0/16`(内网三段)
- `100.64.0.0/10`(CGNAT,云平台常用)
- **PG 实际 IP 单独再 block 一遍**(belt-and-suspenders,无论它落在哪个段;Docker 用户自定义网络 / VPC peering 等设置会让"段级 block 看似已覆盖"实际能直连)
2. **网络 egress 模型**:容器内 `HTTP_PROXY` / `HTTPS_PROXY` env 强制走宿主侧 proxy + 容器 iptables `DROP outbound except <proxy port>`(防 SDK 不读 env 绕过)。Proxy 跑宿主侧(Squid 或自家 aiohttp),负责:① 域名 allowlist;② 红线段 IP block(同上,proxy 侧也再做一次,defense-in-depth);③ per-user 出网字节计量,纳入 §7.5 软配额(超额 429);④ 审计日志 `network_audit (user_id, task_id, ts, method, host, path, status, bytes_in, bytes_out)`。**Allowlist 初始集**:`*.pypi.org` / `*.pythonhosted.org` / `github.com` / `raw.githubusercontent.com` / `codeload.github.com` / `objects.githubusercontent.com` / `*.npmjs.org` + 部署配置的 PyPI 镜像域名。后续按用户反馈逐步加,优于"全开后某天挖矿事件"。
3. **进程组清理协议**:`docker exec` 命令通过 `setsid` 包一层(`docker exec <c> setsid <cmd>` 或在容器 entrypoint 内封装);timeout / cancel / 正常结束三种路径都走 `kill -- -PGID` 杀整个进程组。**目的**:防 `nohup &` / `disown` / 派生 daemon 在容器内跨 exec 持久化。同 user 不做内隔离 → 残留进程能看到后续 exec 的 in-memory 状态,这是接受的 #1 残留风险,但其前提是没有 stale 进程能跨 exec 存活——这条协议守不住,残留风险就放大成"跨对话持久后门"。
4. **磁盘配额硬化时点**:首版用应用层统计 + 周期扫描(对应 §7.5 软配额);**外部用户开放前必须升级到 xfs project quota 或 ext4 project quota 或 zfs dataset quota**。否则"扫描间隙打满共享 fs"会拖死同节点其他 user(攻击者写满速度远快于扫描周期),且不属于配额超额行为,排查痛苦。
5. **Executor 接口签名 + runtime config 注入**:不在工具调用层 hard-code `docker exec`,通过 backend driver 抽象:
```python
class Executor:
async def call_tool(self, tool: str, args: dict, ctx: ExecCtx) -> ToolResult: ...
```
Container 创建参数走 config:`ZCBOT_SANDBOX_RUNTIME=runc|runsc|...`(默 `runc`),per-user 容器起的时候 `docker run --runtime=<runtime>`。**理由**:未来切 gVisor / Firecracker / Kata / e2b 时应用层零改动(只换 backend driver + 改 config + 重启容器),避免接口形状泄漏 Docker 假设(`docker exec` / `docker cp` / `docker stats`)导致后期重写。
6. **工具按信任域二分,Executor 内部 dispatch**:
- **Host in-process backend**:`read` / `write` / `edit` / `glob` / `grep` / `load_skill` / `web_search` / `web_fetch` — 这些工具原本就在 host 持有凭据(Bocha API key)或走 `paths.py::resolve_user_path` 校验(user-rooted 安全边界已存在,`/v1/files` API 复用同一份),塞进容器既无安全收益又付 ~200ms exec overhead × N 次。
- **Container exec backend**:`shell` / `run_python` — 执行模型生成的任意代码,必须容器隔离。
- Dispatcher 内部分流,使用方(`AgentLoop`)零感知。**接口形状按"未来若需全部进容器 + 内部 tool-runner"留好**:只换 host backend 实现成 unix socket RPC,接口不动。
**升级触发信号(写下来防遗忘,反向兜底:无信号不升级)**:
| 升级方向 | 触发信号 | 不升级的理由 |
|---|---|---|
| Docker → **gVisor** (`runsc`) | 开放陌生用户注册 / 容器逃逸 CVE 公开未及时打补丁窗口期 / 出现可疑 syscall 模式告警 | 现状 Docker + 完整 hardening 已挡住主流容器逃逸路径外的攻击,剩下 kernel 0day 在 dogfood + 信任用户阶段不是 #1 风险;gVisor syscall 重 -30~50% 是真代价 |
| gVisor → **Firecracker / e2b** | 合规客户(PCI / HIPAA) / 高密度多租户(单机 100+ user) / gVisor 兼容性墙撞死(某 C 扩展跑不起来) | Firecracker 内存账每 VM 100MB+ 起步,zcbot 量级不划算;e2b 让数据出去执行再回来,与 storage_root 自持模型冲突 |
| `docker exec`**容器内 tool-runner**(unix socket RPC) | metric `docker_exec_overhead / total_tool_time > 30%` 持续两周 / "模型在容器内起长驻 web 服务并对外服务"工作流 / 单 task 一轮工具调用 >20 次 | 自管进程组清理 + 自管 cgroup 切片 + 协议自带状态污染面 + 失去 Docker 工具链观测(`docker stats` / `docker top` / `docker exec -it bash` 紧急介入)代价 >> 那 200ms × N 收益;**美学统一性 ≠ 升级理由** |
### 7.6 Core 代码改造(按依赖顺序)
| # | 项 | 状态 |

View File

@ -2,7 +2,7 @@
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`
最后更新:2026-05-25(dev SPA 前端 CDN 资源本地化)
最后更新:2026-05-26(§7.5 沙盒落地清单写入 DESIGN)
---
@ -15,12 +15,16 @@
| 5 | Eval Suite | ⏸ 不做 | dogfooding 替代,probe 覆盖健康检查 |
| 6 | 长任务工程化 | 🟡 | task + 恢复 ✅;双层记忆 ✅;context 压缩未做 |
| 7 | 打磨 | ❌ | Docker 沙盒 / 更多 skill |
| §7 SaaS | DESIGN §7 路线 | 🟡 | A 事件流化 ✅;B 完工 ✅;D `/v1` JSON API ✅;D' 过渡 auth + dev SPA ✅;单活 run 锁 + cancel ✅;0004 schema 瘦身 ✅;入口归位 ✅;真 OIDC 待;**C(Executor+docker sandbox)待 —— 外部用户开放 hard prereq,完成前仅 dogfood + 信任同事白名单**。 |
| §7 SaaS | DESIGN §7 路线 | 🟡 | A 事件流化 ✅;B 完工 ✅;D `/v1` JSON API ✅;D' 过渡 auth + dev SPA ✅;单活 run 锁 + cancel ✅;0004 schema 瘦身 ✅;入口归位 ✅;真 OIDC 待;**C(Executor+docker sandbox)待 —— 外部用户开放 hard prereq,完成前仅 dogfood + 信任同事白名单;DoD 详 DESIGN §7.5 落地清单 6 条**。 |
---
## 已完成关键能力
### 2026-05-26
- **§7.5 沙盒落地清单 6 条写入 DESIGN(Stage C 实施硬协议)**:Stage C 动手前把"原则 → 具体协议"沉淀,防实施时漏。① 网络 blocklist 硬编码段(`169.254.0.0/16` cloud metadata / loopback / 内网三段 / `100.64.0.0/10` CGNAT,**PG IP 单独再 block 一遍**——Capital One 2019 同款攻击向量);② egress proxy 模型(容器 `HTTP_PROXY` env + iptables DROP except proxy 端口防 SDK 绕 env,宿主侧 proxy 做域名 allowlist + 字节计量 + `network_audit` 审计日志,allowlist 初始集列出 PyPI / GitHub / npm 等);③ 进程组清理协议(`docker exec` 走 `setsid` + `kill -- -PGID`,防 `nohup &` / `disown` 跨 exec 持久化破"同 user 不内隔离"残留风险假设);④ 磁盘配额硬化时点(开外部前必须升 xfs/ext4 project quota 或 zfs dataset quota,否则扫描间隙打满共享 fs 拖死同节点);⑤ Executor 接口走 backend driver + `ZCBOT_SANDBOX_RUNTIME` config 注入(未来切 gVisor/Firecracker/e2b 应用层零改动,避免 Docker API 形状泄漏到接口层);⑥ 工具按信任域二分 dispatch — **host in-process**:`read/write/edit/glob/grep/load_skill/web_search/web_fetch`(原本就在 host 持凭据 / 走 paths.py 校验,塞容器无收益付 200ms × N),**container exec**:`shell/run_python`(执行任意代码必隔离)。同时把 gVisor / Firecracker / 容器内 tool-runner 三档升级触发信号写死,反向兜底"无信号不升级"。否决:(a) 把落地清单同时写进 DESIGN 和 PROGRESS 双 source — 漂移源,PROGRESS 只指针 DESIGN;(b) 在落地清单里写"勾对"验收语气 — DESIGN 写为什么 + 协议形状,验收语气进 PROGRESS 下一步候选 DoD;(c) 立即开始实施 — 设计先沉淀,实施排进下一步候选 #2 单独节奏。`RUN.md` 不动(运行方式无变化,Stage C 还没实施)。
### 2026-05-25
- **dev SPA 前端 CDN 资源本地化 + 升级稳定版**:`web/static/dev.html` 顶部 markdown 渲染依赖从 jsDelivr CDN 改成本地 `web/static/vendor/markdown/` 文件,避免内网/跨境访问 CDN 抖动导致 Markdown / XSS sanitizer / 代码高亮不可用。版本按 npm/latest 核对后固定为 `marked@16.2.1`(`marked.umd.js`)、`dompurify@3.2.6`、`highlight.js@11.11.1`(含 `github.min.css`),并新增 `tests/test_static_vendor.py` 标准库 unittest 回归检查:HTML 不再出现 `cdn.jsdelivr.net`,且 4 个本地 vendor 文件存在非空。`DESIGN.md` 不动(无架构变化);`RUN.md` 不动(运行方式无变化)。
@ -220,7 +224,7 @@ Python 合计 ~3400 行(+ dev.html 1700 静态 + vendor 1MB)
## 下一步候选(性价比排序)
1. **真 OIDC 接入 + CORS 收紧**(~1 天)—— `/v1/auth/login` 内部换 OIDC ID token 校验(路由层 Depends 不动);CORS 改 platform 域名 allowlist。**真发布给真实用户前必做**。
2. **§7 C Executor + sandbox**(~2-3 天)—— `run_python`/`shell` → `Executor.run(...)`,本地保留 subprocess、SaaS 走 docker;`api_key_env` → `KeyProvider` 运行时注入。多用户在线跑代码前置。
2. **§7 C Executor + sandbox**(~3-5,按 DESIGN §7.5 落地清单 6 条逐项实施)—— `run_python`/`shell` → `Executor.run(...)`,本地保留 subprocess、SaaS 走 docker;`api_key_env` → `KeyProvider` 运行时注入。多用户在线跑代码前置。**Stage C 完成 DoD** = 6 条落地清单全完成 + 红队回归用例通过:① 容器内 `curl http://169.254.169.254/...` → timeout / connection refused;② 容器内 `psql postgresql://<zcbot_pg_host>...` → IP block(连接失败);③ 容器内 `nohup sleep 1000 &` exec 退出后 `docker top <user_container>` 看不到残留进程;④ 跨 user 容器互访(A 容器 `curl http://<B_container_ip>:*`)→ 网络隔离阻断;⑤ 出网走 proxy 时未在 allowlist 的域名 → 403。原 ~2-3 天估值未含 egress proxy 部署 / xfs project quota 升级 / 红队用例,补回真实工程量。
3. **Phase 6 context 三层压缩**(~1 天)—— 兜底,V4 长上下文一般用不到。
> §7 B + D + D' + 单活 run 锁 + cancel + 0004 schema 瘦身 + 入口归位 主体已完工。剩余:真 OIDC → C(Executor)→ F(deploy / billing)。§7 E CLI 双模式撤;Phase G Jinja2/HTMX 撤(详见 DESIGN §7.9)。