Files
iAs/docs/SECURITY_BOUNDARIES.md
T
wunianxiao 092f3b78f2 refactor(sandbox): 按威胁模型精确化——仅不受信任代码进容器
http/db 类工具的子进程是瘦壳(纯计算),危险操作全在受信任主进程,
套容器是纯开销无隔离收益。调整为:

- 开发者预写工具:移除 spec 的 sandbox: profile: stdio,回默认 local
  本地执行(仍保留 env_clear + 白名单注入)
- tool_manager 产出的工具:spec_yaml 自动写 sandbox: profile: stdio,
  不受信任代码(LLM 提供 rust_code)默认进 stdio 容器
- tool_manager 编译:仍走 builder 容器
- 移除 ias-network-runner Dockerfile(当前无工具直接联网)
- executor docker 分支仅保留 stdio/builder,删 network 分支

验证:datetime 本地执行 +08:00;tool_manager 产出工具 spec 含
sandbox: profile: stdio 且容器内执行成功;25 测试通过。
2026-06-22 16:53:36 +08:00

156 lines
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 两层安全边界设计
> 审批决定"能不能做"Docker 限制"做坏了能坏到哪里",审计回答"谁让系统新增或修改了什么能力"。
## 架构
```
LLM 调用工具
→ registry.dispatch(name, params, user_id, approved)
→ load spec + validate params
→ 【授权边界】if high_risk && !approved: 统一审批(不依赖消息类型)
→ 【隔离边界】按 sandbox.profile 选择执行方式:
- local : 本地 spawnenv_clear + 白名单注入)
- stdio : docker run ias-stdio-runner (无网络、只读根)
- network : docker run ias-network-runner(联网、资源限制)
- builder : docker run ias-tool-builder (离线编译,仅 tool_manager
→ stdin 传 envelope → stdout 读 JSON → 超时 kill → 容器销毁
```
## 第一层:统一工具级审批(授权边界)
**位置**`src/tools/executor.rs` `execute_loop()` 入口
高风险工具在 **spawn 之前** 必须获得用户确认,与工具后续输出的消息类型(`result`/`http`/`db`)无关。
```rust
if spec.is_high_risk() && !approved {
// 发送确认码 → 等待用户回复 → Approved/Rejected/Expired
}
```
**修复的漏洞**:原实现审批只在 `http`/`db` 消息分支触发,`stdio → result` 可绕过。`tool_manager`high risk)直接输出 result 即可零确认执行。
## 第二层:一次性 Docker 容器(隔离边界)
### 适用范围(按威胁模型精确化)
http/db 类工具的子进程是"瘦壳"——危险操作(联网、DB 读写)全在受信任的主进程里,
给它们套容器是纯开销,**没有隔离收益**。因此:
| 场景 | 是否容器隔离 | 原因 |
|------|------------|------|
| 开发者预写工具(weather/amap/... | ❌ 本地执行 | 子进程纯计算,危险操作在主进程 |
| tool_manager 产出的工具 | ✅ stdio 容器 | 含 LLM 提供的 rust_code,不受信任 |
| tool_manager 编译 | ✅ builder 容器 | cargo 执行任意 build script/proc macro |
### 两类基础镜像
| 镜像 | 用途 | 网络 | 文件系统 |
|------|------|------|----------|
| `ias-stdio-runner` | tool_manager 产出的不受信任工具 | none | 只读根 + tmpfs /tmp |
| `ias-tool-builder` | tool_manager 编译 Rust 工具 | none | 可写 /workbind mount |
构建:`docker/build-images.sh`
### 容器安全基线
所有容器统一:
- `--rm` 一次性,用完销毁
- `--cap-drop ALL` 丢弃所有 Linux capability
- `--security-opt no-new-privileges` 禁止提权
- `--memory` / `--cpus` / `--pids-limit` 资源限制
- 只挂载工具二进制(readonly+ tmpfs /tmp
- 不挂载项目根目录、不挂载 docker.sock
- env 按白名单注入(`env_clear` + spec.env
### 沙箱配置(spec
```yaml
sandbox:
profile: stdio # local | stdio | builder
network: none # none | default
memory: 256m
cpus: "0.5"
pids_limit: 64
read_only: true
```
默认 `profile: local`(不启用容器)。开发者预写工具保持默认。
**tool_manager 产出的工具自动写入 `sandbox: profile: stdio`**(见 tool_manager `spec_yaml`),
不受信任代码默认进容器。
### 启用沙箱
```bash
export IAS_DOCKER_SANDBOX=1 # 工具执行走一次性容器
export IAS_DOCKER_BUILDER=1 # tool_manager 编译走 builder 容器
```
未设置时退回本地执行(仍保留 env_clear + 白名单注入)。
### builder 容器特殊处理
`--cap-drop ALL` 丢掉 `CAP_DAC_OVERRIDE`root 无法写 bind mount 的非 root 目录。
解法:builder 容器以宿主 `uid:gid` 运行(`--user`),自然能写自己的目录。
镜像内 `CARGO_HOME=/tmp/cargo` 预缓存 serde_json 并 `chmod 777`,任意 uid 可用。
编译加 `--offline`,确保 `--network none` 下不尝试联网。
## tool_manager 加固
| 措施 | 实现 |
|------|------|
| 禁止自改 | `RESERVED_NAMES` 包含 `tool_manager`,禁止 create/update/build |
| 强制 high risk | `spec_yaml` 忽略调用方 risk_level,一律写 `high` |
| 审计日志 | 每次调用追加 `tools/.tool_audit.log`ts/uid/action/tool/result |
| 构建超时 | `timeout 180s` 包裹 cargo build |
| 失败清理 | 构建失败删除残留旧二进制,避免 reload 后调用旧版本"假成功" |
| 离线编译 | `IAS_DOCKER_BUILDER=1` 时在 builder 容器内 `--offline` 编译 |
## 结构化结果
`executor` 返回 `ToolRunOutcome` 枚举替代裸字符串:
```rust
enum ToolRunOutcome {
Ok(String),
Err { kind: ToolErrorKind, message: String },
}
```
`daemon` 据此判断是否 reloadtool_manager 成功后重载注册表),
替代原 `is_tool_error()` 的中文前缀字符串匹配。
## 审批并发修复
`ApprovalManager::clean_expired()` 两阶段加锁:
收集过期 `code_hash` → 更新 DB → 重新加锁按 `code_hash` 定位删除。
不依赖快照 index,避免并发回复导致 index 错位误删未过期审批。
## 验证
| 场景 | 结果 |
|------|------|
| tool_manager update 自身 | ❌ 拒绝(保留名) |
| create_tool risk_level=low | spec 强制 high |
| tool_manager 产出工具 spec | 自动含 `sandbox: profile: stdio` |
| datetime 本地执行 | +08:00(宿主时区,默认不套容器) |
| tool_manager 产出工具 Docker 执行 | 容器内运行成功 |
| builder 容器编译 serde_json | ✅ 离线成功 |
| cargo test | 25 passed |
## 实施状态
| # | 步骤 | 状态 |
|---|------|------|
| 1 | 统一工具级审批 | ✅ |
| 2 | 禁止 tool_manager 自改 | ✅ |
| 3 | 强制生成工具 high risk | ✅ |
| 4 | env_clear 环境白名单 | ✅ |
| 5 | 修 clean_expired 并发 | ✅ |
| 6 | 三类 Docker 镜像 + sandbox spec | ✅ |
| 7 | stdio 工具迁移到一次性容器 | ✅ |
| 8 | network 工具迁移 | ➖ 不需要:工具走宿主代理联网,子进程纯计算,套容器无隔离收益 |
| 9 | tool_manager build 迁移到 builder 容器 | ✅ |
| 10 | 结构化结果替换 is_tool_error | ✅ |