Prompt Injection 的本质不是“坏字符串”,而是一个混淆代理(Confused Deputy)问题:模型获得了处理用户数据或代用户执行操作的权限,攻击者控制的内容却诱导它把这些权限用于另一个目的。

同一句注入指令在两个系统中的影响完全不同。只读摘要器可能生成错误摘要;能够读取私有邮件、上传文件、写入记忆、调用 MCP 工具并访问公网的 Agent,则可能泄露数据或造成持久化入侵。

因此,工程目标不应是“让模型永远不受操纵”,而应是:

即使不可信内容影响了模型,系统仍必须阻止未授权的数据访问、状态变更和外部影响。

本文从威胁模型出发构建这套架构。技术结论与来源最后核验于 2026 年 7 月 16 日。

核心结论

  • 所有模型输出都是不可信提议,包括 Tool Call 与生成代码。
  • 应分离可信指令与不可信数据,但不能把分隔符误当安全边界。
  • 认证、鉴权、参数校验、限流和政策执行必须位于确定性代码中。
  • 打破危险能力组合:不可信输入、敏感数据和外部通信通道不能无控制地同时存在。
  • 对派生数据持续追踪来源;不可信网页的摘要仍然受到该网页影响。
  • 用户确认必须绑定到展示过的精确操作和参数,不能只问一句“是否继续”。
  • 限制网络外发、跳转、URL、Markdown 渲染和其他隐蔽输出通道。
  • 同时使用正常任务与自适应重复攻击评测安全性,不能只跑静态 Payload。

什么是 Prompt Injection

OWASP LLM01:2025 将 Prompt Injection 定义为使模型行为或输出发生非预期变化的输入。这些输入可以可见或隐藏,可以直接来自用户,也可以通过外部内容传入。NIST AI 100-2 E2025 则将其纳入更完整的对抗机器学习分类体系。

更准确的描述并不是“系统文本和用户文本完全没有区别”。现代 API 确实提供角色与指令层级,模型也可以通过训练更可靠地遵循这些层级。但安全问题仍具有概率性:低信任文本依然可能影响决策,尤其当任务本身就要求解释这些文本时。

Prompt Injection 与 Jailbreak

  • **Prompt Injection:**改变应用行为。例如恶意邮件要求助手转发私有附件。
  • **Jailbreak:**绕过模型安全政策。例如用户诱导模型生成被禁止内容。

二者有重叠,但控制措施不同。模型安全训练对 Jailbreak 非常重要;Agent Prompt Injection 还需要传统应用安全,因为真正影响取决于模型外部的数据和工具。

直接与间接注入

直接注入通过用户可控请求进入,可能要求覆盖指令、提取秘密、越权调用工具或绕过业务政策。

间接注入藏在系统读取的数据中:

  • 网页、搜索结果、广告与链接元数据;
  • 邮件、日历、聊天消息、工单与文档;
  • 代码仓库、Issue 评论、构建日志与包元数据;
  • RAG Chunk 与被污染知识库;
  • Tool、MCP Server、Sub-agent 与 API 返回值;
  • 图片、音频、OCR Layer、不可见 DOM 与文件元数据;
  • 先前写入的 Memory 或 Summary。

间接注入难以处理,因为恶意内容往往也包含用户真正需要的信息。删除所有“像指令的句子”可能直接破坏任务。

完整攻击链

真正的安全事件通常不只需要一次指令覆盖:

text
攻击者可控来源
      |
      v
模型把内容解释为权威指令
      |
      v
Agent 访问能力或私有资产
      |
      v
未授权操作、数据披露或持久状态

防御者可以切断任意一条边:

  1. 阻止或标记攻击者可控内容;
  2. 降低其对计划阶段的权威性;
  3. 移除不必要能力;
  4. 按真实用户和资源鉴权每次操作;
  5. 阻断危险数据流与目标地址;
  6. 要求有意义的人工确认;
  7. 阻止不可信结果升级为可信 Memory。

模型鲁棒性只是其中一层,不是完整控制系统。

生产评测必须覆盖的攻击类型

指令与目标劫持

模型放弃用户任务、改变筛选标准、歪曲文档或遵循新目标。即使不调用工具,也会伤害排序、审核、招聘与推荐系统。

数据外泄

