区分 Direct 和 Indirect Prompt Injection,不是为了给 Payload 分类,而是为了确定攻击者通过哪条信任路径影响模型。
text
Direct Prompt Injection
攻击内容由当前交互用户直接提交给应用。
Indirect Prompt Injection
攻击内容先进入网页、邮件、文档、RAG、Tool Result 等外部载体,
随后由系统自动读取并放入模型 Context。
两者的核心机制相同,安全边界却不同。
Direct:用户输入本身就是攻击入口
假设一个客服 Agent 的应用指令要求它只能查询当前用户的订单。用户直接要求模型忽略限制并查询另一个账号,这属于 Direct Prompt Injection。
text
攻击者
→ User Message
→ LLM Context
→ 模型输出 / Tool Proposal
Direct Injection 通常较容易复现,因为攻击者能够快速调整措辞并观察结果。但它是否能形成安全影响,仍取决于下游授权。
如果订单 Tool 会根据当前登录用户强制过滤资源,即使模型生成了越权参数,也应该被拒绝。此时模型行为可能失败,系统授权仍然有效。
Indirect:业务数据变成了指令载体
Indirect Prompt Injection 更接近传统应用安全中的存储型或跨组件污染问题。攻击者不直接与 Agent 对话,而是控制 Agent 将来可能读取的数据。
text
攻击者写入网页 / 邮件 / 文档
↓
内容被业务系统保存或发布
↓
Agent 在正常任务中读取
↓
内容进入 Context
↓
影响模型计划或 Tool Proposal
常见载体包括:
text
搜索结果与网页正文
电子邮件和工单
共享文档与代码仓库文件
RAG 知识库 Chunk
图片 OCR 或多模态内容
MCP Resource 与 Tool 返回值
其他 Agent 的输出
长期 Memory
间接注入的危险之处在于:读取行为本来是合法业务流程。用户可能只是要求"总结这封邮件"或"分析这个仓库",应用却把攻击者控制的内容带进了高权限 Agent 的决策环境。
Stored Injection 与持久化
如果恶意内容被保存到知识库、Memory、工单或仓库中,并在之后的任务里反复触发,可以把它视为 Stored Prompt Injection。
text
一次写入
→ 多次检索
→ 多个用户或 Agent 读取
→ 跨会话影响
这类问题的风险不仅来自单次触发,还来自作用范围和持续时间:内容可能影响不同用户、不同任务,甚至在原始攻击入口消失后继续存在。
Memory Security 和 RAG Poisoning 会在后续章节单独分析。这里先保留一个判断原则:任何能够重新进入 Context 的持久化内容,都应该重新作为 Source 进行信任评估。
Indirect Injection 的数据流为什么更难看清
Direct Injection 的输入来源通常就是当前用户。Indirect Injection 则可能跨越多个组件:
中间组件可能执行:
- HTML 清洗;
- 文档解析;
- OCR;
- Chunking;
- Embedding;
- 检索和重排;
- 摘要或格式转换。
这些处理可能改变攻击文本,但通常不会自动消除其指令语义。与传统 Taint Analysis 类似,经过转换不等于失去污染属性。
为内容保留 Provenance
如果 Context Builder 只接收纯文本:
python
context.append(document.text)
后续组件很难判断这段话来自用户、企业知识库还是外部网页。
更合理的数据结构需要保存来源和允许用途:
python
ContextItem(
content=document.text,
source_type="external_web",
source_id=document.url,
trust="untrusted",
owner=None,
allowed_uses=["summarize"],
)
Provenance 标签不能保证模型不受影响,但可以让模型之外的 Policy Engine 判断:外部网页可以贡献摘要事实,却不能成为写操作的授权来源。
测试时不要只换措辞
评估 Direct Injection 时,Payload 变体确实有价值。评估 Indirect Injection 时,还应系统变化攻击载体和传播路径:
text
同一攻击意图
× 不同 Source
× 不同解析方式
× 不同 Context 位置
× 不同任务阶段
× 不同持久化范围
这比单纯收集"忽略之前指令"的改写更接近真实系统风险。