yemrealtanay/llm-hasher
GitHub: yemrealtanay/llm-hasher
llm-hasher 是一个本地运行的 PII 脱敏中间件,在文本发送给 LLM 前自动检测敏感信息并替换为加密 token,返回时再还原原始值,全程零云端依赖。
Stars: 24 | Forks: 0
# 🔏 llm-hasher
**面向 LLM pipeline 的隐私优先 PII tokenization 中间件。**
在发送给任何 LLM 之前,将敏感数据替换为不透明的 token。
在返回时恢复原始值。你的 vault,你的密钥。
[](LICENSE)
[](go.mod)
[](docker-compose.yml)
[](https://ollama.ai)
[快速开始](#quick-start) · [API 参考](#api-reference) · [工作原理](#how-it-works) · [贡献指南](#contributing)
## 问题所在
你将客户记录、支持聊天或文档发送给 LLM 进行摘要、分析或分类。这些文本包含真实的信用卡号、家庭住址、姓名和国家身份证号——它们以明文形式发送给第三方 API,并可能存储在你自己的数据库中。
**llm-hasher 位于你的应用程序和 LLM 之间。**它会剔除敏感数据,发送干净的 token,并在响应中将真实值还原。LLM 永远不会看到真实的 PII。你的数据库存储的是 token,而不是明文。
## 工作原理
```
Your App ──► POST /v1/tokenize ──► llm-hasher ──► tokenized text
│
detects PII locally
(Ollama, no cloud)
stores in encrypted vault
Your App ──► [your LLM call with tokenized text]
Your App ──► POST /v1/detokenize ──► llm-hasher ──► original text restored
```
**示例:**
```
Input: "Hi, my card is 4111-1111-1111-1111 and email is john@example.com"
Output: "Hi, my card is CREDIT_CARD_john12_4f8a2b and email is EMAIL_john12_9c3d1a"
```
LLM 接收到的是 tokenization 后的版本——它仍然理解上下文(“用户提供了一个 CREDIT_CARD”),但永远看不到真实的号码。当 LLM 的响应返回时,你将其通过 `/v1/detokenize` 处理,就能找回真实的值。
## 功能
- **本地 PII 检测** —— 使用在你自己的服务器上运行的 [Ollama](https://ollama.ai),检测过程中没有任何数据离开你的基础设施
- **混合检测** —— 针对结构化类型(信用卡、电子邮件、电话号码、IBAN、IP)使用快速 regex,针对上下文相关类型(姓名、地址、国家身份证、护照)使用 LLM
- **加密 vault** —— token 与值的映射存储在 SQLite 中,并采用 AES-256-GCM 加密,密钥永远不会离开进程
- **使用你自己的 ID** —— 使用你业务领域中的 `context_id`(`"zoom_call_789"`、`"contact_123"`),而无需跟踪外部的 UUID
- **去重** —— 同一上下文中的相同 PII 始终映射到同一个 token,因此 LLM 能够一致地进行推理
- **性能** —— 大段文本会被分块并由并行的 goroutine 处理;detokenization 采用单次多字符串替换
- **双重用途** —— 作为独立的 HTTP 服务运行,或作为 Go 库导入
- **零运行依赖** —— 单一二进制文件 + SQLite;Docker Compose 已包含 Ollama
## 支持的 PII 类型
| 类型 | 检测方法 |
|---|---|
| `CREDIT_CARD` | Regex (Visa, Mastercard, Amex, Discover) |
| `EMAIL` | Regex |
| `PHONE_NUMBER` | Regex (国际格式) |
| `IP_ADDRESS` | Regex (IPv4) |
| `BANK_ACCOUNT` | Regex (IBAN) |
| `PERSON_NAME` | Ollama LLM (上下文感知) |
| `HOME_ADDRESS` | Ollama LLM (上下文感知) |
| `NATIONAL_ID` | Ollama LLM (SSN, TC Kimlik, NIN 等) |
| `PASSPORT` | Ollama LLM |
| `DATE_OF_BIRTH` | Ollama LLM |
## 快速开始
### Docker (推荐)
```
git clone https://github.com/yemrealtanay/llm-hasher
cd llm-hasher
make docker-up
```
就是这样。Docker Compose 会启动 Ollama,自动拉取 `llama3.2:3b` (~2GB),并在端口 `8080` 上启动服务。
```
# 检查其是否正在运行
curl http://localhost:8080/healthz
# {"status":"ok"}
```
### 裸金属
```
git clone https://github.com/yemrealtanay/llm-hasher
cd llm-hasher
make setup # installs Ollama if missing, pulls model, builds binary
make run
```
## API 参考
### `POST /v1/tokenize`
检测文本中的 PII,并使用有 vault 支持的 token 进行替换。
**请求:**
```
{
"text": "My card is 4111-1111-1111-1111 and I live at 123 Main St, Boston",
"context_id": "zoom_call_789",
"ttl": "24h"
}
```
| 字段 | 必填 | 描述 |
|---|---|---|
| `text` | 是 | 需要进行 tokenization 的文本 |
| `context_id` | 否 | 用于限定映射范围的你自己的 ID。如果省略,系统会生成并返回一个随机 ID。请使用你业务领域中的稳定 ID 以便后续进行 detokenization。 |
| `ttl` | 否 | Token 过期持续时间(例如 `"24h"`、`"7d"`)。`"0"` 或省略 = 永不过期。 |
**响应:**
```
{
"tokenized_text": "My card is CREDIT_CARD_zoomc7_4f8a2b and I live at HOME_ADDRESS_zoomc7_9c3d1a",
"context_id": "zoom_call_789",
"entities": [
{ "token": "CREDIT_CARD_zoomc7_4f8a2b", "pii_type": "CREDIT_CARD" },
{ "token": "HOME_ADDRESS_zoomc7_9c3d1a", "pii_type": "HOME_ADDRESS" }
]
}
```
### `POST /v1/detokenize`
在包含 token 的文本中恢复原始值。
**请求:**
```
{
"text": "The user provided CREDIT_CARD_zoomc7_4f8a2b as payment.",
"context_id": "zoom_call_789"
}
```
**响应:**
```
{
"original_text": "The user provided 4111-1111-1111-1111 as payment."
}
```
### `POST /v1/tokenize/batch`
在单个请求中对多个文本进行 tokenization(并行处理)。
**请求:**
```
{
"items": [
{ "text": "Call with John, card 4111-1111-1111-1111", "context_id": "call_1" },
{ "text": "Support ticket from jane@example.com", "context_id": "ticket_42" }
]
}
```
### `DELETE /v1/contexts/{context_id}`
硬删除某个上下文的所有 token 映射。适用于合规/被遗忘权场景。
```
curl -X DELETE http://localhost:8080/v1/contexts/zoom_call_789
# 204 No Content
```
## 真实用例模式
### 模式 1:LLM 代理 (记录分析)
```
# 1. 发送到 LLM 前进行 tokenize
resp = requests.post("http://localhost:8080/v1/tokenize", json={
"text": transcript,
"context_id": f"zoom_{call_id}"
})
tokenized = resp.json()
# 2. 将 tokenize 后的文本发送到你的 LLM
llm_response = openai.chat.completions.create(
messages=[
{"role": "system", "content": "Summarize this call transcript."},
{"role": "user", "content": tokenized["tokenized_text"]}
]
)
# 3. 对 LLM 响应进行 detokenize
final = requests.post("http://localhost:8080/v1/detokenize", json={
"text": llm_response.choices[0].message.content,
"context_id": f"zoom_{call_id}"
})
print(final.json()["original_text"])
```
### 模式 2:数据库存储
```
# 在保存到 DB 之前 — 进行无过期时间的 tokenize
resp = requests.post("http://localhost:8080/v1/tokenize", json={
"text": transcript,
"context_id": f"contact_{contact_id}",
"ttl": "0" # no expiry — lives as long as the record
})
db.save(contact_id=contact_id, transcript=resp.json()["tokenized_text"])
# 向用户显示时 — 实时进行 detokenize
row = db.get(contact_id)
resp = requests.post("http://localhost:8080/v1/detokenize", json={
"text": row.transcript,
"context_id": f"contact_{contact_id}"
})
show_to_user(resp.json()["original_text"])
```
### 模式 3:Go 库
```
import "github.com/yemrealtanay/llm-hasher/pkg/hasher"
h, err := hasher.New(
hasher.WithOllama("http://localhost:11434", "llama3.2:3b"),
hasher.WithVault("data/vault.db", ""),
)
defer h.Close()
result, err := h.Tokenize(ctx, transcript, "zoom_call_789", nil)
// result.Text contains tokenized transcript
original, err := h.Detokenize(ctx, llmResponse, "zoom_call_789")
```
## 配置
将 `configs/config.example.yaml` 复制到 `configs/config.yaml` 并进行调整:
```
ollama:
model: "llama3.2:3b" # or llama3.1:8b for higher recall
confidence_threshold: 0.7 # lower = more aggressive detection
chunk_size: 800 # words before parallel chunking kicks in
vault:
default_ttl: "24h" # "0" for no expiry
```
**加密密钥**(生产环境):将 `VAULT_KEY` 环境变量设置为 64 位十六进制字符(32 字节)。如果未设置,系统会自动生成一个密钥并保存到 `data/vault.key`。
```
# 生成密钥
openssl rand -hex 32
# 添加到 .env:
# VAULT_KEY=标签:AI风险缓解, DLL 劫持, Docker, EVTX分析, Go, LLM评估, Ollama, Ruby工具, 中间件, 大语言模型, 安全防御评估, 数据脱敏, 日志审计, 网络安全, 请求拦截, 隐私保护