From 092f3b78f2525b80e9d82ef1821d6e2da320ebdd Mon Sep 17 00:00:00 2001 From: wunianxiao Date: Mon, 22 Jun 2026 16:53:36 +0800 Subject: [PATCH] =?UTF-8?q?refactor(sandbox):=20=E6=8C=89=E5=A8=81?= =?UTF-8?q?=E8=83=81=E6=A8=A1=E5=9E=8B=E7=B2=BE=E7=A1=AE=E5=8C=96=E2=80=94?= =?UTF-8?q?=E2=80=94=E4=BB=85=E4=B8=8D=E5=8F=97=E4=BF=A1=E4=BB=BB=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=BF=9B=E5=AE=B9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 测试通过。 --- docker/Dockerfile.network-runner | 8 ----- docker/build-images.sh | 10 +++---- docs/SECURITY_BOUNDARIES.md | 30 +++++++++++++------ src/tools/executor.rs | 4 +-- src/tools/spec.rs | 6 ++-- tools/amap/specs/amap_geocode.tool.yaml | 2 -- tools/amap/specs/amap_map_link.tool.yaml | 2 -- tools/amap/specs/amap_poi_search.tool.yaml | 2 -- .../amap/specs/amap_reverse_geocode.tool.yaml | 2 -- tools/amap/specs/amap_route_plan.tool.yaml | 2 -- tools/amap/specs/amap_travel_plan.tool.yaml | 2 -- .../specs/get_current_datetime.tool.yaml | 2 -- tools/fetch_page/specs/fetch_page.tool.yaml | 2 -- tools/memories/specs/delete_memory.tool.yaml | 2 -- tools/memories/specs/get_memory.tool.yaml | 2 -- tools/memories/specs/read_memories.tool.yaml | 2 -- tools/memories/specs/update_memory.tool.yaml | 2 -- tools/memories/specs/write_memory.tool.yaml | 2 -- tools/tool_manager/src/main.rs | 4 +++ .../weather/specs/weather_forecast.tool.yaml | 2 -- tools/weather/specs/weather_hourly.tool.yaml | 2 -- tools/weather/specs/weather_now.tool.yaml | 2 -- tools/web_search/specs/web_search.tool.yaml | 2 -- 23 files changed, 35 insertions(+), 61 deletions(-) delete mode 100644 docker/Dockerfile.network-runner diff --git a/docker/Dockerfile.network-runner b/docker/Dockerfile.network-runner deleted file mode 100644 index 89f3935..0000000 --- a/docker/Dockerfile.network-runner +++ /dev/null @@ -1,8 +0,0 @@ -# ias-network-runner — 允许联网的工具运行环境 -# 供需要自行发起 HTTP 的工具使用(当前工具均通过宿主 http 消息代理联网, -# 此镜像为将来工具直接联网预留)。运行时仍可按需 --network none。 -FROM debian:bookworm-slim -RUN apt-get update \ - && apt-get install -y --no-install-recommends ca-certificates curl \ - && rm -rf /var/lib/apt/lists/* -ENTRYPOINT [] diff --git a/docker/build-images.sh b/docker/build-images.sh index de206fb..8454559 100755 --- a/docker/build-images.sh +++ b/docker/build-images.sh @@ -1,16 +1,16 @@ #!/usr/bin/env bash -# 构建三类沙箱基础镜像。无需为每个工具单独写 Dockerfile。 +# 构建沙箱基础镜像。 +# - ias-stdio-runner : 运行 tool_manager 产出的不受信任工具(无网络只读) +# - ias-tool-builder : tool_manager 编译 Rust 工具(离线) +# 无需为每个工具单独写 Dockerfile。 set -euo pipefail cd "$(dirname "$0")" # docker/ echo "=== 构建 ias-stdio-runner(无网络只读容器)===" docker build -t ias-stdio-runner -f Dockerfile.stdio-runner . -echo "=== 构建 ias-network-runner(联网容器)===" -docker build -t ias-network-runner -f Dockerfile.network-runner . - echo "=== 构建 ias-tool-builder(Rust 编译容器)===" docker build -t ias-tool-builder -f Dockerfile.tool-builder . echo "=== 完成 ===" -docker images | grep -E '^ias-(stdio|network|tool)' +docker images | grep -E '^ias-(stdio|tool)' diff --git a/docs/SECURITY_BOUNDARIES.md b/docs/SECURITY_BOUNDARIES.md index 1e45b18..db7ce2c 100644 --- a/docs/SECURITY_BOUNDARIES.md +++ b/docs/SECURITY_BOUNDARIES.md @@ -33,12 +33,22 @@ if spec.is_high_risk() && !approved { ## 第二层:一次性 Docker 容器(隔离边界) -### 三类基础镜像 +### 适用范围(按威胁模型精确化) + +http/db 类工具的子进程是"瘦壳"——危险操作(联网、DB 读写)全在受信任的主进程里, +给它们套容器是纯开销,**没有隔离收益**。因此: + +| 场景 | 是否容器隔离 | 原因 | +|------|------------|------| +| 开发者预写工具(weather/amap/...) | ❌ 本地执行 | 子进程纯计算,危险操作在主进程 | +| tool_manager 产出的工具 | ✅ stdio 容器 | 含 LLM 提供的 rust_code,不受信任 | +| tool_manager 编译 | ✅ builder 容器 | cargo 执行任意 build script/proc macro | + +### 两类基础镜像 | 镜像 | 用途 | 网络 | 文件系统 | |------|------|------|----------| -| `ias-stdio-runner` | 普通 stdio 工具 | none | 只读根 + tmpfs /tmp | -| `ias-network-runner` | 需联网的工具(预留) | default | 只读根 + tmpfs /tmp | +| `ias-stdio-runner` | tool_manager 产出的不受信任工具 | none | 只读根 + tmpfs /tmp | | `ias-tool-builder` | tool_manager 编译 Rust 工具 | none | 可写 /work(bind mount) | 构建:`docker/build-images.sh` @@ -58,7 +68,7 @@ if spec.is_high_risk() && !approved { ```yaml sandbox: - profile: stdio # local | stdio | network | builder + profile: stdio # local | stdio | builder network: none # none | default memory: 256m cpus: "0.5" @@ -66,7 +76,9 @@ sandbox: read_only: true ``` -默认 `profile: local`(不启用容器)。现有非 tool_manager 工具均设为 `profile: stdio`。 +默认 `profile: local`(不启用容器)。开发者预写工具保持默认。 +**tool_manager 产出的工具自动写入 `sandbox: profile: stdio`**(见 tool_manager `spec_yaml`), +不受信任代码默认进容器。 ### 启用沙箱 @@ -121,9 +133,9 @@ enum ToolRunOutcome { |------|------| | tool_manager update 自身 | ❌ 拒绝(保留名) | | create_tool risk_level=low | spec 强制 high | -| datetime 本地执行 | +08:00(宿主时区) | -| datetime Docker 执行 | +00:00(容器 UTC,证明确实隔离) | -| 容器残留检查 | 无(--rm 生效) | +| tool_manager 产出工具 spec | 自动含 `sandbox: profile: stdio` | +| datetime 本地执行 | +08:00(宿主时区,默认不套容器) | +| tool_manager 产出工具 Docker 执行 | 容器内运行成功 | | builder 容器编译 serde_json | ✅ 离线成功 | | cargo test | 25 passed | @@ -138,6 +150,6 @@ enum ToolRunOutcome { | 5 | 修 clean_expired 并发 | ✅ | | 6 | 三类 Docker 镜像 + sandbox spec | ✅ | | 7 | stdio 工具迁移到一次性容器 | ✅ | -| 8 | network 工具迁移 | ⏸ 保持 stdio profile(工具走宿主代理联网,最小权限不给直接网络) | +| 8 | network 工具迁移 | ➖ 不需要:工具走宿主代理联网,子进程纯计算,套容器无隔离收益 | | 9 | tool_manager build 迁移到 builder 容器 | ✅ | | 10 | 结构化结果替换 is_tool_error | ✅ | diff --git a/src/tools/executor.rs b/src/tools/executor.rs index 025ff98..55373ee 100644 --- a/src/tools/executor.rs +++ b/src/tools/executor.rs @@ -379,10 +379,10 @@ fn build_local_cmd(spec: &ToolSpec, binary_path: &str) -> Command { } /// Docker 隔离执行:一次性容器,默认无网络、只读根、cap-drop ALL、 -/// no-new-privileges,仅挂载工具二进制(ro)+ tmpfs /tmp +/// no-new-privileges,仅挂载工具二进制(ro)+ tmpfs /tmp。 +/// 供 tool_manager 产出的不受信任工具使用(profile: stdio)。 fn build_docker_cmd(spec: &ToolSpec, binary_path: &str) -> Command { let image = match spec.sandbox.profile.as_str() { - "network" => "ias-network-runner", "builder" => "ias-tool-builder", _ => "ias-stdio-runner", }; diff --git a/src/tools/spec.rs b/src/tools/spec.rs index 2c6aca1..e45a734 100644 --- a/src/tools/spec.rs +++ b/src/tools/spec.rs @@ -17,7 +17,7 @@ pub struct ParamSpec { /// /// ```yaml /// sandbox: -/// profile: stdio # local | stdio | network | builder +/// profile: stdio # local | stdio | builder /// network: none # none | default /// memory: 256m /// cpus: "0.5" @@ -27,8 +27,8 @@ pub struct ParamSpec { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(default)] pub struct SandboxSpec { - /// 沙箱画像:`local` 本地执行(默认,不启用容器);`stdio` 无网络只读容器; - /// `network` 联网容器;`builder` 构建容器(供 tool_manager) + /// 沙箱画像:`local` 本地执行(默认,不启用容器);`stdio` 无网络只读容器 + /// (供 tool_manager 产出的不受信任工具);`builder` 构建容器(供 tool_manager) pub profile: String, /// 网络:`none`(默认)或 `default` pub network: String, diff --git a/tools/amap/specs/amap_geocode.tool.yaml b/tools/amap/specs/amap_geocode.tool.yaml index 0fcec20..2b5d263 100644 --- a/tools/amap/specs/amap_geocode.tool.yaml +++ b/tools/amap/specs/amap_geocode.tool.yaml @@ -16,5 +16,3 @@ params: required: false desc: 城市名称,可选。限定地理编码的城市范围 -sandbox: - profile: stdio diff --git a/tools/amap/specs/amap_map_link.tool.yaml b/tools/amap/specs/amap_map_link.tool.yaml index 50a7b13..fbfc541 100644 --- a/tools/amap/specs/amap_map_link.tool.yaml +++ b/tools/amap/specs/amap_map_link.tool.yaml @@ -13,5 +13,3 @@ params: required: false desc: 地图中心点经纬度坐标,格式"经度,纬度",默认天安门坐标 -sandbox: - profile: stdio diff --git a/tools/amap/specs/amap_poi_search.tool.yaml b/tools/amap/specs/amap_poi_search.tool.yaml index d2058bb..1bd4b01 100644 --- a/tools/amap/specs/amap_poi_search.tool.yaml +++ b/tools/amap/specs/amap_poi_search.tool.yaml @@ -19,5 +19,3 @@ params: required: false desc: 搜索半径(米),如 500、1000、3000。默认 1000 米 -sandbox: - profile: stdio diff --git a/tools/amap/specs/amap_reverse_geocode.tool.yaml b/tools/amap/specs/amap_reverse_geocode.tool.yaml index 62f066e..1632192 100644 --- a/tools/amap/specs/amap_reverse_geocode.tool.yaml +++ b/tools/amap/specs/amap_reverse_geocode.tool.yaml @@ -13,5 +13,3 @@ params: required: true desc: 经纬度坐标,格式为"经度,纬度",如"116.397,39.908" -sandbox: - profile: stdio diff --git a/tools/amap/specs/amap_route_plan.tool.yaml b/tools/amap/specs/amap_route_plan.tool.yaml index a8db3b9..18f5860 100644 --- a/tools/amap/specs/amap_route_plan.tool.yaml +++ b/tools/amap/specs/amap_route_plan.tool.yaml @@ -19,5 +19,3 @@ params: required: false desc: 出行方式:driving(驾车)、walking(步行)、riding(骑行)、transit(公交)。默认 driving -sandbox: - profile: stdio diff --git a/tools/amap/specs/amap_travel_plan.tool.yaml b/tools/amap/specs/amap_travel_plan.tool.yaml index 62d1874..58cb483 100644 --- a/tools/amap/specs/amap_travel_plan.tool.yaml +++ b/tools/amap/specs/amap_travel_plan.tool.yaml @@ -16,5 +16,3 @@ params: required: false desc: 感兴趣的关键词,逗号分隔,如"景点,美食,酒店"。默认"景点,美食" -sandbox: - profile: stdio diff --git a/tools/datetime/specs/get_current_datetime.tool.yaml b/tools/datetime/specs/get_current_datetime.tool.yaml index 6181c8d..547e9a0 100644 --- a/tools/datetime/specs/get_current_datetime.tool.yaml +++ b/tools/datetime/specs/get_current_datetime.tool.yaml @@ -7,5 +7,3 @@ risk_level: low timeout_secs: 3 params: [] -sandbox: - profile: stdio diff --git a/tools/fetch_page/specs/fetch_page.tool.yaml b/tools/fetch_page/specs/fetch_page.tool.yaml index 309dc2a..aa5ae69 100644 --- a/tools/fetch_page/specs/fetch_page.tool.yaml +++ b/tools/fetch_page/specs/fetch_page.tool.yaml @@ -10,5 +10,3 @@ params: required: true desc: 网页URL -sandbox: - profile: stdio diff --git a/tools/memories/specs/delete_memory.tool.yaml b/tools/memories/specs/delete_memory.tool.yaml index c56dfd0..ea05b1c 100644 --- a/tools/memories/specs/delete_memory.tool.yaml +++ b/tools/memories/specs/delete_memory.tool.yaml @@ -13,5 +13,3 @@ params: required: true desc: 记忆ID -sandbox: - profile: stdio diff --git a/tools/memories/specs/get_memory.tool.yaml b/tools/memories/specs/get_memory.tool.yaml index fcfc75a..c0cadb8 100644 --- a/tools/memories/specs/get_memory.tool.yaml +++ b/tools/memories/specs/get_memory.tool.yaml @@ -13,5 +13,3 @@ params: required: true desc: 记忆的数字ID,从 read_memories 返回结果中获取 -sandbox: - profile: stdio diff --git a/tools/memories/specs/read_memories.tool.yaml b/tools/memories/specs/read_memories.tool.yaml index 482a204..596a796 100644 --- a/tools/memories/specs/read_memories.tool.yaml +++ b/tools/memories/specs/read_memories.tool.yaml @@ -10,5 +10,3 @@ env: - IAS_MEMORY_DIR params: -sandbox: - profile: stdio diff --git a/tools/memories/specs/update_memory.tool.yaml b/tools/memories/specs/update_memory.tool.yaml index b92b2ee..2220471 100644 --- a/tools/memories/specs/update_memory.tool.yaml +++ b/tools/memories/specs/update_memory.tool.yaml @@ -16,5 +16,3 @@ params: required: true desc: 更新后的新内容,覆盖原记忆 -sandbox: - profile: stdio diff --git a/tools/memories/specs/write_memory.tool.yaml b/tools/memories/specs/write_memory.tool.yaml index 318ad56..aa7ea4c 100644 --- a/tools/memories/specs/write_memory.tool.yaml +++ b/tools/memories/specs/write_memory.tool.yaml @@ -13,5 +13,3 @@ params: required: true desc: 要记录的记忆内容,用自然语言描述。如"用户住在合肥包河区联投新安里A区" -sandbox: - profile: stdio diff --git a/tools/tool_manager/src/main.rs b/tools/tool_manager/src/main.rs index 6e26360..85feaab 100644 --- a/tools/tool_manager/src/main.rs +++ b/tools/tool_manager/src/main.rs @@ -269,6 +269,10 @@ fn spec_yaml(tool_name: &str, params: &Value) -> String { out.push_str("params: []\n"); } + // 安全约束:tool_manager 产出的工具不受信任(含 LLM 提供的 rust_code), + // 默认进 stdio 沙箱容器(无网络、只读根),防止数据外泄/越权文件读 + out.push_str("sandbox:\n profile: stdio\n"); + out } diff --git a/tools/weather/specs/weather_forecast.tool.yaml b/tools/weather/specs/weather_forecast.tool.yaml index dc9b7fb..45051c7 100644 --- a/tools/weather/specs/weather_forecast.tool.yaml +++ b/tools/weather/specs/weather_forecast.tool.yaml @@ -13,5 +13,3 @@ params: required: true desc: 城市名称,如"北京"、"上海"、"合肥"。支持地级市和县级市 -sandbox: - profile: stdio diff --git a/tools/weather/specs/weather_hourly.tool.yaml b/tools/weather/specs/weather_hourly.tool.yaml index 556b82a..c60cca5 100644 --- a/tools/weather/specs/weather_hourly.tool.yaml +++ b/tools/weather/specs/weather_hourly.tool.yaml @@ -13,5 +13,3 @@ params: required: true desc: 城市名称,如"北京"、"上海"、"合肥"。支持地级市和县级市 -sandbox: - profile: stdio diff --git a/tools/weather/specs/weather_now.tool.yaml b/tools/weather/specs/weather_now.tool.yaml index 0c93562..1331ab5 100644 --- a/tools/weather/specs/weather_now.tool.yaml +++ b/tools/weather/specs/weather_now.tool.yaml @@ -13,5 +13,3 @@ params: required: true desc: 城市名称,如"北京"、"上海"、"合肥"。支持地级市和县级市 -sandbox: - profile: stdio diff --git a/tools/web_search/specs/web_search.tool.yaml b/tools/web_search/specs/web_search.tool.yaml index 2f7e722..d4f2dda 100644 --- a/tools/web_search/specs/web_search.tool.yaml +++ b/tools/web_search/specs/web_search.tool.yaml @@ -14,5 +14,3 @@ params: - name: max_results desc: 返回的最大搜索结果数量(1-20),默认5条 -sandbox: - profile: stdio