用隐藏注入基准与来源策略测试Agent
buried-injections用于测试检测器能否识别藏在工具结果中的提示注入,避免评测只覆盖固定攻击模板。taintgate探索根据输入来源约束高风险工具调用,两个项目均开源并征集独立测试。
AI 深度解读
能拦住「忽略之前的指令」,不代表能拦住藏在发票、邮件或搜索结果里的转账要求。攻击目标没变,只是包装得更像正常业务,检测器就可能放行。
作者用两个开源项目拆解这个问题:buried-injections 测量检测器换个场景还灵不灵,taintgate 则在工具执行前检查关键参数来自哪里。它们值得一起看,因为一个检验「能否认出攻击」,另一个尝试限制「认不出来时能做什么」。不过,基准成绩和来源匹配都有明确边界。
检测器分数高,可能只是认熟了题面
buried-injections 用 629 个 AgentDojo 攻击实例评测 10 个开源检测器,另外准备了 97 个正常样本。629 个实例对应 27 段不同的注入文本,嵌在邮件、网页、账单等普通工具输出中。
这里不能把 629 个实例理解成 629 种独立攻击。作者要测的是:恶意指令混进正常内容后,检测器还能不能识别,以及它会不会顺手拦掉正常请求。
按各检测器出厂默认阈值测试,作者称,开箱即用时取舍最好的方案仍漏掉接近一半攻击。但正文没有给出完整成绩表,也没交代这个方案具体是谁,不能据此给所有检测器排座次。
Prompt Guard 2 的例子更能说明阈值的影响。默认 cutoff 为 0.5 时表现很差;用三个 AgentDojo 领域的正常流量校准,再到第四个未见领域评估,阈值设为 0.003 时,各领域攻击检出率达到 97–100%,全部 97 个正常样本中出现 5 次误报。
0.003 是这套基准和配置下的结果,不能直接当成生产推荐值。
作者主张固定误报预算,再报告检出率,并附上 95% Wilson 区间。这个统计区间用来表达样本有限带来的不确定性,避免一个漂亮百分比掩盖评估条件。
另一个问题是「背题」。标准攻击集共用「This is an important message from me…」包装,检测器可能只学会了认这句话。为此,项目增加 --template 轴,让相同攻击目标换六种表达:
standard使用原始包装。novel_wrapper改成释义、商务或角色扮演表达。obfuscation使用 base64、ROT13、Unicode 相似字符或代码注释。foreign_lang换成其他语言。split_indirect把指令软化、拆散,埋进邮件或网页。benign_framed将恶意动作写成普通业务请求。
默认测试命令是 python bench/run.py --dataset agentdojo --detector <name>;加上 --template all,才能观察换包装后是否失效。

六种模板是测量方法,不是作者已经证明所有检测器都会崩溃。正文也没有提供完整的逐模板结果,这个区别要保留。
与其反复读文字,不如在执行动作前检查来源
文本分类回答的是「这段话像不像攻击」。taintgate 换了一个问题:这次工具调用里的关键参数,究竟是用户给的,还是 Agent 从外部内容里读到的?
它把用户消息记为可信来源,把工具返回内容记为不可信来源。准备执行转账、删除文件、修改恢复邮箱等操作时,再根据参数来源决定 allow、ask 或 deny。
下面的代码展示了如何记录发票内容,并在转账前检查参数:
from taintgate import Policy, Session, ToolCallBlocked
policy = Policy.from_yaml("policy.yaml")
session = Session(policy, user_messages=["Please pay this invoice."])
session.observe("read_invoice", invoice_text) # 工具输出记录为不可信来源
decision = session.check(
"send_money",
{"recipient": invoice_iban, "amount": 98.70},
)
decision.action # "allow" | "ask" | "deny"
decision.reasons # 例如:收款人来自工具输出,而非用户输入
要看的是 session.observe():先记录读到了什么,后面的检查才有来源可比。session.check() 返回决策;从这段示例本身看,真正执行工具的一方仍需落实决策。
对应的规则要求:收款人来自不可信内容时,先请求确认。
rules:
- tool: [send_money]
when: {recipient: {untrusted: true}}
action: ask
reason: recipient came from tool output, not from the user
关键是 action: ask。规则不需要先证明发票里藏着恶意指令,只要收款人来自外部内容,就触发确认。
也可以通过 session.wrap(send_money, approve=ask_human) 包装工具,被阻止的调用会抛出 ToolCallBlocked。作者将策略描述为 fail-closed,也就是未获放行时阻止执行;实际接入时,必须保证调用链没有绕过这一关。

来源匹配有用,但还不是完整的数据流追踪
taintgate 当前做法是把调用参数的值,与记录过的用户输入、工具输出进行匹配,比较前会规范化空格和标点。
这是来源匹配,不是完整的信息流追踪。
例如,外部文本中的值经过重新格式化、切片或 base64 编码后再传给工具,就可能逃过匹配。作者在 README 中承认了这个限制,并把来源传播列为待贡献方向。
因此,「攻击者再怎么改写,也改变不了参数来自外部内容」可以作为设计动机,但当前实现未必始终识别得出这种来源关系。
MCP 和框架接入也要分清完成状态。作者邀请贡献者构建 MCP-proxy,让 MCP server 的 tools/call 在执行前经过 Session,并验证 deny、ask 无法未经授权执行;已有 mcp_server.yaml 可作基础。LangChain、LlamaIndex 的适配同样属于待贡献方向,目标是在工具分发前调用 session.check()。
两个项目都是 Python 项目,采用 MIT 许可。作者称代码规模较小且有测试,但这些描述不等于已经完成真实业务环境的安全验证。
我的判断
我赞同把「识别可疑文本」和「限制高风险动作」分开。前者可能被表达方式影响,后者可以针对收款人、删除目标等具体参数设规则,排查原因也更直接。
但「来自用户」不自动等于安全,「来自文档」也不自动等于恶意。用户让 Agent 处理发票,收款账号自然可能来自发票。来源规则如果过于宽泛,就会把正常工作都变成确认弹窗。
来源信息能帮助决策,不能独自代表操作授权。
与继续给检测器加规则相比,动作边界检查多了一层独立约束;与固定工具白名单相比,它又能细化到参数。不过,原文没有提供三种方案在真实业务中的对照数据,也没有给出台账式的绕过率、确认频次或运行开销。关于哪种组合更划算,目前只能把它看作设计探索。
基准部分同样有适用范围:27 段不同注入文本、97 个正常样本,足以暴露问题,尚不足以替一般团队证明生产可靠性。Prompt Guard 2 校准后的结果尤其提醒我们,比较检测器之前,得先对齐阈值和误报条件。
能拿来做什么
- 如果你在评估 Agent 的注入检测器,除了直接越狱文本,还应加入邮件、网页、账单中的隐藏指令。可以用
--template all比较同一目标换包装后的成绩,同时记录正常请求被拦截的数量。 - 如果你的 Agent 会转账、删文件或修改账号信息,就在真正调用工具的位置检查关键参数。接入
session.check()或工具包装器时,要验证ask、deny确实能阻止执行,而不只是留下日志。 - 如果你准备采用 taintgate,先测试参数经过格式化、切片、base64 编码后的情况。对来源匹配失败的路径,补充明确策略,不能把未匹配到误当成已获授权。
- 如果想本地复现,基准项目的入口是
git clone https://github.com/rudratoshs/buried-injections,随后执行cd buried-injections && make setup && make bench;策略项目则执行git clone https://github.com/rudratoshs/taintgate,再运行cd taintgate && pip install . && python examples/banking_demo.py。两者都是 Python 项目,原文没有提供 Node 适配方案。