实战:路径与工作流

Agent 沙箱实战:从本地到生产的安全边界

更新 原创整合
标签
sandboxsecurityagent-deploymentplaybooks

为什么需要这篇指南

Anthropic 公开了自己的沙箱架构和安全事件(参见 Anthropic 沙箱架构解读),核心教训是:标准隔离组件(gVisor、seccomp、hypervisor)扛住了考验,出问题的都是自己写的组件

但那篇文章解决的是"理解 Anthropic 怎么做"。你面对的问题是:我的项目该怎么做?

这篇指南按场景给出方案,附带配置和命令,能直接用。


先选方案:决策流程图

你的 Agent 在哪运行?执行什么级别的代码?
│
├─ 本地开发者机器,需要 shell / 文件系统
│  → Seatbelt (macOS) / Bubblewrap (Linux)
│
├─ CI/CD 流水线,执行 PR 中的代码
│  → Docker 容器 + 可选 gVisor
│
├─ 生产环境,面向非技术用户
│  → VM 隔离
│
└─ 纯 API 调用,不执行用户代码
   → 最小沙箱(key 隔离 + 限流)

一条原则:隔离强度匹配用户的监督能力。开发者能看懂 bash,用轻量沙箱。非技术用户看不懂,上 VM。


场景一:本地开发 Agent

Claude Code 用的是这个方案。Anthropic 开源了实现:srt

安装 srt

# macOS
brew install anthropic-experimental/tap/srt

# Linux (Ubuntu/Debian) — 从源码构建
sudo apt install bubblewrap
git clone https://github.com/anthropic-experimental/sandbox-runtime.git
cd sandbox-runtime && cargo build --release

macOS Seatbelt profile 示例

Seatbelt 通过 profile 文件定义权限。以下只允许读写工作区:

; sandbox-profile.sb
(version 1)
(deny default)
(allow file-read* (subpath "/usr/lib") (subpath "/System"))
(allow file-read* file-write* (subpath "/Users/you/projects/my-agent"))
(deny file-read* (subpath "/Users/you/.ssh"))
(deny file-read* (subpath "/Users/you/.aws"))
(deny file-read* (subpath "/Users/you/.gnupg"))
(deny network*)
sandbox-exec -f sandbox-profile.sb your-agent-binary

Linux Bubblewrap 示例

bwrap \
  --ro-bind /usr /usr --ro-bind /lib /lib --ro-bind /lib64 /lib64 \
  --bind /home/user/projects/my-agent /workspace \
  --unshare-net --die-with-parent \
  -- /usr/bin/python3 /workspace/agent.py

关键参数:--unshare-net 禁止网络,--ro-bind 只读挂载系统目录,--die-with-parent 父进程退出时自动清理。


场景二:CI/CD 流水线

Agent 在 CI 中执行 PR 里的代码(代码审查、自动修复、测试生成),是高风险场景——PR 里的代码可能来自任何贡献者。

Docker + 资源限制

FROM python:3.12-slim
RUN pip install --no-cache-dir your-agent-sdk
RUN useradd -m -s /bin/bash agent
USER agent
WORKDIR /workspace
ENTRYPOINT ["python", "-m", "your_agent"]
# docker-compose.ci.yml
services:
  agent:
    build: .
    read_only: true
    tmpfs:
      - /tmp:size=100M
    mem_limit: 512m
    cpus: 1.0
    pids_limit: 64
    network_mode: none
    security_opt:
      - no-new-privileges:true
    environment:
      - AGENT_SESSION_ID=${BUILD_ID}
      - LLM_API_KEY=${SCOPED_API_KEY}
    volumes:
      - ./repo:/workspace:ro
BUILD_ID=$GITHUB_RUN_ID SCOPED_API_KEY=$AGENT_KEY \
  docker compose -f docker-compose.ci.yml up --abort-on-container-exit

需要更强隔离:加 gVisor

gVisor 在用户态实现 Linux 内核,容器内进程无法接触真正的内核。适合你不信任 PR 作者的场景。

# 安装 runsc 后,用 gVisor 运行容器
docker run --runtime=runsc --network=none --read-only \
  -e LLM_API_KEY=$KEY your-agent-image

场景三:生产环境 Agent

面向非技术用户的产品(类似 Claude Cowork),用户无法判断 Agent 执行命令的风险。方案:VM 隔离

平台 方案 示例命令
macOS Apple Virtualization Framework 通过 VZVirtualMachineConfiguration 配置,只挂载工作区
Windows HCS (Host Compute Service) docker run --isolation=process --network=none -v C:\ws:C:\ws:RO ...
云端 Firecracker / Kata Containers microVM,启动时间 <125ms,适合高密度部署

