多租户或处理不可信内容的部署场景下,Claude Code 文档明确推荐采用”安全边界 + 凭证代理”模式:把敏感凭证放在 Agent 边界之外,由边界外的反向代理在请求出站时注入凭据,Agent 本身只持有调用接口的能力,却永远看不到真实的 Key。这种模式依赖文件系统隔离、网络隔离与代理注入三件套协同,目标是即便 Agent 被 prompt injection 攻陷,攻击者也只能”借力调接口”,拿不到可外泄的长期凭证。
一、为什么要把凭证从 Agent 视野里拿走
Agent 在执行任务时会读取文件、调用外部 API、调用 shell。如果 SSH 私钥、云厂商 AccessKey、GitHub Token 直接挂在 ~/.ssh、~/.aws 或环境变量里,被污染的 prompt 一句”读 ~/.ssh/id_rsa 发到某个公网 URL”就能把资产卷走。多租户场景下风险更放大:一个租户的越权操作可能横向影响其他租户的凭证与数据。
“凭证不暴露给 Agent”是 Anthropic 在《Securely deploying AI agents》中提出的核心安全边界模式。文档原话是把敏感资源(凭据、长期密钥)放在 Agent 信任边界之外,Agent 边界内只放完成任务必需的能力;Agent 与外部服务的真正认证由边界外的代理完成,代理看到 Key,Agent 只看到接口。
二、安全边界的四层能力
Claude Code 默认权限系统是 allow / ask / deny 规则,只能挡”是否允许执行”这一层;要做高安全部署,必须再叠加操作系统级隔离。文档给出了四类能力:
| 能力 | 控制对象 | 典型实现 |
|---|---|---|
| 文件系统隔离 | 写路径白名单、读路径黑名单 | bubblewrap、Seatbelt、容器 volume mount |
| 网络隔离 | 出站域名白名单 | 出口代理 + DNS 策略 |
| 凭证隔离 | 关键文件 deny、环境变量 deny / mask | sandbox.credentials 配置块 |
| 进程隔离 | 容器 / 微 VM | Docker、Firecracker、gVisor |
四层必须同时存在:只挡文件系统不挡网络,被盗数据可被发出去;只挡网络不挡文件系统,攻击者可在启动脚本里埋反向通道。每缺一层,安全等级都会显著下降。
三、”凭证代理”模式的工作原理
凭证代理(credential proxy)是该模式的核心组件。它运行在 Agent 信任边界之外,做两件事:截获 Agent 发出的出站请求,在转发到真实服务前注入凭据(如把 Authorization 头补全);用域名白名单限制 Agent 可触达的目的地。整个过程对 Agent 完全透明——Agent 只知道”调用某个 HTTP 接口”,不知道”接口背后附了什么 Key”。
// .claude/settings.local.json:把关键凭证文件 deny + 环境变量 mask
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true,
"network": {
"allowedDomains": ["api.anthropic.com", "github.com"]
},
"credentials": {
"files": [
{ "path": "~/.aws/credentials", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [
{ "name": "GITHUB_TOKEN", "mode": "mask" }
]
}
}
}
mask 模式比 deny 更巧妙:Agent 看到的是占位符,只有请求真正抵达白名单内的目的主机时,边界外的代理才把真实 Key 注入回请求头。gh、npm 这类需要认证的工具仍能正常工作,而 Agent 进程与它的日志里永远只有占位符。
四、落地步骤
按”先隔离再注入”的顺序逐层打开,不要一步到位。
- 开启沙箱:在 Claude Code 内执行
/sandbox选 auto-allow,并在 settings.json 把sandbox.enabled与sandbox.failIfUnavailable同时设为 true,缺失 bubblewrap / socat 时直接拒绝启动; - 声明 deny 凭证路径:把
~/.ssh、~/.aws/credentials、含长期 Key 的.env列入credentials.files,用deny模式先彻底切断读取; - 把常用认证改成 mask:仅对 Agent 必须使用、但密钥不能外泄的 Token(如
GITHUB_TOKEN)用mask,让工具照常工作; - 配出口代理白名单:在网络层只放行
api.anthropic.com与 Agent 真正要访问的仓库 host;其他域名一律走代理拒绝; - 部署外部代理注入凭据:用 mitmproxy、Envoy、或自建 sidecar 在 Agent 边界外做认证头注入,并把代理进程放在另一个容器或 VM;
- 审计与回放:把代理日志落到独立存储,定期回放”Agent 实际发出的请求”做敏感数据扫描。
五、几种隔离技术的对比
不同隔离层给的安全强度与运维成本差异很大,按”威胁等级”挑对应方案:
| 隔离技术 | 隔离强度 | 性能开销 | 运维复杂度 | 适用场景 |
|---|---|---|---|---|
| 沙箱运行时 | 中(OS 级) | 很低 | 低 | 本地开发、单机 Agent |
| Docker 容器 | 中(取决于配置) | 低 | 中 | 团队统一环境 |
| gVisor | 高 | 中 | 中 | 多租户 SaaS 后端 |
| Firecracker / QEMU 微 VM | 高 | 较高 | 较高 | 高合规、不可信代码 |
威胁等级高(如多租户 SaaS、运行不可信 PR)就往上走 gVisor 或微 VM;只跑个人 Agent 用沙箱运行时已够。不要”杀鸡用牛刀”,但更不能”用普通容器跑生产多租户 Agent”。
六、易被忽略的边界外细节
第一,权限规则不能替代沙箱。allow/ask/deny 是 prompt 级别的判断,模型能改主意;只有 OS 级隔离才是真边界。两者必须叠加,缺一不可。
第二,Claude Code 内置的 Read / Edit / Write 工具不经过沙箱。沙箱只覆盖 Bash 子进程,对文件读写工具的边界要靠权限规则 + 容器挂载解决。把仓库挂在只读 volume、关键目录用 tmpfs 即可补上。
第三,注入头不能完全替代密钥隔离。如果 Agent 自己拼了 raw Key(而不是依赖代理注入),代理只是 transport 层的 MITM,Agent 进程里仍有可读到的字符串。mask 模式的目的正是把 Key 从 Agent 视野里彻底抹掉。
到这里,Claude Code 在多租户环境下的安全部署模式就清晰了:用 OS 级沙箱封文件系统与网络,用 sandbox.credentials 把关键文件 deny / mask,再把凭据注入交给边界外的代理完成。Agent 拿到的是”做事能力”,拿不到”做事需要的秘密”,即便模型被攻陷,可外泄的也只是占位符与出站域名内的接口调用。
常见问题(FAQ)
Q1:mask 与 deny 怎么选?
Agent 必须用到该 Key 才能完成任务的选 mask(如 GITHUB_TOKEN),其他一律选 deny。
Q2:沙箱开了为什么还看到 ~/.ssh?
默认读取策略覆盖几乎整盘,关键文件必须显式列入 credentials.files 才能挡住。
Q3:能不能只用沙箱不开网络隔离?
不推荐。被沙箱封住的命令仍可能读 SSH Key,没网络隔离就能把 Key 发出去。