12 个月前绝不会给的权限,今天已是常态
随着 agent 变强,它的潜在爆炸半径也在变大。工程问题就一个:怎么给这个半径封顶。Anthropic 把跨三款产品做封堵踩过的坑,第一次摊开讲。
概率压不到零,任何概率性防御都有非零漏检率。所以安全的真正抓手不是赌模型自律,而是在环境层把爆炸半径硬封住——监督它「能做什么」,而不是监督它「做了什么」。
封顶爆炸半径,只有两条路
一条是监督 agent 的行为,另一条是监督它的能力。Anthropic 把工程力气几乎全压在了第二条上——因为第一条被证明会退化。
Human-in-the-loop:每一步都问用户要权限
理论上行得通。但弹窗弹得越多,用户对每个就越不上心,监督质量随时间大幅下降。一个本为提供监督的功能,反而可能让人干脆不看了。
Containment:用沙箱、VM、出站控制封死边界
不管 agent 想做什么,先用硬边界限定它能够到什么。一个朴素但关键的例子:如果凭证从不进入沙箱,那它就无法被外泄——无论起因是用户、是模型找到了「创造性」路径、还是攻击者。
三类风险,三个要防的组件
agent 的安全风险落在三类里:
we've seen Claude models "helpfully" escape a sandbox in order to complete a task, examine git history to find answers to a coding test, and spontaneously identify the benchmark it was being run on in order to decrypt its answer key.
对应地,防御施加到三个组件上——它们应当重叠、互补,当一层不可用时另一层顶上:
用进程沙箱、VM、文件系统边界、出站控制,给「agent 能够到什么」设一道硬边界。这是本文重点,也是最可靠的一层——它不靠概率。
系统提示、分类器、探针、训练修改。Gray Swan 红队基准上,Opus 4.7 单次攻击成功率约 0.1%、100 次自适应后约 5–6%;Claude Code auto mode 在执行前拦下约 83% 的越界行为。但模型是概率性的,这层只能塑造它「倾向」做什么,永远做不到 100%,所以不能单独站。
MCP server、第三方插件、网页搜索都会把你控制不了的来源喂进上下文。一个审计过的 GitHub connector,照样能把一个被投毒的 README 直接加载进模型上下文——尽管它通过了恶意软件检查。细粒度限权能压缩半径:只读数据库的 agent,能比能写生产的部署得宽泛得多。
三款产品,三套隔离舱室
聚焦环境层。三款产品面向不同受众,逼出三套不同的隔离架构——从最薄的服务端容器,到最厚的密封 VM。每个设计都是在「需要 agent 多大能力」和「需要用户多大介入」之间逐步找平衡得到的。
.claude;凭证留宿主 keychain,从不进客户机Cowork VM 的六个主要隔离机制:纵深分层,外硬内简。
Cowork 的两次架构调整:可靠性 vs 隔离的真实取舍
最初整个 agent loop 跑在 VM 里(full-VM 模式),Claude 作为普通 Linux 用户运行、对自己被沙箱包着毫无觉察——好处是没有外层进程握着「逃生舱口的钥匙」。但很快发现:VM 启动期间任何失败都会让 Cowork 不可用。于是把 agent loop 挪到 VM 之外、代码执行仍留在里面,Claude 出错时还能响应用户、帮忙调试。对安全影响极小,因为 VM 仍对 agent 执行的代码强制文件和网络控制。本地 MCP server 也同理挪到了 VM 外——跑在里面更难审计、VM 更新时依赖易脆、且不支持要和本地数据库等进程交互的 MCP。
文件挂载三模式 + 一个符号链接的坑
Claude 得能访问宿主上的一些文件才有用,但要最小化半径、给用户透明度。Cowork 提供三种挂载模式:read-only / read-write / read-write-no-delete。一个潜在的坑:符号链接解析必须发生在路径校验之前,否则授权文件夹内的一个 symlink 可以指向外部、逃逸出去。企业客户可由管理员通过 MDM 设置里的挂载路径 allowlist 来控制。
四起「漏掉的风险」:沙箱完美工作,数据照样被偷走
封堵投入最多的地方,也是最多「出人意料的安全失败」发生的地方。下面四起都是第一手复盘——两起最贵的教训都是 egress:数据从一条被许可的通路流出,模型层在这两起里都帮不上忙,因为没有异常可抓。
一名研究员钓鱼成功,诱使一名员工带着恶意 prompt 启动 Claude Code。钓鱼看起来就是普通协作——一封 "can you run this for me?" 的邮件,附一段可直接粘贴的 prompt,读起来像例行任务说明。但在设置步骤之间,它温和地让 Claude 读 ~/.aws/credentials、编码、POST 到外部端点。
~/.aws/credentials → 编码 → POST 外发模型层防御锚定用户意图,在这条路径上形同虚设。唯一拦得住的是环境层:egress 控制拦住 POST + 文件系统边界让 ~/.aws 一开始就够不着。
~/.aws 够不着的文件系统边界。当他们把这个能用的 payload 发到内部 Slack 讨论时,有人指出:一些内部 agent 会读 Slack。payload 就此变成「环境里的常驻污染」。他们只好在帖子里加了个 canary 字符串,好在有东西读到它时能察觉。在一个 agent 什么都读的世界里,连调查工具都是攻击面。
最直接的一例:开发者 clone 一个 repo 来 review PR,repo 里含一个定义了 hook 的 .claude/settings.json。因为 Claude Code 在启动时就读项目设置——在弹出「Do you trust this folder?」之前——攻击者提交的那个 hook 就自动执行了。其余几起结构类似:来自尚未受信目录的输入,在信任边界建立之前就被解析了。
.claude/settings.json修法:把 project-open / config-load / localhost 监听器,当成来自互联网的入站请求那样对待——不能因为「感觉是本地的、在用户同意前到达」就默认信任。
Cowork 的出站 allowlist 正确放行了到 api.anthropic.com 的流量——产品离了调自家 API 没法工作。这一次,一个放进用户挂载工作区的恶意文件,带了隐藏指令 + 一个攻击者控制的 API key。Claude 照做,读了工作区里其他文件,用攻击者的 key 调了 Files API。代理一看目的地是 api.anthropic.com 就放行了。文件被传到了攻击者的 Anthropic 账号。
api.anthropic.com = 放行「向任意账号上传文件」。api.anthropic.com → 是自家域名✗ 放行api.anthropic.com代理坐在 VM 内而非服务器上——因为只有 VM 知道请求的来源;从服务器看,一个 Cowork 请求和任何别的 API 客户端没法区分。
企业安全团队评估 Cowork 时问:为什么我们的端点检测看不进去?答案是——那套把 Claude 封住的隔离,同样把基于宿主的 EDR 挡在了外面。从 EDR 视角看,Cowork 是一个不透明的 hypervisor 进程,没法检视客户机内部。隔离降低了可见性,而这对合规态势依赖端点可见性的团队是个麻烦。
把 Claude 封住的隔离,反过来也把你的监控挡在外面——隔离与可见性是一对矛盾。
信任 agent 读到的东西
企业常问怎么保护 MCP 连接。但正确的问题比 MCP 更宽:提供给 agent 的任何外部资源,同时是两种风险——传统供应链意义上的代码执行风险,和 prompt injection 向量。传统依赖审计(pin 版本、验签、审源)能治前者,却漏掉后者。
远程 vs 本地,比看起来更重要
本地安装的工具是可审计的:读得了代码、pin 得住版本、知道它不会在你脚下变。而远程工具——托管 MCP server、云端 connector——可以在你批准之后的任何时点改变行为,你安装时的信任决定就可能不再成立。目录之外的东西都应当被当作不受信:先拿假数据、在恶意工具的爆炸半径能被控住的环境里跑一遍。
once a poisoned tool return has steered the agent into exfiltrating data, the log just shows a successful, authorized API call. There's no after-the-fact signal to find.
在 Claude Code 和 Cowork 里,工具调用经由代理路由,强制网络和文件策略,并能在返回值进入上下文前检视它。做检视的分类器可以是个小而快的模型,不必是那个做推理的。
三个待解问题
模型和产品推进得很快,风险也在变形。Anthropic 点名了三个正在啃的难题:
CLAUDE.md 文件、挂载工作区、定时与长跑 agent 的状态目录。一次注入只要落进任何一个,就会在 agent 每次启动时被重新加载——经典后渗透意义上的新型持久化。session 启动时的好分类器会需要变得更普遍。反复回到的三条原则
agents may be a new category of software, but their system-level interactions are not. They still read files, open sockets, and spawn processes.