controlplaneio/sandbox-probe

GitHub: controlplaneio/sandbox-probe

一个用静态 Go 二进制实测 AI 编程 agent 沙箱真实边界的探测工具,通过基线与沙箱报告的对比揭示实际隔离能力。

Stars: 22 | Forks: 2

# Sandbox Probe “我信任这个 sandbox 吗?”——这是一个基于信仰的问题,而信仰是威胁模型糟糕的基石。每个 AI 编程 agent 在发布时都会讲述一段关于其 sandbox 能做什么和不能做什么的故事:容器策略、[Landlock](https://landlock.io/) 规则、seccomp 过滤器,“我们只允许从工作区读取”。这个故事是供应商的“地图”。而你要保卫的是真实的“领土”:开发者的笔记本电脑,里面有 dotfiles、SSH agent、云凭证,以及一个可能会被足够巧妙的 prompt injection 诱导而“四处乱逛”的 agent。 `sandbox-probe` 是一个单一的静态 Go 二进制文件,你可以把它直接丢*进* sandbox——[Claude Code](https://code.claude.com/docs/en/overview)、[Gemini CLI](https://geminicli.com/)、[`nono`](https://github.com/always-further/nono)、某个容器,任何环境都行——让它四处看看。先在裸机上运行一次以获取基线,然后再在 agent 内部运行一次。两份报告之间的差异就是 sandbox 的实际边界,这是测量出来的,而不是假设出来的。如果结果显示该 agent 能够读取 `~/.aws/credentials`、解析任意的 DNS,或者能够访问 `169.254.169.254`,那么在你通过它发布下一行代码之前,请先收紧安全策略。 ## 目录 - [适用人群](#who-this-is-for) - [工作原理](#how-it-works) - [检测内容](#what-it-detects) - [解读报告](#reading-a-report) - [快速开始](#quick-start) - [测试 agent 的 sandbox](#testing-an-agents-sandbox) - [CLI 参考](#cli-reference) - [报告格式](#report-format) - [支持的代码助手](#supported-code-assistants) - [安装说明](#installation) - [开发指南](#development) ## 适用人群 `sandbox-probe` 旨在回答四个具体场景: - **评估 AI 编程 agent 的影响范围。** 你允许开发者使用 Claude Code、Gemini CLI 或类似工具;你想要一份具体的清单,列出被攻陷的 agent 在其 sandbox 内部能够读取、写入或访问哪些内容。 - **调整 sandbox 策略。** 你正在编写 Landlock 规则(通过 [`nono`](https://github.com/always-further/nono))、seccomp profile 或容器策略,并且需要添加规则前后的证据,证明你添加的规则确实关上了你想要关的门。 - **检测 sandbox 随时间的退化。** 在 agent(或你的封装程序)的每次发布中,在其 sandbox 内运行探测程序,并在边界扩大时发出警报——例如,新版本的 agent 开始能看到 `~/.aws/credentials`。 - **在同等条件下横向对比 sandbox。** 将相同的探测程序应用于 Claude Code、Gemini、`nono` 策略和 Docker 容器;生成的报告可以直接进行对比,因为使用的方法论是完全相同的。 ## 工作原理 一次扫描就是一个单一的静态 Go 二进制文件,它运行一个*任务*(task)注册表——每个任务尝试一类操作(读取敏感路径、扫描端口、对 sandbox 运行时进行指纹识别……),并记录内核允许它执行的操作。任务被分组为*任务集*(`baseline`、`ps`、`all`)。输出是一份包含各项发现的 JSON 报告。 预期的工作流程是进行对比,而不是绝对测量: ``` flowchart LR Probe([sandbox-probe]) --> Host[Run unconfined
on the host] Probe --> Sandbox[Run inside
the agent's sandbox] Host --> BR[baseline.json] Sandbox --> SR[sandbox.json] BR --> Diff{diff} SR --> Diff Diff --> Out[Sandbox boundary
= everything the
sandbox blocked] ``` 在单次扫描内部,编排流程如下所示: ``` flowchart TB CLI[sandbox-probe scan
--tasksets baseline] --> Reg[Task registry
pkg/tasks/tasks.go] Reg --> Tasks[Run each task
sequentially] Tasks --> F[Findings
finding_type + value] F --> Report[report.json
+ logs/sandbox-probe-*.log] ``` `baseline` 任务集中的 9 个任务: - `baseline_path_task` - `baseline_network_task` - `baseline_proxy_task` - `baseline_socket_task` - `baseline_process_task` - `baseline_user_context_task` - `baseline_hostname_task` - `baseline_sandbox_task` - `baseline_mount_task` `ps` 任务集增加了 `ps_all_task`、`ps_parent_task` 和 `ps_single_task`。任务按顺序确定性地运行。每个任务返回零个或多个带有稳定 `finding_type` 字符串的 `Finding` 对象,这使得基线与 sandbox 之间的差异对比变得有意义。 ## 检测内容 下表中的每一行都是你在 `report.json` 中会看到的 `finding_type` 字符串,以及它捕获的内容和它所回答的安全问题。完整列表位于 [`pkg/tasks/tasks.go`](./pkg/tasks/tasks.go) 中。 | JSON 中的 `finding_type` | 捕获的内容 | 回答的问题 | | --- | --- | --- | | `sensitive_readable_paths` | 可读但具有安全敏感性的文件(SSH 密钥、云凭证、浏览器 cookie 等) | “攻击者能否窃取我的 AWS 密钥、SSH 密钥或浏览器 cookie?” | | `writeable_paths` | 可写的系统路径和家目录路径 | “攻击者能否篡改 shell rc 文件、cron 或 systemd units?” | | `external_host_dns_resolution` | 探测程序能够解析的主机名 | “agent 到底能不能访问公共 DNS?” | | `external_host_connectivity` | 探测程序能够通过网络访问的主机 | “攻击者能否将数据泄露到外部服务器?” | | `tcp_ports_open` / `udp_ports_open` | 本地可达的端口 | “agent 暴露给了哪些本地服务?” | | `proxy_detection` | 环境变量中的代理配置 | “流量是否被强制通过一个 agent 可能会破坏的代理?” | | `unix_socket_detection` | 从内部可见的 Unix domain sockets | “agent 能否与 Docker daemon、SSH agent、dbus 等通信?” | | `process_detection` / `parent_process_detection` | 可见的进程及其启动父进程 | “在同一上下文中还运行着什么,以及是谁启动了该 agent?” | | `mounted_volumes_detections` | 从内部可见的已挂载文件系统 | “宿主机的文件系统暴露了哪些内容?” | | `user_context_detection` | UID、GID、EUID、EGID | “该 agent 是否以特权用户身份运行?” | | `hostname_detection` | 系统 hostname | “sandbox 是否泄露了宿主机身份?” | | `sandbox_detection` | 检测到的运行时(Docker、Podman、LXC、Firejail、Bubblewrap、gVisor、WSL、OpenVZ、Seatbelt、Landlock) | “到底有没有任何强制限制?属于哪种类型?” | ## 解读报告 一项发现如下所示: ``` { "findingType": "sensitive_readable_paths", "task": "baseline_path_task", "description": "Sensitive readable paths", "value": [ "/home/alice/.aws/credentials", "/home/alice/.ssh/id_ed25519" ] } ``` 将其作为一个三步链条来解读:*此发现意味着 X → 告诉了你 Y → 建议采取行动 Z。* - **`sensitive_readable_paths` 包含了 `~/.aws/credentials`** → 该 agent 的 sandbox 没有阻止读取云凭证 → 收紧 sandbox 策略(或者明确接受剩余风险,并留下书面记录)。 - **`external_host_connectivity` 包含了 `169.254.169.254`** → 该 agent 可以访问云实例元数据服务 → 如果你运行在 EC2/GCE 上,这就是一条窃取 IAM 凭证的路径;请阻止访问 link-local 地址的出口流量。 - **`sandbox_detection` 为空** → 未检测到任何限制 → 要么是探测程序尚未对该运行时进行指纹识别(请提交 issue),要么是确实没有 sandbox。 将基线报告与 sandbox 报告进行差异对比的意义在于:让这些决策基于证据,每一行差异都是 sandbox 实际拒绝的一项能力。 ## 快速开始 ``` # 构建 (Go 1.25+) make build # 在此主机上以 unconfined 模式运行以获取 baseline ./bin/sandbox-probe scan --output_path baseline.json # 检查 findings jq '.findings | map({findingType, task})' baseline.json ``` 然后在 agent 的 sandbox 内部运行相同的二进制文件,并对比两份报告——请参阅[测试 agent 的 sandbox](#testing-an-agents-sandbox)。 ## 测试 agent 的 sandbox 核心模式位于 [`tests/`](./tests) 目录中:每个 agent 有两个并行的脚本,一个在不受限的环境中运行探测程序,另一个在 agent 的 sandbox 内运行。 ``` tests/ ├── baseline_nono.sh # probe runs under a permissive nono policy ├── baseline_claude.sh # probe runs unconfined; Claude Code is just the runner ├── baseline_gemini.sh # ... same for Gemini ├── sandbox_nono.sh # probe runs under a restrictive nono policy ├── sandbox_claude.sh # Claude Code runs the probe inside its real sandbox ├── sandbox_gemini.sh # ... same for Gemini ├── detect_docker.sh # probe runs inside Docker ├── detect_podman.sh # probe runs inside Podman └── detect_bwrap.sh # probe runs inside Bubblewrap ``` 报告会生成在 `./reports/` 目录下。一个典型的对比工作流如下: ``` ./tests/baseline_claude.sh ./tests/sandbox_claude.sh diff <(jq -S . reports/baseline-claude.json) \ <(jq -S . reports/sandbox-claude.json) ``` [`nono`](https://github.com/always-further/nono) 在本仓库中扮演两个角色:它是一个 Landlock (Linux) / Seatbelt (macOS) 封装工具,能够将 sandbox 策略应用于任何二进制文件,因此我们将其用作 (a) 一个在已知策略下运行探测程序的*测试框架*,以及 (b) *我们正在分析其限制能力的 sandbox 之一*。这两个 `nono` 脚本演示了这两种模式。 有关更多详细信息,请参阅 [`docs/CONTRIBUTING.md`](./docs/CONTRIBUTING.md#trialing-against-agent-sandboxes)。 ## CLI 参考 ### `scan` 运行已配置的任务并输出 JSON 报告。 ``` ./bin/sandbox-probe scan [flags] ``` | 标志 | 描述 | 默认值 | | --- | --- | --- | | `--tasksets` | 要运行的逗号分隔的任务集:`baseline`、`ps`、`all` | `baseline` | | `--tasks` | 要运行的额外单个任务(逗号分隔) | _无_ | | `--output_path` | 写入 JSON 报告的路径 | `report.json` | | `--tags` | 附加到报告的元数据标签(逗号分隔) | _无_ | | `--fast` | 跳过“可能安全”的路径以加快迭代速度 | `false` | 示例: ``` # 默认:baseline taskset 到 ./report.json ./bin/sandbox-probe scan # 多个 tasksets,并将 tags 写入 report metadata ./bin/sandbox-probe scan --tasksets baseline,ps --tags test,docker # 仅单个 named task ./bin/sandbox-probe scan --tasks baseline_network_task # 自定义输出路径 ./bin/sandbox-probe scan --output_path results.json ``` ### `tasks list` 列出每个已注册的任务及其描述。 ``` ./bin/sandbox-probe tasks list ``` 规范的任务列表(目前跨两个任务集共有 12 个任务)即为此命令的输出结果。 ### `version` 打印版本号、git commit 和构建日期。 ``` ./bin/sandbox-probe version ``` 示例输出: ``` version dev git commit 44f7a7bcd2d3ae4215de43dd4d893c3b24587f40 build date 2026-05-16T10:39:11Z ``` ## 报告格式 报告是一个 JSON 对象,包含以下顶级字段: - `version` — 报告 schema 版本(目前为 `1.0.0`) - `timestamp` — 扫描运行的时间 - `probeBinary` — Go 版本、操作系统、架构、静态标志、二进制版本、commit、构建日期 - `metadata` — 来自 `--tags` 的用户提供的标签 - `findings` — `Finding` 对象数组(见下文) 每个 `Finding` 包含四个字段,定义于 [`api/proto/report/v1/report.proto`](./api/proto/report/v1/report.proto): - `findingType` — 稳定的字符串键(参见[检测内容](#what-it-detects));这是你要进行差异对比的字段 - `task` — 产生该发现的是哪个任务(例如 `baseline_path_task`) - `description` — 人类可读的标签 - `value` — 实际数据;形状取决于 `findingType`(字符串、字符串列表、整数列表,或者是用于进程 / 用户身份 / 代理配置的结构化对象) 报告片段示例: ``` { "version": "1.0.0", "timestamp": "2026-05-16T15:30:45Z", "probeBinary": { "goVersion": "go1.25.0", "os": "linux", "arch": "amd64", "static": false }, "metadata": { "tags": ["claude-code", "sandbox-run"] }, "findings": [ { "findingType": "sandbox_detection", "task": "baseline_sandbox_task", "description": "Sandbox/container runtime", "value": "landlock" }, { "findingType": "sensitive_readable_paths", "task": "baseline_path_task", "description": "Sensitive readable paths", "value": ["/home/alice/.ssh/id_ed25519"] } ] } ``` 扫描过程中的控制台输出是结构化日志;相同的数据也会写入 `logs/` 目录下带有时间戳的日志文件中(例如 `logs/sandbox-probe-2026-05-16-15-30-45.log`)。 ## 支持的代码助手 包含的测试脚本针对: - **[Claude Code](https://code.claude.com/docs/en/overview)** — 参见 `tests/baseline_claude.sh`、`tests/sandbox_claude.sh` - **[Gemini CLI](https://geminicli.com/)** — 参见 `tests/baseline_gemini.sh`、`tests/sandbox_gemini.sh`(以及 `*_interactive.sh` 变体) 任何会运行任意二进制文件的 AI agent 原则上都可以工作——该探测程序并不依赖于具体的 agent。欢迎贡献针对其他 agent 的测试脚本。 ## 安装说明 ### 前置条件 构建所需: - Go 1.25 或更高版本 - Protocol Buffer 编译器 (`buf`) — 通过 `make install-buf` 安装(仅在修改 protobuf 定义时需要) 用于端到端测试(取决于你想测试哪些 sandbox): - `jq` — 用于解析报告的 JSON 处理器 - `docker` 和/或 `podman` — 用于容器化测试 - `claude-code` — 用于 Claude 测试的 Claude Code CLI - `gemini-cli` — 用于 Gemini 测试的 Gemini CLI - [`nono`](https://github.com/always-further/nono) — 用于 AI agent 和其他程序的 Landlock/Seatbelt 封装工具 ### 从源码构建 ``` git clone https://github.com/controlplaneio/sandbox-probe.git cd sandbox-probe make build ``` 如果你打算在容器内运行 `sandbox-probe`,请确保它使用标准库路径进行静态构建,或者安排挂载相关的路径。这通常不是问题,但在非 glibc 或非 FHS 系统(如 Alpine、NixOS 或任何通过 Nix 构建的系统)上可能会遇到麻烦。 ## 开发指南 完整的任务列表(也可以通过 `./bin/sandbox-probe tasks list` 获取): | 任务 | 描述 | | --- | --- | | `baseline_path_task` | 扫描文件系统以查找可写和敏感的可读路径 | | `baseline_network_task` | 扫描网络以进行 DNS 解析、连通性检查以及开放 TCP/UDP 端口检测 | | `baseline_proxy_task` | 从环境变量中检测代理配置 | | `baseline_socket_task` | 扫描文件系统以查找 Unix domain sockets | | `baseline_process_task | 检测运行中的进程及父进程信息 | | `baseline_user_context_task` | 检测用户和组上下文信息(UID、GID、EUID、EGID) | | `baseline_hostname_task` | 检测系统 hostname | | `baseline_sandbox_task` | 检测容器运行时和 sandbox 环境(Docker、Podman、LXC 等) | | `baseline_mount_task` | 检测宿主机挂载的卷和文件系统挂载情况 | | `ps_all_task` | 使用 `ps` 列出所有运行中的进程 | | `ps_parent_task` | 使用 `ps` 获取父进程信息 | | `ps_single_task` | 使用 `ps` 获取当前运行进程的信息 | 添加新任务只需要实现 `Task` 接口([`pkg/tasks/tasks.go`](./pkg/tasks/tasks.go))并在 `taskRegistry` 中注册它(如果合适的话,也可以在 `taskSetRegistry` 条目中注册)。新任务返回的发现必须在 `expectedTypes` 中注册相应的 `finding_type`,才能通过运行时验证。 有关完整的贡献者指南,请参阅 [`docs/CONTRIBUTING.md`](./docs/CONTRIBUTING.md)。
标签:AI代理安全, EVTX分析, Go, Homebrew安装, Ruby工具, Web截图, Web报告查看器, 容器安全, 插件系统, 无线安全, 日志审计, 沙箱逃逸, 环境探测, 请求拦截