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

5.9 KiB
Raw Blame History

两层安全边界设计

审批决定"能不能做"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)无关。

if spec.is_high_risk() && !approved {
    // 发送确认码 → 等待用户回复 → Approved/Rejected/Expired
}

修复的漏洞:原实现审批只在 http/db 消息分支触发,stdio → result 可绕过。tool_managerhigh 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

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), 不受信任代码默认进容器。

启用沙箱

export IAS_DOCKER_SANDBOX=1   # 工具执行走一次性容器
export IAS_DOCKER_BUILDER=1   # tool_manager 编译走 builder 容器

未设置时退回本地执行(仍保留 env_clear + 白名单注入)。

builder 容器特殊处理

--cap-drop ALL 丢掉 CAP_DAC_OVERRIDEroot 无法写 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.logts/uid/action/tool/result
构建超时 timeout 180s 包裹 cargo build
失败清理 构建失败删除残留旧二进制,避免 reload 后调用旧版本"假成功"
离线编译 IAS_DOCKER_BUILDER=1 时在 builder 容器内 --offline 编译

结构化结果

executor 返回 ToolRunOutcome 枚举替代裸字符串:

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