注入内容诱导 Agent 读取秘密,并通过以下通道传出:

  • Tool 参数或消息接收人;
  • 攻击者 URL 与 Query String;
  • 图片、Link Preview、Markdown 或 Redirect 请求;
  • 生成文件、表单、评论和公开帖子;
  • 其他租户可见的副作用。

只过滤自然语言输出无法覆盖这些非文本通道。

Tool 与 Agent 劫持

攻击使用合法 Tool 完成未授权目的、修改参数、触发重复操作,或把任务委托给权限更高的 Sub-agent。格式合法的 JSON Tool Call 仍可能是恶意调用。

RAG 与语料污染

攻击者植入容易在目标查询中排名靠前的内容,操纵回答、请求秘密或影响未来 Memory。检索相关性与内容可信度是两个独立维度。

持久化与延迟注入

Payload 把自己写进 Memory、项目指令、摘要、工作流状态或生成配置,并在后续会话中激活,此时最初来源已经不可见。

多模态与混淆注入

指令可以藏在图片、OCR 文本、音频、Unicode、编码块、空白字符、CSS 或元数据中。标准化可以暴露部分 Payload,但不能可靠判断语义意图。

Best-of-N 与自适应攻击

攻击者不断改变措辞、语言、编码、位置与多轮铺垫,直到某次成功。只通过一个静态 Payload 不代表安全。

控制措施之前先做威胁建模

每条工作流都应记录:

要素 需要回答的问题
资产 哪些私有数据、资金、凭证、声誉或持久状态可能受损?
Principal 哪个用户、租户、服务、Agent 或管理员身份在行动?
不可信来源 谁能控制网页、消息、文件、工具输出、Memory 或图片?
能力 哪个组件能够读、写、执行、发布、通信、购买或委托?
Sink 数据能从哪里离开:网络、邮件、URL、文件、UI、日志或其他 Agent?
不变量 即使模型已被攻破,哪些条件仍必须成立?

一个可执行的不变量:

邮件内容可以影响摘要,但不能为私有附件选择新的接收人。

这个条件能由代码强制执行。“模型必须忽略恶意指令”不能。

常见防线为什么失效

关键词与正则拦截

ignore previous instructions 之类模式只能拦截演示文本,不等于拦截攻击类别。攻击者可以改写或把目标分散到多轮上下文;正常安全讨论也会出现这些词,造成误报。

检测器适合 Telemetry、Triage 和深度防御,但不能因为检测结果是“安全”就授予权限。

破坏性 Sanitization

删除括号、尖括号、Unicode 或 Base64 会破坏合法代码与文档,却无法阻止普通自然语言攻击。Sanitization 适合移除 Active HTML、Script、Macro 和不安全文件结构,不能可靠清洗语义。

分隔符与 Prompt 加固

真实 System/Developer/User/Tool 角色和清晰标签有助于模型判断。把 <|system|> 手写在一条普通 Prompt 中不会建立提供方角色边界。即使是真实角色分离,依然属于概率性模型控制,不是确定性鉴权。

保密 System Prompt

Prompt 中不能存放凭证或安全关键秘密,应假设指令可能被重建或泄露。Prompt 保密可以保护知识产权,但不能成为权限系统成立的前提。

输出敏感词过滤

搜索 PASSWORD 或 Prompt 前 50 个字符既会漏掉改写和 Tool 外泄,也会阻止正常解释。应校验 Schema,并检查数据流、目的地和渲染输出。

只依赖 Guardrail Model

Classifier 与加固模型可以提高攻击成本,但仍面临概率性与 Distribution Shift。Anthropic 的浏览器 Agent 研究在显著改善后仍明确报告残余风险。安全关键控制必须位于 Classifier 外。

安全架构

1. 最小化 Agency

只做摘要的工作流不应获得写工具;只需要一个日历时,不应获得全部网盘和邮箱。使用短期、任务级凭证,并保留源系统的对象级鉴权。

优先:

text
总结用户选中的邮件

而不是:

text
读取所有邮件 + 读取所有文件 + 任意浏览 + 发送消息

最安全的危险能力,是进程从未得到的能力。

2. 分离 Control Flow 与不可信数据

根据可信用户意图和政策生成计划;在没有高权限工具的隔离组件中处理不可信内容;只向高权限 Controller 返回 Typed Value,而不是自由文本指令。

该模式仍需要数据流政策:攻击者可能无法决定调用哪个 Tool,却能操纵接收地址等参数。CaMeL 研究在 Dual-LLM 基础上增加 Provenance 与 Capability,同时约束 Control Flow 和 Data Flow。

