把 Claude 关进确定性边界:从文件系统、网络出口到执行沙箱

2026-07-22 30 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

当 AI 从聊天窗口走向网页操作、代码执行和办公协作,安全问题就不再只是“模型会说什么”,而是“模型实际上能碰到什么”。Anthropic 对 Claude 多类产品的隔离架构进行了说明,其中最重要的工程判断是:代理安全不能主要依赖权限弹窗、提示词约束或事后检测,而应由文件系统、网络和执行环境中的确定性限制兜底。

这类架构真正困难的地方,也不是把代理放进一个容器就结束了。风险通常集中在信任边界,以及那些业务上必须保留的出口:共享目录、浏览器会话、网络代理、凭据注入和宿主机接口。

权限提示不是安全边界

权限确认仍然有价值,但它解决的是交互和审计问题,不适合充当最后一道隔离层。

代理可能处理网页、邮件、文档和代码仓库中的不可信内容。攻击者可以把指令藏在这些内容里,诱导模型申请一个看似合理的操作。即使每次操作都弹窗,用户也可能因为上下文不足或频繁确认而批准危险请求。

确定性边界采用不同的思路:即使代理决定执行错误操作,操作系统和基础设施仍然拒绝越界。例如:

  • 代理只能读写指定工作目录,无法读取用户主目录和 SSH 密钥。
  • 运行环境没有宿主机 Docker Socket,不能借容器管理接口逃逸。
  • 网络默认关闭,确有需要时只能经过受控出口。
  • 进程没有额外 Linux capabilities,也不能通过 setuid 提权。
  • 临时凭据只具备当前任务所需权限,并在任务结束后失效。

这里的重点是“不可由模型协商”。提示词可以要求模型不要读取某个文件,而只读挂载会让读取或修改在系统调用层面直接失败。

三个需要单独设计的控制面

文件系统:把可见范围缩到任务本身

代码代理往往需要读取仓库、生成构建产物并执行测试,但这不意味着它需要访问整个开发机。工作区应显式挂载;源码可以只读,输出目录则单独开放写权限。

还要警惕看起来普通的共享路径。Git 配置、包管理器配置、云端凭据、SSH agent socket 和浏览器配置都可能把宿主机信任带入沙箱。符号链接、挂载点以及构建缓存也应纳入检查。

执行环境:假设生成的代码不可信

只限制 shell 命令名称并不可靠。Python、Node.js、编译器和构建工具本身就能读文件、创建进程和发起网络请求。更稳妥的做法是在进程和内核边界实施约束,包括非 root 用户、能力集裁剪、资源配额、系统调用过滤和短生命周期实例。

这也意味着“允许测试”实际上是在允许执行仓库中的代码。来自拉取请求的测试脚本、安装钩子或构建插件,都应按不可信程序处理。

网络:最危险的往往是允许的出口

完全断网最容易推理,但很多代理确实需要访问包仓库、内部 API 或网页。问题随即从“有没有网络”变成“允许访问哪里、使用什么身份、能发送哪些数据”。

一个仅按域名放行的代理仍可能受到 DNS 重绑定、重定向、子域接管和内容托管服务滥用的影响。允许访问公共代码托管或对象存储平台,也可能同时提供数据外传通道。因此,出口策略需要同时考虑目标、协议、重定向、请求方法、响应大小、身份和审计,而不是只维护一张域名列表。

可以这样实践:启动一个默认断网的代码沙箱

下面是一个可直接改造的 Docker 示例。假设当前目录是待处理的代码仓库,代理或测试命令通过容器内的 sh 执行。运行前把镜像替换为团队审核过、固定 digest 的工具镜像。

docker run --rm -it \
  --name agent-sandbox \
  --network none \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=256m \
  --mount type=bind,src="$PWD",dst=/workspace,readonly \
  --mount type=volume,src=agent-output,dst=/output \
  --workdir /workspace \
  --user 65534:65534 \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --pids-limit 256 \
  --memory 1g \
  --cpus 1.0 \
  python:3.12-slim \
  sh

这个命令建立了几条硬边界:容器没有网络;仓库只读;结果只能写入 /output;根文件系统只读;进程以非 root 用户运行;额外 capabilities 被移除;进程数、内存和 CPU 均有限制。

进入容器后可以验证限制是否真的生效:

python - <<'PY'
from pathlib import Path
from urllib.request import urlopen

checks = []

try:
    Path('/workspace/should-fail.txt').write_text('x')
    checks.append(('workspace is read-only', False))
except OSError:
    checks.append(('workspace is read-only', True))

try:
    Path('/output/result.txt').write_text('sandbox output\n')
    checks.append(('output is writable', True))
except OSError:
    checks.append(('output is writable', False))

try:
    urlopen('https://example.com', timeout=2)
    checks.append(('network is blocked', False))
except Exception:
    checks.append(('network is blocked', True))

for name, passed in checks:
    print(('PASS' if passed else 'FAIL'), name)

if not all(passed for _, passed in checks):
    raise SystemExit(1)
PY

这是最小示例,不等同于生产级隔离。生产环境还应固定镜像摘要,配置 seccomp 或更强的沙箱运行时,限制输出容量,清理任务卷,并确认没有挂载 /var/run/docker.sock、宿主机凭据或其他管理接口。

若任务必须联网,可以这样实践:让代理容器只连接内部网络,并把唯一可路由出口交给独立代理服务。出口服务应解析并固定目标地址、拒绝私网和云元数据地址、重新校验重定向,同时按任务身份记录请求。不要把长期 API Key 写入代理可读取的环境变量;更合适的方式是由出口服务持有凭据,并为经过策略检查的请求代签或注入短期令牌。

从失败路径反推设计

Anthropic 的经验强调了一个容易被忽视的事实:架构图上的隔离区域可能很清晰,真实漏洞却经常出现在区域之间。评审代理系统时,应沿数据和权限实际流动的方向检查:

  • 工作区是否包含指向边界外部的符号链接或敏感配置?
  • 浏览器自动化是否复用了带有高权限登录态的用户配置?
  • 允许访问的网络目标能否重定向到内网或元数据服务?
  • 构建工具能否通过插件、安装脚本或缓存执行额外代码?
  • 日志和产物是否可能携带密钥、个人数据或完整文档?
  • 退出沙箱的结果是否会被另一个高权限系统自动执行?

隔离设计不能只检查“代理入口”,还要检查每一条被允许的出口。出口越通用,策略越难验证;权限越长期,单次失误的影响越大。

落地时的取舍

更严格的边界会牺牲部分便利。断网环境无法临时安装依赖,只读仓库会改变部分构建流程,短期凭据和出口代理也增加平台复杂度。但这些成本换来的是可测试、可审计的安全属性。

适合团队采用的顺序是:先默认拒绝文件、网络和执行权限,再按具体任务逐项开放;把每项开放权限写成机器可执行的策略;为信任边界和允许出口建立回归测试;最后再用权限提示和模型侧防护改善交互。模型防护可以降低错误操作概率,而确定性边界负责限制错误真正发生时的破坏范围。


相关推荐