VM 模式的关键设计

  1. Credential 永远不进 VM —— 宿主机持有 API key,VM 只拿 scoped token
  2. 网络出口白名单按 API endpoint 粒度配置,不是按域名(Anthropic 的教训:api.anthropic.com 上有文件上传 API)
  3. Agent loop 跑在 VM 外,只把代码执行放进 VM。VM 启动失败不影响 Agent 整体可用性

场景四:API 调用型 Agent(无执行环境)

Agent 只调用 LLM API 和外部工具 API,不执行任意代码。很多 RAG Agent 和工作流 Agent 属于这类。风险最低,但别跳过 key 隔离和输出过滤。

# 1. Key 隔离:每个会话用 scoped token
token = get_scoped_token(user_id=session.user_id,
    allowed_models=["gpt-4o-mini"], max_tokens_per_day=100000, expires_in=3600)

# 2. 输出过滤:拦截敏感模式
import re
SENSITIVE_PATTERNS = [
    r'(?:api[_-]?key|token|secret)\s*[=:]\s*\S+',
    r'\b\d{16}\b',
    r'-----BEGIN (?:RSA |EC )?PRIVATE KEY-----',
]
def filter_output(text: str) -> str:
    for p in SENSITIVE_PATTERNS:
        text = re.sub(p, '[REDACTED]', text)
    return text

# 3. 限流
@limits(calls=60, period=60)
def call_llm(prompt: str) -> str: ...

Credential 隔离检查清单

对任何场景都适用:

  • API key 不写入代码仓库(.gitignore 包含 .env
  • Agent 进程只持有 scoped token,不持有长期 key
  • Token 有过期时间(不超过会话时长)
  • ~/.ssh~/.aws~/.gnupg~/.config/gcloud 在沙箱外或不可读
  • 环境变量注入 token 而非配置文件(减少 Agent 主动读取的机会)
  • 数据库连接使用只读账号或限定 schema 的账号
  • 日志中不打印 token(确认 logging 配置 redact 敏感字段)

测试你的沙箱:验证边界是否生效

部署前跑一遍。每一条都应该失败才对。

# 测试 1:网络出口(应该被拒绝)
curl -m 5 https://httpbin.org/ip 2>&1 | grep -q "refused\|unreachable" && echo "PASS" || echo "FAIL: 网络未隔离"

# 测试 2:读取 credential(应该被拒绝)
cat ~/.ssh/id_rsa 2>&1 | grep -q "denied\|No such" && echo "PASS" || echo "FAIL: credential 可读"

# 测试 3:写入禁区(应该被拒绝)
touch /etc/pwned 2>&1 | grep -q "denied\|read-only" && echo "PASS" || echo "FAIL: 可写系统目录"

# 测试 4:资源爆炸(应该被限制)
python3 -c "x='A'*10**9" 2>&1 | grep -q "MemoryError\|killed" && echo "PASS" || echo "FAIL: 无内存限制"

# 测试 5:fork bomb(在 pids_limit=64 的容器内应该快速失败)

常见错误

错误 1:Symlink 绕过文件系统边界。 攻击者创建指向沙箱外的 symlink(ln -s /etc/passwd /workspace/fake),沙箱先检查路径再解析 symlink 就会漏。修复:路径校验在 realpath() 之后。srt 已处理。

错误 2:Allowlist 按域名而非 endpoint 粒度。 Anthropic 的 api.anthropic.com 事件:域名 allowlist 放行了文件上传 API。修复:出口控制做到 域名 + path prefix 粒度,或用 MITM proxy 检查请求内容。

错误 3:项目配置在信任确认前执行。 Claude Code 的 .claude/settings.json hook 事件。修复:所有项目级配置的解析推迟到用户显式确认之后。

错误 4:容器用 root 运行。 Dockerfile 里必须有 RUN useradd -m agent && USER agent。以 root 运行意味着容器逃逸直接拿到宿主机 root。


开源工具速查

工具 用途 平台 适合场景
srt Seatbelt/Bubblewrap 封装 macOS + Linux 本地开发 Agent
Docker 容器隔离 全平台 CI/CD、云端执行
gVisor 用户态内核 Linux 不信任代码来源
nsjail Linux namespace 沙箱 Linux 红队、细粒度控制
Firecracker 轻量 VM Linux 高密度生产部署
Kata Containers 容器 + VM 混合 Linux K8s 强隔离