3. 追踪来源与信任

为数据附加来源:

text
recipient = "attacker.example"
origin = web_page_73
trust = untrusted
derived_by = extraction_model_v4

Taint 必须通过 Summary、Translation、OCR 与模型抽取继续传播。转换不会让攻击者可控信息自动变可信。

4. 确定性鉴权 Tool Call

只检查 Tool Name 不够,还需检查:

  • 认证后的 Principal 与 Tenant;
  • Resource Owner 与对象级权限;
  • 允许的 Action 和参数 Schema;
  • Data Classification 与 Destination;
  • 每个安全敏感参数的来源;
  • 事务、速率和金额限制;
  • 是否需要精确用户确认。

模型只负责提议,应用代码负责决定。

5. 控制数据外发

使用目标 Allowlist、DNS/IP 校验、Redirect 上限、跳转后 URL 二次校验与网络政策。禁止敏感值进入 URL、Markdown、Analytics、Log 或公共 Tool 参数。通过受控 Proxy 获取外部内容,不向模型生成代码提供无限网络。

6. 把确认绑定到精确操作

“Agent 需要权限继续”不构成知情确认。界面应展示具体接收人、资源、金额、数据字段和不可逆影响。Approval 必须以加密或事务方式绑定 Canonical Action,防止模型在确认后替换参数。

7. 沙箱执行

生成代码应在隔离 Runtime 中运行:

  • 无环境默认凭证;
  • 默认只读或临时文件系统;
  • 明确 CPU、内存、时间、进程和输出限制;
  • 禁止网络或只允许狭窄 Egress Proxy;
  • 审计 Package 与 Syscall;
  • 不允许不可信内容直接触达 Host Execution。

Sandbox 限制影响范围,但不能判断业务操作是否被授权。

8. 保护 RAG 与 Memory

  • 检索前强制 Document ACL;
  • 保留 Source、Author、Time 与 Trust;
  • 分开计算 Relevance Score 与 Trust Score;
  • 不把检索到的指令自动升级为 Procedural Memory;
  • 隔离新语料,Parser 变化后重新评测;
  • Memory Write 必须是独立鉴权操作;
  • 删除传播到 Index、Summary、Cache 与 Memory。

可运行的确定性 Policy Gate

下面只使用 Python 标准库,在模型提出 Tool Call 后执行鉴权,不依赖任何 LLM SDK。

python
from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from hashlib import sha256
import json


class Verdict(str, Enum):
    ALLOW = "allow"
    CONFIRM = "confirm"
    DENY = "deny"


@dataclass(frozen=True)
class ToolCall:
    user_id: str
    action: str
    resource_owner: str
    destination: str | None
    data_labels: frozenset[str]
    influenced_by_untrusted: bool
    destructive: bool = False


@dataclass(frozen=True)
class Decision:
    verdict: Verdict
    reason: str
    action_digest: str


def action_digest(call: ToolCall) -> str:
    payload = {
        "action": call.action,
        "data_labels": sorted(call.data_labels),
        "destination": call.destination,
        "destructive": call.destructive,
        "resource_owner": call.resource_owner,
        "user_id": call.user_id,
    }
    canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
    return sha256(canonical.encode()).hexdigest()


def authorize(
    call: ToolCall,
    *,
    allowed_actions: set[str],
    trusted_destinations: set[str],
    approved_digest: str | None = None,
) -> Decision:
    digest = action_digest(call)

    if call.action not in allowed_actions:
        return Decision(Verdict.DENY, "action is outside task scope", digest)
    if call.resource_owner != call.user_id:
        return Decision(Verdict.DENY, "resource owner mismatch", digest)

    external = (
        call.destination is not None
        and call.destination not in trusted_destinations
    )
    sensitive = bool(call.data_labels & {"secret", "personal", "financial"})

    if call.influenced_by_untrusted and (external or call.destructive):
        return Decision(
            Verdict.DENY,
            "untrusted content cannot control external or destructive effects",
            digest,
        )
    if external and sensitive:
        return Decision(
            Verdict.DENY,
            "sensitive data cannot be sent to an untrusted destination",
            digest,
        )
    if external or call.destructive:
        if approved_digest != digest:
            return Decision(
                Verdict.CONFIRM,
                "exact action requires user confirmation",
                digest,
            )

    return Decision(Verdict.ALLOW, "policy satisfied", digest)

