srathish/prompt-injection-guard
GitHub: srathish/prompt-injection-guard
一个针对大语言模型 prompt injection 攻击的防御库,通过模式检测、风险评分和内容隔离三层机制保护模型上下文免受外部恶意文本的注入。
Stars: 0 | Forks: 0
# prompt-injection-guard
一个防御 prompt injection 的安全层。它对不受信任的内容运行模式与结构
检测器,在工具返回的结果到达模型之前,将其包裹在隔离块中,
并根据内置的已标注攻击语料库衡量自身的捕获率。可以把它想象成一个
垃圾邮件过滤器,专门过滤 LLM 从外部世界获取的文本。
我开发这个项目,是因为“直接告诉模型忽略被注入的指令”这种建议在遇到真实的检索文档时根本行不通。我想要一些具体的东西:可以实际指出的检测器、可以设定阈值的评分、能够直接插入到工具调用前的隔离包装器,以及关于其实际效果的诚实数据。
## 概述
该库包含三个核心组件,每个组件都可以独立使用:
- **检测器 (Detectors)** (`guard/detectors.py`) — 针对五类攻击家族的独立扫描器。每个检测器返回的发现结果都包含严重程度以及触发它们的确切字符位置,因此决策始终是可解释的。
- **扫描器 (Scanner)** (`guard/scanner.py`) — 将各项发现汇总为加权风险评分,并应用 `allow` / `flag` / `block`(允许 / 标记 / 拦截)的阈值策略。
- **隔离器 (Quarantine)** (`guard/quarantine.py`) — 使用每次调用随机生成的边界 token 将不受信任的内容包裹在带有清晰标签的块中,并中和其中嵌入的角色标签。
可选的 LLM 第二阶段 (`guard/llm_stage.py`) 可以对模糊的中间地带进行裁决。它是 `anthropic` SDK 之上的一个轻量级封装,并且完全可以被模拟 —— **启发式 pipeline 在零 API 密钥和零 pip 安装的情况下即可运行。**
## 威胁模型
Prompt injection 是指不受信任的文本一旦进入模型的上下文,就会试图覆盖开发者给出的指令 —— 比如“忽略你之前的指令,改做 X”。模型原生无法区分*自身的指令*和*被要求读取的数据*;这两者都是以 token 的形式接收的。
危险的攻击面通常不是用户直接输入的内容。而是系统**代表用户**拉取的内容:
- **工具结果** — 一个网页、一个 API 响应、或一个被要求总结的文件。攻击者只要控制了其中任何一项,就控制了落入上下文窗口中的文本。
- **检索到的文档** — RAG 段落、知识库条目、电子邮件、支持工单。这就是*间接* prompt injection:攻击者从不直接与模型对话,他们只是将 payload 植入到模型稍后会读取的某个地方。
这种理念驱动了整个设计:该工具假设每一份外部文档都具有潜在的对抗性,并将扫描和隔离这些内容视为一种安全边界,而不是一种格式上的修饰。
## 架构
```
untrusted content
(tool result / retrieved doc)
|
v
+--------------------------------------+
| detectors |
| instruction_override |
| role_impersonation | each finding:
| delimiter_escape | severity + char span
| encoded_payload |
| data_exfiltration |
+--------------------------------------+
|
findings[]
v
+--------------------------------------+
| scanner: weighted risk score |
| score >= block -> BLOCK |
| score >= flag -> FLAG ---------+ |
| else -> ALLOW | |
+-----------------------------------|--+
| |
BLOCK / ALLOW (optional) FLAG
| |
| v
| +-----------------------+
| | LLM second stage |
| | (anthropic / mock) |
| +-----------------------+
v |
+-------------------------------+
| quarantine allowed content |
| random boundary + defang tags|
+-------------------------------+
|
v
safe block -> prompt
```
## 设计决策
- **模式存在于单一的数据驱动表中,而非散落的正则表达式。** 每一个基于短语的信号都是 `guard/detectors.py` 中的一行 `PatternRule`。添加一个新的攻击短语只需一行代码,包含其类别、严重程度和人类可读的信息。这使得检测器易于审计且方便扩展。
- **倾向于精确的检测器,而非贪婪的检测器。** 指令覆盖检测器会寻找一个覆盖性动词*加上*一个类似指令的宾语(“ignore … prior **instructions**”),因此“please disregard my previous **email**”(请忽略我之前的**邮件**)不会被误报。角色检测器需要寻找实际的 `` 标签或 `[INST]` token,而不是代码示例中光秃秃的“system”一词。这正是保持可用精确度的关键。
- **正则表达式无法识别的结构信号拥有专属函数。** 较长的 base64 串、零宽/双向控制字符以及同形异义字(混合脚本)伪装都能被直接检测到。同形异义字检查仅在拉丁单词*内部楔入*易混淆字符时触发,因此英语句子中正常的俄语单词不会触发误报。
- **三级策略,而非二元策略。** `flag` 用于真正模糊的中间地带,这也正是可选 LLM 阶段发挥价值的地方。
- **每次调用生成随机的隔离边界。** 固定的分隔符是可猜测的 —— 不受信任的内容可以打印一个匹配的闭合标记从而逃逸。每次调用生成基于 `secrets` 的新 token 消除了这一隐患。
- **LLM 阶段是可选且可模拟的。** 检测必须在离线和 CI 环境中工作。模型 ID 来自 `ANTHROPIC_MODEL`(默认为 `claude-sonnet-5`),如果缺少 `ANTHROPIC_API_KEY` 会产生友好的错误提示,而不是抛出堆栈追踪。
## 快速开始
检测、扫描、隔离、基准测试或测试不需要任何依赖项 —— 一切都在 Python 3.11+ 标准库上运行。
从命令行扫描文件:
```
python -m guard scan suspicious.txt
# 退出代码:0 = allow,1 = flag,2 = block
```
在 Python 中使用:
```
from guard import scan, quarantine
result = scan(retrieved_document)
if result.blocked:
raise ValueError(result.explain())
# 否则,在它进入 prompt 之前仍然要将其包装:
safe_block = quarantine(retrieved_document, source="web").render()
```
在内置语料库上运行基准测试:
```
python -m guard bench # prints the tables and writes bench_results.md
```
运行测试套件(仅使用标准库 `unittest`):
```
python3 -m unittest discover -s tests -v
```
可选步骤 —— 启用实时的 LLM 第二阶段:
```
pip install -r requirements.txt
export ANTHROPIC_API_KEY=sk-...
```
```
from guard import scan
from guard.llm_stage import LLMStage
result = scan(retrieved_document, llm_stage=LLMStage.from_env())
```
## 我如何确认它有效
本仓库在 `corpus/` 目录下提供了一个带标注的语料库 —— 包含 36 个攻击样本和 34 个逼真的良性硬负样本。良性样本集是刻意构建成*类似于*攻击的:一封写着“please disregard my previous email”(请忽略我之前的邮件)的电子邮件、包含单词 `system` 的代码、看起来像 base64 的 git SHA、要求您发送发票邮件的支持消息,以及讨论 prompt injection 的文档。以下数字是 `python -m guard bench` 在该语料库上使用默认策略运行的真实输出。
### 混淆矩阵
| | 预测为攻击 | 预测为良性 |
| --- | --- | --- |
| **实际攻击** | 36 (TP) | 0 (FN) |
| **实际良性** | 2 (FP) | 32 (TN) |
### 指标
| 指标 | 值 |
| --- | --- |
| precision | 0.947 |
| recall | 1.000 |
| F1 | 0.973 |
| accuracy | 0.971 |
| 攻击样本 | 36 |
| 良性样本 | 34 |
### 各类别捕获率
| 类别 | 捕获数 | 总数 | 比率 |
| --- | --- | --- | --- |
| instruction_override | 8 | 8 | 1.000 |
| role_impersonation | 7 | 7 | 1.000 |
| delimiter_escape | 7 | 7 | 1.000 |
| encoded_payload | 6 | 6 | 1.000 |
| data_exfiltration | 8 | 8 | 1.000 |
这两个误报是真实的:它们都是*逐字引用了攻击字符串*的良性文档 —— 其中一个包含了字面短语“ignore all previous instructions”作为示例,另一个在解释越狱测试时引用了“you are now DAN”。一个能捕获真实 payload 的检测器同样会捕获嵌入了该确切 payload 的文档。我宁愿在数据中展示这一点,也不愿通过调整语料库来掩盖它。
当扫描器返回 `flag` 或 `block` 时,该样本即被视为*已检测到*。该指标是在这个特定语料库上使用默认阈值测量的;它是对*此*测试集的真实反映,而不是在真实环境中达到 95% 精确度的声明。
## 局限性
我想对它的作用和局限性保持清醒的认识:
- **启发式算法是可以被绕过的。** 这些检测器匹配的是已知的攻击特征。一种新颖的措辞、一种我没有添加的语言、一种更巧妙的编码,或者分散在多个检索块中的 payload 都可能成为漏网之鱼。在一个经过精心筛选的语料库上拥有高 recall,并不等同于对具有适应能力的攻击者具有鲁棒性。
- **语料库很小,而且是由我编写的。** 70 个样本足以发现退化问题并演示各个类别,但不足以做出统计学上的声明。良性硬负样本被选得很刁钻,但它们仍然是我自己挑选的。
- **隔离并不等于遏制。** 将内容包裹在带标签的块中会增加注入的难度,但一个能力强的模型仍然可能选择遵循嵌入的指令。没有任何字符串包装器能改变这一点。
- **LLM 阶段会增加成本、延迟及其自身的攻击面** —— 您是在要求一个模型来评判对抗性文本。它是针对 `flag` 频段的一个有用的第二意见,而不是主要的控制手段。
正确的应对姿态是**纵深防御**:扫描并隔离不受信任的内容,保持工具权限最小化,使得成功的注入也无计可施;对破坏性操作要求确认,并记录所有日志。这个库是整个防御栈中的一层,而不是全部。
标签:DLL 劫持, 大语言模型, 逆向工具