transilienceai/whitney
GitHub: transilienceai/whitney
开源静态 AI 安全扫描器,填补通用规则在间接提示注入上的检测空白。
Stars: 1 | Forks: 0
# Whitney
**开源静态 AI 安全扫描器 — 发现传统扫描器遗漏的提示注入模式,且不在每次运行时消耗 LLM API 信用。**
Whitney 是精选的 [Semgrep](https://semgrep.dev) 规则集、轻量 Python 包装器、可选 LLM-as-judge 分类层以及 AI 依赖 SBOM 提取器的组合。零自定义 SAST。默认路径零 LLM 调用。目前仅支持 Python(路线图包含 TS/JS/Go)。
```
pip install whitney
whitney scan ./your-repo
```
```
SEVERITY FILE :LINE TITLE
critical app/handlers/chat.py :42 LangChain SQLDatabaseChain with user-controlled question (P2SQL)
critical app/main.py :89 pandas_dataframe_agent.run with user input — arbitrary code exec
high app/api.py :17 Flask handler interpolates request.json into LLM prompt
high app/rag.py :55 WebBaseLoader fetched content flows into LLM chain unfiltered
```
## 检测范围
**15 种源类型**的提示注入:
| 类别 | 覆盖的源类型 |
|---|---|
| **直接注入** | `direct_http`(Flask/FastAPI/Django)、`direct_cli`(argparse/click/stdin)、`direct_voice`(Whisper/Twilio SpeechResult) |
| **间接获取** | `indirect_rag`(Chroma/Pinecone/Weaviate/pgvector)、`indirect_web_fetch`(requests/WebBaseLoader/SeleniumURLLoader)、`indirect_file_upload`(PyPDFLoader/UnstructuredFileLoader)、`indirect_email`(SES/SNS)、`indirect_search`(Tavily/SerpAPI/Brave/Google CSE) |
| **间接代理** | `indirect_tool_response`(LangChain 工具返回值)、`indirect_mcp`(MCP call_tool 响应)、`indirect_a2a`(CrewAI/LangGraph 代理间上下文交接) |
| **间接存储** | `indirect_db_stored`(数据库查询结果进入提示)、`indirect_memory_stored`(Mem0/Zep/LangChain 内存回放) |
| **跨模态** | `cross_modal_image_ocr`(pytesseract/easyocr)、`cross_modal_unicode`(标签块/ZWJ/同形异义字) |
此外,**仅凭存在性触发的关键汇点**:LangChain `PALChain` / `PythonAstREPLTool`(CVE-2023-36258 类别)以及 `SQLDatabaseChain` / `create_sql_agent` / `NLSQLTableQueryEngine`(P2SQL 类别)。
并提供一个 **AI 依赖 SBOM**(`whitney sbom`)—— 针对 `requirements.txt`、`pyproject.toml`、`package.json` 以及 `model=...` 赋值中发现的 AI SDK 与模型的 CycloneDX 1.5 清单,包含一个已知易受攻击的 SDK 版本对照表。
## 存在原因
通用静态 AI 规则集(Semgrep `p/ai-best-practices`、Bandit、Agent Audit)仅能捕获约 30–50% 的真实提示注入模式,且对间接注入(RAG 检索、Web 获取、工具响应、代理间上下文交接)完全不可见。Whitney 旨在填补这一空白。
| 扫描器 | 语料召回 | 语料精确率 | 语料 F1 | 在 6 个真实 AI 仓库上的发现 |
|---|---|---|---|---|
| **Whitney 默认(无 LLM)** | **1.000** | 0.897 | 0.945 | **47** |
| **Whitney 分类(可选)** | **1.000** | **1.000** | **1.000** | **47** |
| Semgrep `p/ai-best-practices` | 0.500 | 0.867 | 0.634 | 14 |
| Agent Audit 0.18.2 | 0.308 | 0.571 | 0.400 | — |
| Bandit / Semgrep `p/security-audit` | 0.000 | — | — | 0 |
语料库包含 **35 个手工标注的测试用例**(26 个正例 + 9 个负例),覆盖全部 15 种源类型,每个用例附带包含 `source_url`、`source_commit`、`vuln_subtype` 与标注理由的 YAML 侧车文件。可通过 `python -m tests.corpus.eval` 在本地复现。
在 **5 个从未用于开发规则的盲测仓库** — `aimaster-dev/chatbot-using-rag-and-langchain`、`Lizhecheng02/RAG-ChatBot`、`SachinSamuel01/rag-langchain-streamlit`、`streamlit/example-app-langchain-rag`、`Vigneshmaradiya/ai-agent-comparison` — 中,Whitney 产生 11 个发现,其中 9 个为真阳性、2 个为开发者在 `main()` 测试脚手架中的假阳性。**精确率达 81.8%,经人工审计。** 完整审计表位于 `tests/corpus/DIFFERENTIAL.md`。
## 已知防御机制
仅当在不可信内容到达 LLM **之前** 调用了 **供应商防护栏** 或 **正确的 LLM-as-judge 分类** 时,Whitney 才会抑制告警:
- AWS Bedrock Guardrails(`apply_guardrail`、`GuardrailIdentifier=` 在 `invoke_model` 上)
- Azure AI 内容安全 / 提示防护(`ContentSafetyClient.detect_jailbreak`)
- Lakera Guard(`api.lakera.ai` 或 SDK 调用)
- NeMo Guardrails(`LLMRails.generate`、包装式调用)
- DeepKeep AI 防火墙(`dk_request_filter`)
- OpenAI 审核(`client.moderations.create`)
- 正确的 LLM-as-judge(通过可选分类层判定;详见 [`docs/TRIAGE.md`](docs/TRIAGE.md))
**明确不计入的弱防御**:正则/Pydantic 字符串校验、长度限制、关键词黑名单、系统提示警告。均可通过 Unicode、同形异义字、Base64、语言切换或改写绕过。Whitney 仍会在 `details["defense_present"]` 中记录其存在,以便修复建议指向更强的替代方案。
## 架构
```
whitney/
├── scanner.py # public scan_repository(path) entry point
├── semgrep_runner.py # subprocess wrapper around Semgrep CLI
├── llm_triage.py # opt-in LLM-as-judge classifier (Claude Opus or mock)
├── sbom.py # AI dependency SBOM scanner (CycloneDX 1.5)
├── models.py # stdlib @dataclass Finding (no pydantic)
├── cli.py # `whitney scan|sbom|version` command-line interface
└── rules/
├── prompt_injection_taint.yaml # Semgrep taint mode — flow detection
├── prompt_injection_critical_sinks.yaml # PAL chain + SQL chain — presence alone
└── prompt_injection_structural.yaml # CrewAI / LLMChain / WebBaseLoader idioms
```
三个 Semgrep 规则文件,各具不同的检测理念:
1. **`prompt_injection_taint.yaml`** — 单一整合污点规则。50+ 模式源、25+ 模式净化、40+ 模式汇点,配合针对 UI/存储形态的精确 `pattern-not` 排除。仅在实际数据流存在漏洞时捕获直接与间接注入。
2. **`prompt_injection_critical_sinks.yaml`** — AST 模式规则,针对**仅凭存在即关键**的汇点(无需污点流):PAL 链、SQL 链、任意代码路径的工具调用执行器。
3. **`prompt_injection_structural.yaml`** — 针对**代码结构层面**的漏洞 AST 规则(而非数据流):CrewAI `Task(..., context=[upstream_task])` 代理交接、LangChain `LLMChain` 惯用写法、`WebBaseLoader` + 链、`PdfReader` + LLM。
每条规则均通过 `pattern-not-inside: def $F(...): ... $BEDROCK.apply_guardrail(...) ...` 对已识别防御机制实现函数级抑制。
总计 Python 代码约 800 行,分布在 `scanner.py`、`semgrep_runner.py`、`llm_triage.py`、`sbom.py`、`models.py`、`cli.py` 中。无自定义污点引擎、无 Tree-sitter 遍历器、无自定义 AST 分析。全部工作由 Semgrep 完成。
## 零 LLM 默认,可选分类
默认扫描路径(`whitney scan ./repo` 或未设置环境变量时的 `scan_repository(path)`)**零 LLM 调用**,并在各次运行间产生字节级一致的输出。可选模式在此基础上叠加:
```
# 默认 — 仅 Semgrep,无 API 调用,完全可复现
whitney scan ./my-repo
# 模拟分诊 — 结构启发式用于 LLM-as-judge 正确性,无 API 调用
WHITNEY_STRICT_JUDGE_PROMPTS=1 WHITNEY_TRIAGE_MOCK=1 whitney scan ./my-repo
# 实际分诊 — 调用 Claude Opus 对 LLM-as-judge 提示进行分类
export ANTHROPIC_API_KEY=sk-ant-...
WHITNEY_STRICT_JUDGE_PROMPTS=1 whitney scan ./my-repo
```
真实模式分类使用 `claude-opus-4-6` 在 `temperature=0` 下进行,判决结果以 `(model_id, prompt_version, code_hash)` 为键缓存,因此对未变更代码的重复扫描无需消耗成本。分类调用上限为每次扫描 50 次,错误时 fail-open。完整操作说明、成本估算与故障排查请参见 [`docs/TRIAGE.md`](docs/TRIAGE.md)。
## 路径排除
路径排除自动应用于:`venv`、`.venv`、`env``__pycache__`、`node_modules`、`tests`、`test_*`、`fixtures`、`examples`、`docs`、`dist`、`build`、`site-packages`。测试固件内部的发现视为开发者视角的假阳性,无论其技术正确性如何。
## 输出内容
```
from whitney import scan_repository
findings = scan_repository("./my-repo")
for f in findings:
print(f.severity.value, f.check_id, f.details["file_path"], f.details["line_number"])
print(" CWE:", f.details["cwe"])
print(" OWASP LLM Top 10:", f.details["owasp"])
print(" OWASP Agentic:", f.details["owasp_agentic"])
```
每个发现包含:
- `check_id`、`title`、`description`、`severity`、`remediation`
- `details.file_path`、`details.line_number`、`details.end_line`、`details.code_snippet`
- `details.cwe` — 例如 `["CWE-94"]`
- `details.owasp` — OWASP LLM Top 10 年份特定标签,例如 `["LLM01:2025"]`
- `details.owasp_agentic` — OWASP 代理 Top 10,例如 `["AA01:2026"]`
- `details.confidence` — `HIGH` / `MEDIUM` / `LOW`
- `details.technology` — 规则中识别的框架
CWE 与两项 OWASP 家族直接在规则 YAML 元数据中提供;监管框架增强(ISO 42001、EU AI Act、NIST AI RMF、MITRE ATLAS)由消费 Whitney 输出的下游工具负责;[Shasta](https://github.com/transilienceai/shasta) 合规包是其中一种消费者。
## 已知限制
- **仅限单文件。** Semgrep OSS 污点分析仅限于过程内与单文件。跨文件流程(污点源在 `handlers/chat.py`、LLM 汇点在 `services/llm.py`)无法追踪。经验证在 3 个真实仓库中所有漏洞路径均为单文件,但大型单体仓库可能需要 Semgrep Pro 或未来的结构规则扩展。
- **仅 Python。** TypeScript / JavaScript / Go 支持在路线图上;一旦补全源/汇分类体系,规则编写方式可直接迁移。
- **防护策略验证不在范围内。** 若开发者调用 `bedrock.apply_guardrail(GuardrailIdentifier="xxx")`,Whitney 假定策略 `"xxx"` 确实覆盖提示注入。验证策略内容需在扫描时拉取 AWS 策略定义。
- **盲测中 2 个已知 FP 模式。** 若开发者 `main()` 测试脚手架中包含硬编码查询且辅助文件导入 RAG 检索器,可能产生假阳性。在从未扫描的真实代码上精确率达 81.8%,保留 `def main():` 入口点以确保合法 CLI 应用仍被捕获的代价。
## 参见
- [**docs/TRIAGE.md**](docs/TRIAGE.md) — 可选 LLM-as-judge 分类层的操作指南
- [**docs/SCANNER.md**](docs/SCANNER.md) — 规则文件的深层架构说明
- [**tests/corpus/DIFFERENTIAL.md**](tests/corpus/DIFFERENTIAL.md) — 与所有通用扫描器的完整基准对比
- [**tests/corpus/README.md**](tests/corpus/README.md) — 标注语料库的结构说明与新增用例方法
- [**Shasta**](https://github.com/transilienceai/shasta) — 云端 AI 治理姊妹项目(涵盖 AWS Bedrock / SageMaker、Azure OpenAI / ML)、监管框架映射(ISO 42001、EU AI Act、NIST AI RMF、MITRE ATLAS)、策略生成、仪表板与报告
## 许可证
Apache-2.0。参见 [LICENSE](LICENSE)。
标签:AI依赖SBOM, AI安全, AI安全扫描, Chat Copilot, GraphQL安全矩阵, LLM-as-judge, PB级数据处理, prompt注入检测, Python安全工具, SAST, Semgrep规则集, URL发现, XML 请求, 云安全监控, 代码安全, 安全专业人员, 安全合规, 安全开发, 安全扫描器, 安全扫描工具, 安全检测, 安全漏洞检测, 安全运维, 开源安全扫描, 提示注入, 漏洞枚举, 盲注攻击, 网络代理, 逆向工具, 集群管理, 静态分析