
# PromptStrike
**将原始的 LLM 攻击输出转化为经过分类和严重性评分的渗透测试报告。**



PromptStrike 是一款黑盒 LLM 红队扫描器。它会针对 LLM endpoint 探测
**OWASP LLM Top 10 (2025)** 漏洞,使用 **LLM-as-judge** 来判定
每次攻击是否真正成功,过滤误报,使用文档化的评分标准对严重性进行评分,并生成一份包含
OWASP + MITRE ATLAS 映射和 GRC 附录的独立 HTML/Markdown 报告。
## 为什么会有这个项目
现有的 LLM 红队工具非常擅长*生成攻击*和输出
*原始信号*,但它们未能触及安全团队真正需要的部分 —
一份有理有据、划分优先级的发现。
- **[Garak](https://github.com/NVIDIA/garak)** 运行许多探测并基于
字符串/启发式检测器报告每个探测的成功/失败。
- **[Promptfoo](https://www.promptfoo.dev/)** 生成一个跨 prompt 和断言的
评估/红队结果矩阵。
- **[PyRIT](https://github.com/Azure/PyRIT)** 是一个灵活的编排
框架 — 你需要自己组合攻击、评分和报告。
它们留给你的是大量的尝试和检测器命中记录。**真正的差距在于最后一英里:** 哪些命中是真实的(而不是检测器的误报),每一个的严重程度如何,它如何映射到公认的分类法中,以及如何将其移交给 CISO *以及*工程师。PromptStrike 对此有着明确的坚持:
```
raw attack attempts → LLM-as-judge verdict → FP filter + dedup →
documented severity (CVSS-style vector) → OWASP/ATLAS mapping → report
```
它不追求拥有最多的探测方式。它的目标是让每一个确认的发现都**可信、有排名且易于沟通**。
## 示例报告
这是一份基于基线发现生成的真实报告(完整文件:
[`docs/sample-report.html`](docs/sample-report.html),或由 GitHub 渲染的
[`docs/sample-report.md`](docs/sample-report.md))。
**执行摘要** — 按严重性分类的发现、内联的 SVG 严重性条形图,以及
适合 CISO 阅读的风险叙述:

**单个发现** — 严重性徽章、CVSS 风格的向量、OWASP + MITRE ATLAS、影响、
复现步骤,以及**经过脱敏处理的**请求/响应证据:

HTML 报告是一个**单一的自包含文件**(内联 CSS,无外部
资源,暗黑主题)。除了上述内容外,它还包括覆盖所有 10 个
OWASP 类别的覆盖矩阵、针对每个发现的修复建议,以及交叉引用
NIST AI RMF、ISO/IEC 42001 和欧盟《AI 法案》条款的 GRC 附录。
## 快速开始
```
make install # pip install -e ".[dev]"
promptstrike --help
# 针对内置的、故意存在漏洞的目标的端到端演示。
# 除了带有两个小模型的本地 Ollama 外,没有任何外部服务:
ollama pull dolphin-mistral llama3.2
make scan-demo # promptstrike scan --config config.yaml --out-dir .demo
```
`scan-demo` 针对本地目标运行**完整的流水线** — 扫描 → 评判 →
分类 → 报告 — 并将带有时间戳的结果 JSON 和 HTML 报告写入到
`.demo/`。不需要 API key,也不依赖外部 endpoint。
## 测试你自己的目标
演示扫描的是一个内置的虚拟目标。本节将引导你将 PromptStrike 指向一个
**真实**目标 — 即你有权测试的 LLM endpoint 或应用。本指南
假设你了解基本的安全概念,但从未使用过此工具。
### 开始之前
你需要准备五样东西:
- **(a) 书面授权**以测试目标。黑盒探测会向实时系统发送
对抗性 prompt;只能对你拥有的或
有明确书面测试许可的系统进行操作。(参见[负责任的测试](#responsible-testing)。)
- **(b) 目标 LLM/应用的一个可访问的 endpoint** — 一个 PromptStrike 可以从你运行它的地方向其发送聊天请求的 URL。
- **(c) 凭证**,如果 endpoint 需要的话(如 API key、token 等)。
- **(d) 配置好的评判模型** — 可以是本地的 [Ollama](https://ollama.com)
模型(免费,能力较弱),也可以是云 API key(例如 Anthropic;能力更强,
推荐用于生成真实报告)。评判模型将决定每次攻击是否真正成功;
请参阅[选择评判模型](#choosing-the-judge-model)。
- **(e) 本地安装好的 PromptStrike** — 参见上方的[快速开始](#quickstart)
(`make install`)。
### Provider 概念(请先阅读此部分)
PromptStrike **不会**硬编码其攻击目标。它可以与任何支持
**三种协议之一**的 endpoint 进行通信,你可以通过编辑 YAML
配置文件来选择使用哪一种 — **无需修改代码**:
| `provider:` 值 | 协议 | 典型用例 |
|---|---|---|
| `openai` | OpenAI 兼容的 `/chat/completions` | 大多数托管的聊天机器人 API、网关(vLLM, LiteLLM, Together, Groq, …)以及 Ollama 的 `/v1` 适配层 |
| `anthropic` | Anthropic Messages API | 由 Claude 驱动的应用 |
| `ollama` | Ollama 原生 `/api/chat` | 本地或自托管模型 |
**目标**(你攻击的对象)和**评判模型**(对攻击进行评分的对象)
都是 provider,且它们可以使用不同的协议。如果你的 endpoint 不符合
这三种中的任何一种,请参阅[扩展至新的 Provider](#extending-to-a-new-provider)。
### 配置示例(每种 provider 一个)
将其中的一个复制到文件中 — 比如说 `my-target.yaml` — 然后修改其中的值。
每个字段都有注释说明。
**1. OpenAI 兼容的目标**(例如你自己托管的聊天机器人 API):
```
target:
provider: openai # speaks the OpenAI /chat/completions protocol
base_url: https://api.yourcompany.com/v1 # your API root, ending in /v1
model: your-model-name # the model/deployment name your API expects
api_key: ${YOUR_API_KEY} # read from an env var at runtime (see note below)
agentic: false # true if the target can call tools / take actions
# (raises severity for injection/output/agency findings)
judge:
provider: anthropic # score attacks with a strong cloud model
model: claude-sonnet-5
api_key: ${JUDGE_API_KEY}
# probes: # 可选:运行一个子集;省略此项以运行整个
# - llm01_prompt_injection # in-scope 库。运行 `promptstrike list-probes`。
# - llm02_sensitive_info
```
**2. 基于 Anthropic 的目标**(由 Claude 提供服务的应用):
```
target:
provider: anthropic # speaks the Anthropic Messages API
model: claude-sonnet-5 # the Anthropic model your app serves
api_key: ${YOUR_API_KEY} # read from an env var at runtime
base_url: https://api.anthropic.com # optional; this is the default (override for a proxy)
agentic: false # true if the target can take actions
judge:
provider: ollama # score locally, free (weaker than a cloud judge)
model: llama3.2
options:
temperature: 0 # deterministic, reproducible verdicts
```
**3. 本地 Ollama 目标**(你自己微调或自托管的模型):
```
target:
provider: ollama # speaks Ollama's native /api/chat
model: my-finetuned-model # a model tag from `ollama list`
base_url: http://localhost:11434 # optional; this is the default local daemon
options:
temperature: 0 # deterministic, reproducible target responses
agentic: false # true if the target can take actions
judge:
provider: anthropic # a strong judge keeps the report credible
model: claude-sonnet-5
api_key: ${JUDGE_API_KEY}
```
### 你的首次运行,分步指南
使用上方的配置示例 1(目标 key 为 `YOUR_API_KEY`,评判模型 key 为 `JUDGE_API_KEY`):
**1. 将你的凭证放入环境变量中。** 这些变量仅存在于
当前的终端会话中:
```
export YOUR_API_KEY="sk-your-real-target-key"
export JUDGE_API_KEY="sk-ant-your-real-judge-key"
```
**2. 扫描前验证连通性** — `ping` 会向
每个 provider 发送一个简单的 prompt,让你在几秒钟内就能发现错误的 URL 或 key,而不是在扫描过程中才发现:
```
promptstrike ping --config my-target.yaml
```
**成功**的 ping 看起来像这样(两个 endpoint 都响应了):
```
ping
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┓
┃ Role ┃ Provider ┃ Latency ┃ Reply ┃
┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━┩
│ target │ your-model-name │ 512 ms │ Pong │
│ judge │ claude-sonnet-5 │ 734 ms │ Pong │
└────────┴───────────────────┴─────────┴───────┘
```
**连接失败**会显示 `FAIL` 及其原因(在扫描前修复此问题):
```
│ target │ your-model-name │ FAIL │ api.yourcompany.com returned 401: … │
```
常见原因:错误的 `base_url`,缺失/过期的 key (401/403),或者
无法访问的主机(连接被拒绝 / 超时)。
**3. 运行扫描:**
```
# 完整的 in-scope 库,将输出写入 ./results/
promptstrike scan --config my-target.yaml --out-dir results
# 或者从小处着手 —— 选择一两个类别进行快速的初步扫描:
promptstrike scan --config my-target.yaml --only LLM01,LLM07 --out-dir results
```
**4. 成功运行后的样子** — 每个类别显示一个进度条,然后是
摘要和两个输出文件的路径:
```
Scan complete: 3 finding(s) across 12 attempt(s) in 3 in-scope categories.
Confirmed findings
┏━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━┓
┃ Severity ┃ OWASP ┃ Category ┃ Conf ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━┩
│ Critical │ LLM02:2025 │ Sensitive Info… │ 1.00 │
│ High │ LLM07:2025 │ System Prompt… │ 0.90 │
└──────────┴────────────┴──────────────────┴──────┘
3 confirmed finding(s). Raw results: results/promptstrike-results-…json ·
HTML report: results/promptstrike-report-…html
```
如果目标不可达或 key 错误,扫描将提前停止并打印
带原因的 `Error: …` — 这与第 2 步中 `ping` 暴露出的问题相同。
**5. 打开并阅读报告。** `.html` 文件是自包含的(可在
任何浏览器中打开);`.json` 是原始发现,你以后可以基于它重新生成报告。
```
open results/promptstrike-report-*.html # macOS
xdg-open results/promptstrike-report-*.html # Linux
```
### 扩展至新的 Provider
如果你的目标不支持上述三种支持的协议(例如定制的
API、消息队列、自定义网关),你可以通过实现
`LLMProvider` 接口(单个方法,`chat(messages, system) -> str`)来添加支持。这是一个
**小型且独立的添加:只需一个新的适配器文件**,而无需更改
扫描器、评判模型、分类逻辑或报告。
从 [`src/promptstrike/providers/base.py`](src/promptstrike/providers/base.py)
(接口定义)开始,并复制一个现有的适配器作为模板 —
[`openai_compat.py`](src/promptstrike/providers/openai_compat.py),
[`anthropic.py`](src/promptstrike/providers/anthropic.py), 或
[`ollama.py`](src/promptstrike/providers/ollama.py) — 然后在
[`registry.py`](src/promptstrike/providers/registry.py) 中注册你的 provider
名称。所有
下游流程仅依赖于该接口,因此其他部分不需要任何改动。
## 工作原理
```
flowchart LR
cfg["YAML config
(config.loader, ${ENV})"] --> tgt["Target provider
OpenAI / Anthropic / Ollama"]
cfg --> jdg["Judge provider
(separate model)"]
lib[("Probe library
probes/*.yaml")] --> scan
tgt --> scan["engine.scanner
send each payload"]
scan -- "payload + response" --> jdg
jdg -- "strict JSON verdict" --> raw[("Raw findings
results-*.json")]
raw --> triage["Triage
FP filter · dedup ·
severity rubric · enrich"]
triage --> report["Report
Jinja2 HTML + Markdown"]
raw -. "re-report offline
(different thresholds)" .-> triage
```
设计原则:引擎是**与 Provider 无关的**(统一的 `LLMProvider`
接口),运行完全由**配置驱动**(YAML),**评判模型是一个独立于**
目标的模型(见下文),严重性是一个**文档化的评分标准**而不是
一个不透明的数字,并且每个发现都带有一个**经过验证的** OWASP ID + MITRE ATLAS
技术(或显式的 `NO_DIRECT_ATLAS_MAPPING` 标志)。扫描和报告
是**可分离的**:原始发现会持久化到 JSON,这样你就可以在离线状态下重新进行分类或重新生成报告,而无需再次请求目标。
## OWASP LLM Top 10 (2025) 覆盖范围
黑盒扫描仅观察 endpoint 的输入和输出。需要
访问模型内部、训练数据或基础设施的类别会被
标记为超出范围(并附带原因),而不是进行伪造。
| ID | 类别 | 覆盖情况 | MITRE ATLAS |
|----|----------|----------|-------------|
| LLM01:2025 | Prompt Injection | ✅ 已测试 | `AML.T0051` LLM Prompt Injection |
| LLM02:2025 | 敏感信息泄露 | ✅ 已测试 | `AML.T0057` LLM Data Leakage |
| LLM03:2025 | 供应链 | ⛔ 超出范围 — 需要 artifact/出处访问权限 | — |
| LLM04:2025 | 数据和模型投毒 | ⛔ 超出范围 — 需要训练数据访问权限 | — |
| LLM05:2025 | 不当输出处理 | ✅ 已测试 | 无直接 1:1 映射 (最接近 `AML.T0077`) |
| LLM06:2025 | 过度代理 | ✅ 已测试 | 无直接 1:1 映射 (设计缺陷) |
| LLM07:2025 | System Prompt 泄露 | ✅ 已测试 | `AML.T0056` Extract LLM System Prompt |
| LLM08:2025 | 向量和嵌入弱点 | ⛔ 超出范围 — 需要 RAG/索引访问权限 | — |
| LLM09:2025 | 虚假信息 | ✅ 已测试 | 无直接 1:1 映射 |
| LLM10:2025 | 无限制消耗 | ⛔ 超出范围 — 需要产生滥用负载 | — |
ATLAS 技术名称已对照官方目录
(`mitre-atlas/atlas-data`,v5.6.0)进行了验证;如果某个类别没有清晰的 1:1 对手
技术,PromptStrike 会如实记录 `NO_DIRECT_ATLAS_MAPPING` 标志,
而不是强行套用一个近似的 ID。
## CLI
```
promptstrike ping -c config.yaml # check target + judge are reachable
promptstrike list-probes # show the probe library
promptstrike scan -c config.yaml \ # scan → judge → triage → report
[--target ollama:llama3.2] [--judge …] \
[--only LLM01,LLM07] [--min-confidence 0.6] [--report out.html]
promptstrike report results-*.json # re-render offline from saved results
```
## 选择评判模型
评判模型决定每次攻击是否真正成功,因此**它的质量决定了
整个报告的可信度** — 一个较弱的评判模型会导致误报和
漏报,从而破坏每一项发现的说服力。
**真实扫描的默认选择:强大的 Anthropic 模型**(`claude-sonnet-5` — 性价比最高的强大选项)。**Ollama 是一种显式的选择**,用于
无成本的本地/CI 运行;小型本地模型作为评判者时能力明显较弱,因此
请将其判定结果视为参考性而非权威性的。
```
judge:
provider: anthropic
model: claude-sonnet-5
api_key: ${JUDGE_API_KEY}
```
## 内置的练习目标
`vulnerable_chatbot` 是一个**刻意设置得较为脆弱的本地“应用”**,你可以
扫描它而不是真实的系统。它包装了一个带有故意粗劣的
system prompt 的后端模型 — 一个**伪造的** API key 和**伪造的**内部记录作为
随意的上下文粘贴进去,没有“对此保密”的指令,也没有输入护栏。它会因为
粗心大意而发生泄露,就像仓促开发的真实应用那样,并且通过相同的
`LLMProvider` 接口暴露出来,以便扫描器像对待任何外部目标一样对待它。
## 负责任的测试
PromptStrike 是一款用于授权评估的**防御性安全工具**。
- **仅扫描你拥有或获得明确书面许可测试的系统。**
未经授权探测第三方的 LLM 可能是违法行为。
- 引擎和 prompt 旨在用于**发现和报告漏洞,
而不是为了逃避他人的安全控制。
- **报告包含敏感证据。** 发现内容包含(尽力进行脱敏处理的)
请求/响应记录;请将结果 JSON 和报告视为敏感信息,并
使其远离版本控制(参见 `.gitignore`)。
- 内置的脆弱目标存在**仅仅是为了**被扫描;请勿部署它。
## 路线图
- **多轮 / 渐进式攻击** — 在多轮对话中不断升级的状态会话,而不是
单次 payload。
- **智能体工具滥用链** — 通过真实的工具/函数调用测试 LLM06:
链式注入 → 工具调用 → 数据泄露,并对端到端的影响进行评分。
- **CI 模式** — 高于严重性阈值时非零退出,并输出 SARIF/JSON,
以便扫描可以拦截 pull request。
- 间接(检索传播的)prompt injection、跨运行的报告差异对比,以及
额外的 provider 适配器。
## 构建此项目时学到的经验
**LLM-as-judge 本身就是一个非确定性的组件 — 这可能会在不经意间破坏你的测试发现。** 在针对手动标记的基线验证 pipeline 时,
三个已知的阳性案例最初*未能通过*:评判模型返回了
置信度为 `0.00` 的 `success=false`,即使目标已经明显发生了
泄露(伪造的 secret 就在响应中 — 我用纯字符串
匹配进行了确认)。问题不在目标;而在**评判模型**。小型本地
评判模型(`llama3.2`)在默认 temperature 下是非确定性的,偶尔会将
明目张胆的 system prompt 泄露判定为“未泄露”。
解决方法是让评判模型变得确定性:我向 Ollama provider 添加了 sampling-`options`
透传,并为目标和评判模型固定了 **`temperature: 0`**。随后基线在
一次又一次的运行中可复现地通过了 4/4。
这一教训被总结为该工具现在强制执行的两条设计规则:
1. **固定 sampling 并根据基准真相验证评判模型。** 评判模型的
结论是数据,而不是绝对真理 — 可复现性需要 `temperature: 0`,而
正确性需要将评判模型与手动标记的用例进行核对(
`docs/manual-baseline.md`),而不是假设它是正确的。
2. **倾向于使用强大的评判模型,并明确声明这一点。** 评判模型的质量
决定了报告的可信度,因此默认配置是一个强大的模型,同时清晰地
标明本地选项能力较弱。
另外两个较小的经验已融入代码库中:**严重性是一个文档化的评分标准**
(基础影响 + 可利用性 + 智能体升级 → CVSS 风格的向量),而不是
模型输出的一个不透明数字;此外,HTML 报告会**自动转义**,因为
攻击者控制的响应可能包含 `