SuperMarioYL/promptshield
GitHub: SuperMarioYL/promptshield
PromptShield 是一款离线扫描工具,用于在 AI 编程助手读取代码前检测并拦截隐藏在代码注释、commit 和文档中的提示词注入攻击。
Stars: 1 | Forks: 0
[English](./README.en.md) | **简体中文**
架构
`promptshield scan .` 不需要 `git` 或 `gh`,也不发任何网络请求。只有用 `--diff` / `--pr` 时才会把它们当子进程调用。
##
演示
脚本:扫描 `malicious_pr` → 发现表 → `--pr` 退出码 1 让 CI 变红 → 现场命中真实的 Reddit 数据销毁注入。

## 它扫描什么
PromptShield 把仓库或 diff 拆解成 **Surface** 记录(agent 会读到的每一段文本),再用 YAML 规则引擎把每段映射成零或多个 **Finding**。
**扫描的 Surface(代码即提示词的面):** 代码注释 · docstring · commit message · markdown / README · 配置文件 · 字符串字面量。
**五类威胁(seed 规则集,v0.1):**
| 类别 | 含义 | 代表规则 |
| --- | --- | --- |
| `instruction_override` | 直接对 agent 喊话、让它忽略此前指令 | `PS001` `PS002` `PS003` |
| `data_destructive` | 删库 / 销毁数据(Reddit 那类攻击) | `PS010` `PS011` `PS012` |
| `exfiltration` | 读取密钥并外发 | `PS020` `PS021` `PS022` |
| `tool_abuse` | 诱导 agent 跳过确认、滥用工具自主权 | `PS030` `PS031` |
| `obfuscation` | 零宽 / 双向 Unicode、编码载荷等规避人工 review 的手法 | `PS040` `PS041` |
严重度分三档:**HIGH**(令 CI 失败)· **MED** · **LOW**。为压低误报,部分高噪声动词规则用 `requires` 二次门控——只有同时出现 agent 喊话或"全部 / 递归 / 生产库"这类措辞时才会触发,避免一句"删掉临时缓存"的普通注释就被误报。
**混淆解码 pass(v0.2):** 每段 Surface 还会过一遍解码——base64 / hex / 零宽字符剥离 / 同形字(homoglyph)归一化后的变体都会被重新扫描,所以一条藏在编码层背后的注入仍会被命中;`--no-decode` 可关掉。扫描结果可输出为 **SARIF 2.1.0**(`--format sarif`)供 GitHub code-scanning 摄取,规则集也可用 **可堆叠规则包**(`--rules`,详见[配置项](#配置项))按团队策略扩展。
## 集成到 CI
把仓库自带的 [`.github/workflows/promptshield.yml`](./.github/workflows/promptshield.yml) 复制进任意仓库的 `.github/workflows/`,每个 PR 就会被自动门控:
- **PR 上**:`promptshield scan --diff origin/ ` 只扫改动过的 Surface(新增行 + 新 commit message)。
- **push 到 main**:`promptshield scan .` 扫整棵树。
- 出现任何 **HIGH** 即退出 1,让 check 变红。
- **SARIF 上报**:Action 还会以 `--format sarif` 再跑一遍并把结果上传到仓库的 Security → Code scanning 标签页——即便 HIGH 让扫描 exit 1,SARIF 照样上传,所以发现项会出现在 Security 面板里,而不只是一个红 check。
也可以手动接入:
```
# 只扫本次相对 main 的改动
promptshield scan --diff origin/main
# 扫一份 gh api PR-files JSON(CI 里无需 checkout 全量历史)
gh api repos/OWNER/REPO/pulls/123/files > pr.json
promptshield scan --pr pr.json # 有 HIGH 即 exit 1
```
## 基线工作流
在有历史包袱的老仓库上,先把当前所有 Finding 记成"已接受",之后只对**新增**注入告警:
```
promptshield scan . --update-baseline # 写入 .promptshield-baseline.yaml,并 exit 0
git add .promptshield-baseline.yaml
promptshield scan . # 旧 Finding 被抑制,只报新出现的
```
基线按指纹(`rule_id` + 文件 + excerpt 哈希)抑制,所以一旦真有新注入潜入,它仍会浮出水面。
## 对比同类方案
定位,不是吹嘘——在该认输的地方如实认输。
vs [affaan-m/ECC](https://github.com/affaan-m/ECC)(Claude Code / Cursor 实战配置仓库,代表"agent 例行吞入外部代码"的工作流):
| 能力轴 | PromptShield | Garak / PromptBench | Socket / Snyk |
| --- | :---: | :---: | :---: |
| 扫描**源码注释 / commit / markdown** 中的注入 | ✓ | ✗ | ✗ |
| 离线、零 API key、几秒出结果 | ✓ | 部分 | ✓ |
| 依赖包 CVE / 供应链漏洞 | ✗ | ✗ | ✓ |
| 聊天越狱 / 红队语料成熟度 | 部分 | ✓ | ✗ |
| 一行接入 PR 门控(GitHub Action) | ✓ | 部分 | ✓ |
Garak 在**聊天提示词**红队上更成熟,Socket / Snyk 在**依赖供应链**上无可替代——它们和 PromptShield 是互补关系。PromptShield 只专注一件它们都没碰的事:把你的 coding agent *会读进上下文并照做*的源文本,当作提示词来扫描。
## 配置项
`scan` 命令的主要选项:
| 选项 | 类型 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `PATH` | 路径 | `.` | 要扫描的目录或文件 |
| `--diff REF` | 字符串 | 无 | 只扫 `git diff REF` 的新增行 + 新 commit message |
| `--pr FILE.json` | 路径 | 无 | 扫一份 `gh api .../files` 的 PR-files JSON |
| `--baseline FILE` | 路径 | `.promptshield-baseline.yaml` | 用于抑制已接受 Finding 的基线文件 |
| `--update-baseline` | 开关 | `false` | 把当前所有 Finding 写入基线并 exit 0 |
| `--rules FILE / DIR` | 路径(可重复) | 内置规则集 | 自定义 `rules.yaml` 或目录;可重复,包按加载顺序堆叠(同 id 后者覆盖前者) |
| `--format FORMAT` | 枚举 | `table` | 输出格式:`table` / `json` / `sarif`(SARIF 2.1.0) |
| `--no-decode` | 开关 | `false` | 关闭混淆解码 pass(base64 / hex / 零宽 / 同形字) |
| `--json` | 开关 | `false` | 输出机器可读 JSON 而非 Rich 表格(`--format json` 的别名) |
| `--no-color` | 开关 | `false` | 关闭彩色输出 |
| `--repo DIR` | 路径 | `.` | `--diff` 使用的仓库目录 |
### 规则包
`--rules` 可重复、也可指向一个目录——内置 seed 规则集先加载,每个自定义包按顺序堆叠其上,**同 id 的规则后者覆盖前者**(last-wins)。这样团队无需 fork 内置规则,就能叠加自己的策略包、收窄某条规则,或直接关掉它:
```
# my-team-rules.yaml — 叠加在内置规则集之上
rules:
- id: PS050-custom-exfil # 新增一条团队私有规则
severity: HIGH
category: exfiltration
why: 内部约定的密钥外发措辞,内置规则未覆盖。
patterns:
- "dump (env|secrets|\\.env) to .* curl"
- id: PS012-credential-deletion # 直接关掉某条内置规则
enabled: false
```
```
promptshield scan . --rules my-team-rules.yaml --rules policies/ # 多包堆叠
promptshield rules list --rules my-team-rules.yaml # 查看合并后的生效规则集
```
## 付费 · PromptShield Cloud
**CLI 与 GitHub Action 永久开源免费**,它们是漏斗的入口;营收来自托管的 **PromptShield Cloud** 团队版——把 CLI 之上的团队协作能力变成订阅。
| | 开源 CLI / Action | PromptShield Cloud(团队版) |
| --- | --- | --- |
| 本地 / CI 扫描 | ✓ | ✓ |
| 五类 seed 规则集 | ✓ | ✓ |
| 单仓库基线 | ✓ | ✓ |
| **组织级 PR 门控**(多仓库统一策略) | ✗ | ✓ |
| **共享 / 中心化基线**(跨仓库) | ✗ | ✓ |
| **统一 Finding 看板** | ✗ | ✓ |
| **Slack / 飞书 告警**(命中 HIGH 即推送) | ✗ | ✓ |
| 托管的攻击签名 / 规则订阅源 | ✗ | ✓ |
**定价:** **$8 / 席位 / 月**(年付),或 **$99 / 月**封顶团队版(含 15 席)——比一个 Snyk 席位更省,同时是免费 CLI 之上清晰的"团队协作"升级。
**最短付费路径:** Cloud 看板用一个 token 接入团队现有的 Action 输出 → 展示全组织 Finding + 跨仓库共享基线 → 14 天试用 → Stripe Checkout 自助下单。最有说服力的演示:**你的 6 个仓库,一块看板,一份基线,HIGH 命中即 Slack 告警。**
## 路线图
- [x] **m1 — `scan `**遍历仓库,抽取注释 / docstring / markdown / 字符串字面量为 Surface,跑 YAML 规则引擎,打印 Rich 发现表 + 严重度计数。
- [x] **m2 — diff 与 CI**:`scan --diff `(git diff 新增行)+ `scan --pr `(gh PR-files JSON),HIGH → exit 1,附带 `promptshield.yml` Action。
- [x] **m3 — 基线与演示**:`.promptshield-baseline.yaml` 抑制;复现真实 Reddit 数据销毁注入的 `tests/fixtures/malicious_pr/`;asciinema 演示;双语 README。
- [x] **m4 — SARIF 输出**:`scan --format sarif` 输出 SARIF 2.1.0;GitHub Action 通过 `upload-sarif` 把发现项上报到 Security → Code scanning 标签页。
- [x] **m5 — 可堆叠规则包**:`--rules` 可重复、可指向目录,包按加载顺序堆叠(同 id 后者覆盖前者);规则带 `enabled` 开关;`rules list` 查看合并后的规则集。
- [x] **m6 — 混淆解码 pass**:base64 / hex / 零宽 / 同形字变体重新扫描,`--no-decode` 可关。
- [x] **v0.3 — 检测正确性 + 抗绕过加固**:5 项已复现缺陷修复——(m8) 修掉带撇号字符串前缀导致整条注释被跳过的漏报 / 绕过向量(`msg = "don't" # <注入>` 曾返回 0 findings);(m9) 停止扫描 `.promptshield-baseline.yaml` 本身,避免基线文件反复自我命中;(m10/m11) 修正 diff 解析对 `++` 开头新增行的文件归属,以及 `\ No newline at end of file` 造成的行号 +1 漂移;(m12) 把 `decoded_from`(编码层)透传到 Finding,解码命中的发现项会标注 `[base64]` 而非被当成误报。
- [ ] 语义检测(可选、需开关)——在 regex / 启发式之上叠加,提升对绕过手法的召回。
- [ ] 托管攻击签名 / 规则订阅源(PromptShield Cloud)。
- [ ] 上架 GitHub Marketplace,进入 awesome-claude-code / awesome-ai-coding 等清单。
## 许可证与贡献
[MIT](./LICENSE)。欢迎 PR 与 Issue——发现误报或漏报?带上能复现的最小样本,到 [Issues](https://github.com/SuperMarioYL/promptshield/issues) 开一条,我们会据此收紧规则。
变更记录见 [CHANGELOG.md](./CHANGELOG.md)。
## 分享
```
PromptShield — 在你的 Claude Code / Cursor agent 读取代码之前,扫描其中隐藏的提示词注入。
离线、零 API key、一行接入 CI。现场拦下 Reddit 那条 rm -rf 数据销毁注入。
https://github.com/SuperMarioYL/promptshield
```
PromptShield 是在 Claude Code / Cursor 读取代码前,拦截隐藏注入指令的扫描器。
## 目录 - [为什么需要它](#为什么需要它) - [60 秒上手](#60-秒上手) - [演示](#演示) - [它扫描什么](#它扫描什么) - [集成到 CI](#集成到-ci) - [基线工作流](#基线工作流) - [对比同类方案](#对比同类方案) - [配置项](#配置项) - [付费 · PromptShield Cloud](#付费--promptshield-cloud) - [路线图](#路线图) - [许可证与贡献](#许可证与贡献) - [分享](#share-this) ## 为什么需要它 Claude Code、Cursor 这类 coding agent 会把第三方仓库、PR、代码注释、commit message **直接读进上下文**,然后照着读到的内容去执行——但在仓库和 agent 之间,没有任何一层把这些代码**当作提示词**来扫描。 这正是 [r/LocalLLaMA 那条 "dev sneaks data-nuking prompt injection into their code"](https://www.reddit.com/r/LocalLLaMA/comments/1trdnap/fed_up_with_vibe_coders_dev_sneaks_datanuking/) 帖子揭露的攻击面:有人把一句 `rm -rf /` 的销毁指令藏进注释里,等着别人的 agent 读到并执行。人工 review 的眼睛追不上 agent 的阅读吞吐——这是一种**结构性的吞吐量不对称**,不是"再仔细一点"能解决的。 **为什么是现在:** agentic coding 的普及,把"agent 读到的代码"变成了"agent 会照做的代码"。Cursor 已有上百万开发者,[affaan-m/everything-claude-code](https://github.com/affaan-m/everything-claude-code) 这类 Claude Code 实战仓库让 agent 例行拉取外部 PR 与 vendored OSS。两年前主流还是人把片段复制进聊天框,agent 没有对外部仓库自主读取并执行的回路;MCP 与工具调用的扩张才让注入有了通往"动作"的路径。PromptShield 就是在 agent 读取之前,把这条注入面卡在 CI 里——离线、零 API key、几秒出结果。 PromptShield 引入的新原语是 **Finding over a code-as-prompt surface**:把 agent 将要*读取*的源文本视为攻击向量,而不是当作可执行代码或要做 CVE 扫描的依赖包。这与聊天越狱扫描(Garak)和依赖漏洞扫描(Socket / Snyk)是不同的威胁类别。 ##
示例输出(扫描内置的恶意 PR 复现样本)
``` $ promptshield scan ./tests/fixtures/malicious_pr PromptShield findings ┏━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━┓ ┃ Sev ┃ Rule ┃ Category ┃ Location ┃ Surf ┃ ┡━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━┩ │ HIGH │ PS001-instruction-override-d… │ instruction_ov… │ utils.py:11 │ com… │ │ HIGH │ PS010-destructive-shell │ data_destructi… │ utils.py:12 │ com… │ │ HIGH │ PS011-destructive-instruction │ data_destructi… │ utils.py:17 │ doc… │ │ HIGH │ PS020-exfil-secrets │ exfiltration │ utils.py:17 │ doc… │ └──────┴───────────────────────────────┴─────────────────┴─────────────┴──────┘ Scanned 31 surfaces · 8 HIGH · 0 MED · 0 LOW ✗ HIGH findings present — exit 1 (CI gate failed). ``` 每一条 HIGH 都会附带一句 `why`,说明它为什么是注入——精度优先于召回,让你信得过每一个告警。标签:AI安全, Chat Copilot, GitHub Action, SOC Prime, 云安全监控, 开发工具, 提示词注入防护, 逆向工具, 静态分析