matinfo/pii-airlock
GitHub: matinfo/pii-airlock
一款基于 Microsoft Presidio 构建的本地可逆 PII 脱敏工具,通过 CLI 管道、通用网关代理和 Claude Code hooks 三种方式防止真实个人数据在使用 AI 工具时泄露。
Stars: 0 | Forks: 0
# pii-airlock
[](https://github.com/matinfo/pii-airlock/actions/workflows/ci.yml)
[](https://pypi.org/project/pii-airlock/)


[](https://github.com/astral-sh/ruff)
[](LICENSE)
**[安装](#install) · [网关](#universal-gateway-any-provider) · [CLI](#cli-usage) · [Claude Code](#claude-code-hooks) · [所有代理 →](AGENTS.md) · [安全](#-security-the-mapping-file-holds-real-pii)**
基于 [Microsoft Presidio](https://microsoft.github.io/presidio/) 构建的本地、**可逆** PII 匿名化工具。
将真实的个人数据替换为稳定的占位符 token,将脱敏后的文本发送给模型,然后再从本地映射文件中将原始数据替换回来。
**检测和替换完全在你的本地机器上运行。** CLI 管道和 Claude Code hooks 不会向任何地方发送数据。可选的网关*确实*会将流量转发到你指定的提供商 —— 但这仅发生在 PII 被替换为 token **之后**。真实的个人数据永远不会到达提供商处。
支持在 **macOS、Linux 和 Windows** 上运行,要求 Python ≥ 3.10。默认支持检测英语和法语,可通过配置支持 [任何 spaCy 语言](#adding-a-language)。
## 三种工作流
| 工作流 | 功能 | 适用范围 |
|---|---|---|
| **网关** (proxy) | 在传输过程中进行透明的脱敏/还原 | OpenAI, Codex, Anthropic, Gemini, Cursor, Continue, … |
| **CLI 管道** | `pii-airlock scrub` → LLM → `pii-airlock restore` — 可逆、可脚本化 | 任何场景 |
| **Claude Code hooks** | 在 PII 到达模型之前(包括 prompt 和工具数据中)进行检测 | Claude Code |
这三种方式共享同一个检测引擎 —— 提供商特定的知识仅存在于微小的 payload adapter 中。
## 安装
要求 Python ≥ 3.10 以及 [pipx](https://pipx.pypa.io/)(推荐)或 pip。
在 macOS、Linux 和 Windows 上的工作方式完全相同。
```
pipx install "pii-airlock[proxy]" # gateway-ready install (recommended)
pii-airlock download-models # one-time: fetch NLP models (en + fr)
```
从源码安装最新版本(在版本发布到 PyPI 之前):
```
pipx install git+https://github.com/matinfo/pii-airlock
```
可选的格式支持(纯文本、CSV 和 JSON 默认即可使用):
```
pipx inject pii-airlock 'pii-airlock[proxy]' # universal gateway (any provider)
pipx inject pii-airlock 'pii-airlock[docx]' # Word .docx
pipx inject pii-airlock 'pii-airlock[pdf]' # PDF (text extraction)
pipx inject pii-airlock 'pii-airlock[all]' # everything
```
如果 `download-models` 报告你的解释器没有 `pip`(在 `pipx` 中很常见),请运行打印出的 `pipx inject pii-airlock ""` 命令。该命令现在会为你打印出精确的 model wheel URL。
## 通用网关(任何提供商)
该网关是一个**本地反向代理**。通过客户端的 base-URL 设置将任何 LLM 客户端指向它;代理会清除出站请求中的 PII,并在响应中还原真实值 —— 包括流式响应。客户端完全察觉不到差异。
```
your app ──http──▶ pii-airlock gateway ──https──▶ provider API
◀─────────── (restore) ◀─────────── (scrub)
```
```
# 如果您通过 "pii-airlock[proxy]" 安装,则已包含
# pipx inject pii-airlock 'pii-airlock[proxy]'
pii-airlock proxy # listens on http://127.0.0.1:8745
```
然后在你使用的任何客户端中设置 base URL:
```
# OpenAI SDK / Codex CLI / 大多数兼容 OpenAI 的工具
export OPENAI_BASE_URL=http://127.0.0.1:8745/openai
# Anthropic SDK
export ANTHROPIC_BASE_URL=http://127.0.0.1:8745/anthropic
# Google Gemini — 使用 base path
# https://generativelanguage.googleapis.com -> http://127.0.0.1:8745/gemini
```
**为什么使用代理?** 这是唯一的一个拦截点,它同时具备透明性(只需配置一次)、双向性(自动清除 prompt *并* 还原响应)、通用性(每个提供商都使用 HTTP 通信)和可强制执行性(只知道代理 URL 的客户端无法绕过它泄露数据)。
- **无需 TLS 拦截。** 你的客户端通过纯 HTTP 与 `localhost` 通信;由代理向上游发起真实的 HTTPS 调用。无需安装任何证书。默认绑定在 `127.0.0.1`。
- **身份验证直接透传**,pii-airlock 绝不会记录请求头或请求体。(uvicorn 在 `warning` 日志级别运行,因此也不会记录请求行。)
- **每个请求使用全新的内存映射** —— 网关不会向磁盘写入任何内容。
- **并发安全:** 检测过程通过锁进行了串行化处理,因此可以安全地在并发请求之间共享引擎。
**提供商覆盖范围** —— 三种传输格式,每种在 `pii_scrub/payload.py` 中都有对应的 adapter:
| 路由 | 传输格式 | 覆盖范围 | 流式传输 |
|---|---|---|---|
| `/openai` | OpenAI Chat Completions | OpenAI, Codex, Cursor, Continue, Ollama, LiteLLM, vLLM, … | SSE ✅ |
| `/anthropic` | Anthropic Messages | Claude SDK, 兼容 Claude 的工具 | SSE ✅ |
| `/gemini` | Gemini generateContent | Google Gemini | SSE ✅ · array-stream 已缓冲 |
Adapter 已通过单元和集成测试(模拟上游)针对每个提供商的文档请求/响应结构进行了验证。添加一个提供商 = 只需编写一个小型的 adapter。
## CLI 用法
### 可逆的脱敏 → 还原管道
```
# 1. 清理 — 将 PII 替换为 tokens,并保存映射文件
echo "Contacte Jean Dupont à jean@acme.fr" \
| pii-airlock scrub --map /tmp/m.pii-map.json
# → Contacte à
# 2. 将清理后的文本发送到您的 LLM …
# 3. 恢复 — 在模型的响应中替换回 tokens
echo "J'ai répondu à via ." \
| pii-airlock restore --map /tmp/m.pii-map.json
# → J'ai répondu à Jean Dupont via jean@acme.fr.
```
相同的值总是会获得相同的 token(``),因此模型依然能够识别共指关系。
### 仅检测不修改文本
```
pii-airlock detect notes.txt
# PERSON 0.85 [9:21] 'Jean Dupont'
# EMAIL_ADDRESS 0.99 [24:38] 'jean@acme.fr'
```
### 文件格式
```
pii-airlock scrub report.csv -o report.scrubbed.csv
pii-airlock scrub data.json -o data.scrubbed.json
pii-airlock scrub contract.docx -o contract.scrubbed.docx # requires [docx]
pii-airlock scrub scan.pdf # requires [pdf] → text on stdout
```
### 其他选项
```
pii-airlock scrub prompt.txt --no-map # irreversible one-way scrub
pii-airlock scrub --lang fr,en # explicit language list
pii-airlock scrub --threshold 0.7 # raise confidence cutoff
pii-airlock scrub input.txt -o out.txt --map secrets.pii-map.json
```
## Claude Code hooks
注册防护拦截,在 PII 到达模型之前将其拦截:
```
pii-airlock install-hook # both events, project .claude/settings.json
pii-airlock install-hook --scope user # ~/.claude/settings.json (all projects)
pii-airlock install-hook --event tool # PreToolUse only
pii-airlock install-hook --event prompt # UserPromptSubmit only
```
| 泄露途径 | 覆盖方式 |
|---|---|
| 你在 prompt 中输入的 PII | `UserPromptSubmit` |
| Claude 读取的文件中的 PII | `PreToolUse` |
| Claude 运行的 shell 命令中的 PII | `PreToolUse` |
这些 hook **负责检测并警告/询问** —— 它们不会静默重写 payload(hook API 不支持原地重写)。如果需要静默、可逆的重写,请使用上方的 CLI 管道。
在配置中设置 `hook_decision: deny` 可直接阻止而不是询问。
## 配置
覆盖链(从低到高优先级):
```
bundled defaults → ~/.config/pii-airlock/config.yaml → ./.pii-airlock.yaml → CLI flags
```
默认配置(`config.default.yaml`):
```
languages: [en, fr]
models:
en: en_core_web_lg
fr: fr_core_news_lg
score_threshold: 0.5
entities: [] # empty = all entities Presidio recognizes
hook_decision: ask # ask (surface + confirm) | deny (block)
```
### 添加语言
```
python -m spacy download de_core_news_lg
```
```
# .pii-airlock.yaml
languages: [en, fr, de]
models:
de: de_core_news_lg
```
## 保证与限制
**pii-airlock 的保证**
- **精确的可逆性。** 引擎进行过 token 化的任何值都能通过映射逐字节地还原。对于被 token 化的片段,`restore(scrub(text))` 可以完全还原。
- **确定性。** 在同一个映射中,相同的值会获得相同的 token,因此模型可以保留共指关系。
- **token 是不透明且安全的。** 还原的值会通过正确的 JSON 编码重新插入;包含引号、换行符或 `<…>` 的值不会破坏 payload。
- **纯本地检测。** 检测过程绝不会发起网络调用。只有网关会转发(已经脱敏的)流量。
**它*不能*保证的内容 —— 请务必阅读**
- **检测是尽力而为的,并不彻底。** Presidio + spaCy 基于统计学;它们会漏掉或错误标记实体(在使用 `_sm` 模型或处理包含奇怪空格的电话号码时尤为明显)。pii-airlock 只是降低了风险 —— 它**不能**保证移除所有的 PII。请谨慎审查敏感材料;如有需要,请提高 `score_threshold` 或添加自定义 recognizer。
- **网关作用范围。** 仅对生成 endpoint(chat/messages/generateContent)进行脱敏。Embeddings 及其他 endpoint 将原样透传。Token 仅在消息 **content** 中还原,不会在模型可能输出的 tool-call/function 参数内部还原。
- **Gemini 数组流式传输**(不使用 `alt=sse`)会被缓冲,然后作为一个完整的响应还原,而不是实时流式传输。
- **`.docx`** 的脱敏过程会将修改过的段落重写为单一的 run,因此这些段落内的行内格式不会被保留。**PDF** 仅支持提取 → 输出脱敏后的文本(不会重新渲染 PDF)。
## 平台支持
已在 CI 中的 **Linux、macOS 和 Windows** 上经过测试(Linux 上为 Python 3.10–3.14;macOS/Windows 上为 3.12–3.13)。一个平台细节:
- 映射文件在 **POSIX**(macOS/Linux)上以 **`0600`** 权限创建。**在 Windows 上** `chmod` 无效;文件会继承你账户的默认 ACL —— 在主目录中通常已经是用户私有的。但无论如何,请将映射文件视为机密(见下文)。
## ⚠ 安全:映射文件包含真实的 PII
`*.pii-map.json` 以纯文本形式包含**真实的个人数据**。
- 创建时仅限所有者可读(POSIX 上为 `0600`;Windows 上为默认的用户 ACL —— 见上文)。
- 内置的 `.gitignore` 已排除 `*.pii-map.json` 和 `*.pii-map.*`。
- **永远不要提交映射文件。**
- 当你不再需要还原数据时,请删除它们。
- 当不需要可逆性时,请使用 `--no-map`。
- 网关仅将映射保留在内存中,并在每次响应后将其丢弃。
## 开发
```
git clone https://github.com/matinfo/pii-airlock
cd pii-airlock
pip install -e ".[dev]"
ruff check .
pytest -q
```
单元测试 stub 了 Presidio/spaCy,并且完全在离线状态下运行。要进行实时端到端检查,请先运行 `pii-airlock download-models`,然后运行:
```
echo "Call John Smith at john@example.com" | pii-airlock scrub --map /tmp/test.pii-map.json
```
## 许可证
[MIT](LICENSE) © pii-airlock 贡献者
标签:AI隐私保护, API网关, Blue Team, Microsoft Presidio, PII脱敏, Python, 无后门, 逆向工具