prashdev/promptstrike

GitHub: prashdev/promptstrike

一款黑盒 LLM 红队扫描器,通过 LLM-as-judge 机制过滤误报并自动生成映射了 OWASP LLM Top 10 的 CISO 级漏洞报告。

Stars: 0 | Forks: 0

PromptStrike logo # PromptStrike **将原始的 LLM 攻击输出转化为经过分类和严重性评分的渗透测试报告。** ![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue) ![License: MIT](https://img.shields.io/badge/license-MIT-green) ![Tests: pytest](https://img.shields.io/badge/tests-pytest-informational)
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 阅读的风险叙述: ![PromptStrike 报告 — 执行摘要](https://static.pigsec.cn/wp-content/uploads/repos/cas/f2/f2782f72dec7fc8cadd0655c869f91a8688c17d40a2aa5ec3e2fd483c9bb97ae.png) **单个发现** — 严重性徽章、CVSS 风格的向量、OWASP + MITRE ATLAS、影响、 复现步骤,以及**经过脱敏处理的**请求/响应证据: ![PromptStrike 报告 — 单个发现卡片](https://static.pigsec.cn/wp-content/uploads/repos/cas/38/382a3d0f0e19d0e625dc045a5109f028853f2d3fc1438267707ab1a668077f53.png) 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 报告会**自动转义**,因为 攻击者控制的响应可能包含 `