该政策只是示例,不是通用安全产品。生产环境仍需中心化 Identity、租户范围内的对象级鉴权、目标地址规范化(含跳转和 DNS 策略)、持久分布式限流、事务确认与审计日志。字符串集合不能证明目标安全。关键属性在于:接收人或资源变化会改变 Digest,使旧确认自动失效。

安全评测

建立配对的 Utility 与 Attack 数据集

每条工作流至少包含:

  • 正常任务和边界条件;
  • 直接与间接攻击;
  • 每种不可信来源与模态;
  • Tool 参数操纵;
  • 私有数据外泄;
  • 持久 Memory 和延迟激活;
  • 多语言、编码与混淆变体;
  • 多轮与 Best-of-N 尝试;
  • 与畸形 Tool Output、Redirect 组合的攻击。

评测结果,不评测拒绝用语

记录:

  • **Attack Success Rate:**未授权目标是否真正实现;
  • **Utility Success Rate:**合法任务是否正确完成;
  • 未授权 Tool Call 与 Data Egress Rate;
  • 跨租户访问率;
  • Confirmation Precision 与用户取消率;
  • Detector False Positive/Negative;
  • p50/p95 延迟和成本;
  • 多次攻击成功率与置信区间。

如果请求已经被发送,模型随后说“我不能这样做”没有意义;反过来,安全摘要中提到注入内容也不等于攻击成功。

自适应测试

静态 Payload 会快速过时。允许授权红队 Agent 或人工测试者在严格 Sandbox 中观察防线并持续变异攻击。每次模型、Prompt、Parser、Tool、MCP Server、Memory、Retriever 或 Policy 变化后都要重跑。

生产环境可使用 Canary 与事件 Telemetry,但必须脱敏 Prompt 内容,并防止安全日志成为新的泄漏源。

事件响应

注入已经或可能产生影响时:

  1. 停止受影响 Tool、Connector 与外发路径;
  2. 撤销任务凭证和 Session Token;
  3. 保全 Prompt、源内容、Tool Call、Approval 与 Policy Decision;
  4. 识别受影响用户、租户、资源与目的地;
  5. 删除被污染文档、Memory、Summary、Cache 与 Index;
  6. 核验下游操作并尽可能回滚;
  7. 修复完整架构路径,将攻击链加入回归集;
  8. 按事件政策通知用户或监管机构。

如果派生 Summary 或 Memory 仍包含攻击影响,只删除可见 Payload 不够。

生产检查清单

  • [ ] 已记录 Asset、Principal、Source、Capability、Sink 与 Invariant;
  • [ ] 所有外部来源和 Tool Result 默认标为不可信;
  • [ ] 模型没有环境默认权限;
  • [ ] Tool 鉴权使用真实 User、Tenant、Resource 与 Action;
  • [ ] 安全敏感参数携带 Provenance;
  • [ ] 敏感数据无法发送到任意目标;
  • [ ] Confirmation 展示并绑定精确影响;
  • [ ] 生成代码运行在受限 Sandbox;
  • [ ] RAG ACL 在语义排序前执行;
  • [ ] Memory 与配置写入单独鉴权;
  • [ ] Utility 与自适应攻击门禁覆盖每次发布;
  • [ ] 事件响应包含派生状态清理与凭证撤销。

常见问题

System Prompt 是访问控制边界吗?

不是。它适合声明行为,不能保存凭证或执行权限。无论模型生成什么文本,后端都必须拒绝未授权操作。

可疑输入是否应该一律拦截?

不应该。安全分析师可能需要模型分析注入 Payload。应标记来源、限制能力并检查预期影响,而不是盲目封禁词语。

Fine-tuning 或 RAG 能解决 Prompt Injection 吗?

不能。它们可能改善特定分布上的表现,但不会建立指令与不可信数据之间的确定性隔离。RAG 还会增加间接输入攻击面。

商业模型一定比开源模型安全吗?

安全性取决于具体模型、配置、系统架构、Tool、凭证和威胁模型。提供方防线有价值,但真正有后果的能力仍由应用所有者控制。

总结

Prompt Injection 应被视为概率性决策组件可能被攻破的预期事件。安全系统不会要求这个组件自行批准自己的权限。

降低能力、保留来源、在代码中执行对象级政策、限制数据外发、绑定用户确认并持续测试完整攻击链。即使不可信语言影响了模型,外围架构仍能拒绝把它升级为权威操作。

一手资料