bvolpato/promptcloak
GitHub: bvolpato/promptcloak
PromptCloak 是一款本地运行的 OpenAI 兼容代理与 Python 库,在提示词发送到云端模型前自动扫描并脱敏其中的密钥、密码与 token 等敏感信息。
Stars: 1 | Forks: 0
# PromptCloak
**本地 OpenAI 兼容代理和 Python 库,可在提示词离开您的机器之前对其进行脱敏处理。**
PromptCloak 可以位于编码代理、SDK 或应用程序与任何 OpenAI 兼容的后端之间。它也可以作为小型 Python 过滤器在进程内运行。它在本地扫描值,对 API 密钥、密码、token、私钥、JWT 和自定义规则进行脱敏,然后转发或返回清理后的 payload。
无遥测。无回传通信。无需存储完整密钥。

网站:`https://bvolpato.github.io/promptcloak/`
代码仓库:`https://github.com/bvolpato/promptcloak`
## 60 秒演示
```
brew tap bvolpato/tap
brew install promptcloak
promptcloak init
export OPENROUTER_API_KEY=""
promptcloak serve
```
通过 PromptCloak 发送流量:
```
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.5",
"messages": [
{
"role": "user",
"content": "Here is my .env: OPENAI_API_KEY="
}
]
}'
```
上游看到的内容:
```
OPENAI_API_KEY=[REDACTED_SECRET]
```
部分遮盖可用,但完全遮盖更安全且为默认设置:
```
redaction:
redact_mode: "partial"
```
## 确定性脱敏冒烟测试
不要通过让 LLM 复述其收到的内容来验证脱敏效果。模型可能会拒绝、推断、总结或错误陈述发生的事情。请使用 echo 目标:
```
FAKE_GEMINI_KEY="AI""zaSyFixtureToken000000000000000000000"
curl -fsS http://127.0.0.1:8000/v1/chat/completions \
-H "X-Target-Base-URL: https://httpbin.org/anything" \
-H "Content-Type: application/json" \
--data "$(jq -nc --arg key "$FAKE_GEMINI_KEY" \
'{messages:[{role:"user",content:("GEMINI_API_KEY=" + $key)}]}')" \
| jq -r '.json.messages[0].content'
```
预期输出:
```
GEMINI_API_KEY=[REDACTED_SECRET]
```
PromptCloak 审计日志包含计数和规则名称,绝不包含密钥值。
## 使用 Homebrew 安装
```
brew tap bvolpato/tap
brew install promptcloak
promptcloak version
promptcloak init --target-base-url https://openrouter.ai/api/v1
```
在配置准备就绪后,使用 shell 环境变量在前台运行,或作为服务启动。
前台模式将上游密钥保留在您的 shell 中。服务模式要求密钥通过配置或服务管理器环境变量对服务进程可用。
```
export OPENROUTER_API_KEY=""
promptcloak serve
brew services start bvolpato/tap/promptcloak
```
## 使用 uv 安装发布版本
```
uv tool install \
https://github.com/bvolpato/promptcloak/releases/download/v0.1.4/promptcloak-0.1.4-py3-none-any.whl
promptcloak doctor
```
## 从源码安装
```
git clone https://github.com/bvolpato/promptcloak.git
cd promptcloak
uv sync --extra dev
uv run promptcloak doctor
```
## 作为库使用
PromptCloak 可以在没有代理服务的情况下运行。导入脱敏辅助工具,并在将请求值传递给任何 SDK 之前对其进行过滤。PromptCloak 不会安装 OpenAI、LiteLLM、LangChain 或 Anthropic SDK;示例假定这些 SDK 已经在您的应用程序中存在。
```
uv add \
https://github.com/bvolpato/promptcloak/releases/download/v0.1.4/promptcloak-0.1.4-py3-none-any.whl
```
```
from promptcloak import redact_messages, scan_messages
messages = [
{
"role": "user",
"content": "Debug this .env: OPENAI_API_KEY=",
}
]
safe_messages = redact_messages(messages)
result = scan_messages(messages)
assert result.stats.redactions >= 1
```
对于自定义仅尾部匹配规则:
```
from promptcloak import PromptCloak
from promptcloak.config import RedactionConfig, RuleConfig
cloak = PromptCloak(
RedactionConfig(
rules=[RuleConfig(type="exact", value="abcd1234", name="tail-only")]
)
)
safe_messages = cloak.messages(messages)
```
### OpenAI Python
```
from openai import OpenAI
from promptcloak import redact_messages, redact_params
client = OpenAI()
messages = [{"role": "user", "content": "API key: "}]
response = client.chat.completions.create(
model="gpt-5.5",
messages=redact_messages(messages),
)
response_api = client.responses.create(
**redact_params(
model="gpt-5.5",
input="Summarize this config: OPENAI_API_KEY=",
)
)
```
### LiteLLM
```
from litellm import completion
from promptcloak import redact_params
messages = [{"role": "user", "content": "GEMINI_API_KEY="}]
response = completion(
**redact_params(
model="openrouter/openai/gpt-5.5",
messages=messages,
)
)
```
### LangChain
元组样式的消息:
```
from langchain_openai import ChatOpenAI
from promptcloak import redact_messages
llm = ChatOpenAI(model="gpt-5.5")
response = llm.invoke(
redact_messages(
[
("system", "You are concise."),
("human", "Here is my token: "),
]
)
)
```
LangChain 消息对象:
```
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from promptcloak import redact_messages
llm = ChatOpenAI(model="gpt-5.5")
response = llm.invoke(
redact_messages(
[
HumanMessage(content="Here is my token: "),
]
)
)
```
### Anthropic Python
```
from anthropic import Anthropic
from promptcloak import redact_messages
client = Anthropic()
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=redact_messages(
[{"role": "user", "content": "ANTHROPIC_API_KEY="}]
),
)
```
### LlamaIndex
```
from llama_index.core.llms import ChatMessage
from llama_index.llms.openai import OpenAI
from promptcloak import redact_messages
llm = OpenAI(model="gpt-5.5")
response = llm.chat(
redact_messages(
[
ChatMessage(role="user", content="Here is my token: "),
]
)
)
```
### 原生 HTTP 或自定义客户端
```
import httpx
from promptcloak import redact_payload
payload = {
"model": "openai/gpt-5.5",
"messages": [{"role": "user", "content": "secret="}],
}
response = httpx.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={"Authorization": "Bearer "},
json=redact_payload(payload),
)
```
## 运行
```
uv run promptcloak init --target-base-url https://openrouter.ai/api/v1
export OPENROUTER_API_KEY=""
uv run promptcloak serve
```
默认配置:`~/.config/promptcloak/config.yaml`
```
server:
host: 127.0.0.1
port: 8000
api_key: null
target:
default_base_url: https://openrouter.ai/api/v1
api_key: ${OPENROUTER_API_KEY}
api_key_header: authorization
forward_client_authorization: false
timeout_seconds: 180
allowed_base_urls: []
block_private_targets: true
redaction:
enabled: true
engine: detect-secrets
redact_mode: full
encrypted: false
scan_responses: false
rules:
- type: exact
value: abcd1234
name: tail-only-example
- type: regex
value: sk-[A-Za-z0-9_-]{20,}
name: openai-style-token
```
最佳实践:在 `rules` 中仅存储密钥尾部,切勿存储完整密钥。
## 支持的路由
PromptCloak 会转发任何路径,并针对以下路径提供一流测试:
- `/v1/chat/completions`
- `/v1/responses`
- `/v1/completions`
- `/v1/models`
- 用于 Claude 兼容网关的 `/v1/messages`
它会保留流式响应、工具、结构化输出、视觉 payload 和未知的提供者字段,因为它在不改变请求 schema 结构的情况下进行递归脱敏。
## 动态后端
在配置中设置默认后端,或针对每个请求进行覆盖:
```
curl http://127.0.0.1:8000/v1/responses \
-H "X-Target-Base-URL: https://api.openai.com/v1" \
-H "X-Target-API-Key: $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.5","input":"scan this "}'
```
为 Anthropic 样式的上游身份验证设置 `X-Target-API-Key-Header: x-api-key`。
使用 `target.allowed_base_urls` 进行严格的允许列表控制。
## Codex
Codex 使用 OpenAI Responses 协议。某些网关仅公开 Chat Completions。PromptCloak 可以通过设置 `compat.responses_to_chat: true`,将 Codex 的 `/v1/responses` 流量桥接到上游的 `/v1/chat/completions`。
对于通过 PromptCloak 使用 OpenRouter,请在 Codex 提供者中设置 `env_key = "OPENROUTER_API_KEY"`。Codex 会将密钥附加到 localhost 请求中,PromptCloak 会将该 Authorization 标头转发到允许的 OpenRouter 上游。
PromptCloak 配置:
```
mkdir -p ~/.config/promptcloak
cp examples/promptcloak-openrouter.config.yaml ~/.config/promptcloak/config.yaml
export OPENROUTER_API_KEY=""
uv run promptcloak serve
```
相关的 PromptCloak 设置:
```
target:
default_base_url: https://openrouter.ai/api/v1
api_key: null
forward_client_authorization: true
allowed_base_urls:
- https://openrouter.ai/api/v1
compat:
responses_to_chat: true
```
Codex 配置:
`~/.codex/config.toml`
```
model = "openai/gpt-oss-120b"
model_provider = "promptcloak-openrouter"
[model_providers.promptcloak-openrouter]
name = "PromptCloak OpenRouter"
base_url = "http://127.0.0.1:8000/v1"
env_key = "OPENROUTER_API_KEY"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
```
或者将其保留为单独的配置:
```
mkdir -p ~/.codex
cp examples/codex-openrouter-promptcloak.config.toml ~/.codex/openrouter-promptcloak.config.toml
codex -p openrouter-promptcloak
```
冒烟测试:
```
codex -p openrouter-promptcloak --sandbox read-only --ask-for-approval never exec \
--cd /home/bruno/githubworkspace/promptcloak \
"Reply with exactly: promptcloak-openrouter-ok"
```
单独确认 Chat Completions:
```
curl -fsS http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-oss-120b","messages":[{"role":"user","content":"Reply exactly: ok"}]}' \
| jq -r '.choices[0].message.content'
```
通过更改配置中的 `model` 来使用任何 OpenRouter 模型。仅当您将 PromptCloak 绑定到 localhost 之外时,才添加 `server.api_key`。
桥接说明:
- 切勿在 TOML 中放置原始密钥。请将环境变量名称放在 `env_key` 中。
- 在转发客户端身份验证时,请严格限制 `target.allowed_base_urls`。
- 文本响应和标准函数调用会被转换。
- PromptCloak 在转换之前进行脱敏。
- 对于具有原生 `/v1/responses` 的上游,请关闭 `compat.responses_to_chat`。
## OpenCode
OpenCode 通过 `@ai-sdk/openai-compatible` 和 `options.baseURL` 支持自定义 OpenAI 兼容提供者。
选项 A:在 PromptCloak 上设置上游提供者:
```
export PROMPTCLOAK_TARGET_BASE_URL="https://openrouter.ai/api/v1"
export PROMPTCLOAK_TARGET_API_KEY=""
promptcloak serve
```
选项 B:从 OpenCode 标头中针对每个请求设置上游提供者:
`opencode.json`
```
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"promptcloak": {
"npm": "@ai-sdk/openai-compatible",
"name": "PromptCloak",
"options": {
"baseURL": "http://127.0.0.1:8000/v1",
"headers": {
"X-Target-Base-URL": "https://openrouter.ai/api/v1",
"X-Target-API-Key": "{env:OPENROUTER_API_KEY}"
}
},
"models": {
"openai/gpt-5.5": {
"name": "GPT-5.5 via PromptCloak"
}
}
}
},
"model": "promptcloak/openai/gpt-5.5"
}
```
上游提供者 URL 和密钥为 `X-Target-Base-URL` 和 `X-Target-API-Key`,或者在 PromptCloak 上配置时为 `PROMPTCLOAK_TARGET_BASE_URL` 和 `PROMPTCLOAK_TARGET_API_KEY`。
## Claude Code
Claude Code 使用 Anthropic 兼容流量,而不是 OpenAI Chat Completions。当上游兼容 Anthropic 或是接受 Claude Code 流量的 LLM 网关时,PromptCloak 仍然可以脱敏并转发 Claude Code 请求。
配置:
```
target:
default_base_url: https://api.anthropic.com
api_key: ${ANTHROPIC_UPSTREAM_API_KEY}
api_key_header: x-api-key
```
Shell:
```
export ANTHROPIC_UPSTREAM_API_KEY=""
export ANTHROPIC_BASE_URL="http://127.0.0.1:8000"
export ANTHROPIC_API_KEY="${PROMPTCLOAK_LOCAL_API_KEY:-placeholder}"
export DISABLE_TELEMETRY=1
export DO_NOT_TRACK=1
promptcloak serve
claude
```
PromptCloak 将 `/v1/messages` 转发到配置的上游。它不会将 OpenAI 协议转换为 Anthropic 协议。
## 脱敏引擎
PromptCloak 直接使用 `bc-detect-secrets`,加上用于提供者 token 和用户定义的精确尾部或正则匹配的本地确定性规则。它不依赖于任何模型 runtime。
测试涵盖了类似于以下的伪造 token:
- GitHub 经典和细粒度 PAT
- Atlassian API 密钥
- OpenAI 项目/API 密钥
- Gemini API 密钥
- Anthropic API 密钥
- OpenRouter 密钥
- GitLab、Slack、Stripe、AWS、Google API 密钥
- 1Password、Databricks、DigitalOcean、Hugging Face、Linear、npm、PyPI、SendGrid、Telegram、Twilio、Vault、Shopify、Sentry
- JWT
- PEM 私钥
- 请求体中的 Authorization 样式标头和 URL 凭据
- `password=...`、`token=...`、`api_key=...`
- 用户自定义的精确尾部和正则规则
每次扫描都是本地的。PromptCloak 绝不会调用 LLM 来检测密钥。仅基于熵的匹配被特意禁用;对于不透明的内部格式,请使用自定义规则。
## 对静态规则进行加密
```
uv run promptcloak encrypt-rules
```
这将创建模式为 `0600` 的 `~/.config/promptcloak/key`,使用 AES-GCM 加密 `redaction.rules`,写入 `redaction.encrypted_rules`,并清除明文规则。
您还可以通过以下方式提供密钥材料:
```
export PROMPTCLOAK_CONFIG_KEY="base64-url-safe-32-byte-key"
```
## Docker
发布的镜像:
```
export OPENROUTER_API_KEY=""
docker run -d --name promptcloak --rm \
-p 127.0.0.1:8000:8000 \
-e PROMPTCLOAK_TARGET_BASE_URL=https://openrouter.ai/api/v1 \
-e PROMPTCLOAK_TARGET_API_KEY="$OPENROUTER_API_KEY" \
ghcr.io/bvolpato/promptcloak:0.1.4
curl -fsS http://127.0.0.1:8000/healthz
docker stop promptcloak
```
本地构建:
```
docker build -t promptcloak:local .
docker run -d --name promptcloak --rm \
-p 127.0.0.1:8000:8000 \
-e PROMPTCLOAK_TARGET_BASE_URL=https://openrouter.ai/api/v1 \
-e PROMPTCLOAK_TARGET_API_KEY="$OPENROUTER_API_KEY" \
promptcloak:local
curl -fsS http://127.0.0.1:8000/healthz
docker stop promptcloak
```
Compose:
```
export OPENROUTER_API_KEY=""
docker compose up --build
```
## Helm
本地 chart:
```
helm install promptcloak ./charts/promptcloak \
--set secretEnv.PROMPTCLOAK_TARGET_API_KEY="$OPENROUTER_API_KEY"
kubectl wait deployment/promptcloak --for=condition=Available --timeout=90s
kubectl port-forward svc/promptcloak 8000:8000
```
在另一个 shell 中:
```
curl -fsS http://127.0.0.1:8000/healthz
helm uninstall promptcloak
```
在 Kind 中使用本地构建的镜像:
```
docker build -t promptcloak:local .
kind load docker-image promptcloak:local
helm install promptcloak ./charts/promptcloak \
--set image.repository=promptcloak \
--set image.tag=local \
--set image.pullPolicy=Never \
--set secretEnv.PROMPTCLOAK_TARGET_API_KEY="$OPENROUTER_API_KEY"
```
发布资产:
```
helm pull https://github.com/bvolpato/promptcloak/releases/download/v0.1.4/promptcloak-0.1.4.tgz
helm install promptcloak ./promptcloak-0.1.4.tgz \
--set secretEnv.PROMPTCLOAK_TARGET_API_KEY="$OPENROUTER_API_KEY"
```
## 安全模型
PromptCloak 可在请求体离开您的机器之前对其进行保护。它会在调试日志中遮盖敏感的身份验证标头,但它无法从绑定到提供者的身份验证标头中移除上游凭据,因为提供者需要这些凭据来对请求进行身份验证。推荐的设置:
- 将上游提供者密钥放在 PromptCloak 配置或环境变量中。
- 除非您有意要转发客户端身份验证,否则请保持 `forward_client_authorization: false`。
- 对于 Anthropic 兼容的上游,请使用 `api_key_header: x-api-key`。
- 仅当将 PromptCloak 暴露到 `127.0.0.1` 之外时,才设置 `server.api_key`。
- 在脱敏规则中仅存储密钥尾部。
- 保持 `block_private_targets: true`。
- 在共享/团队安装中设置 `allowed_base_urls`。
- 除非需要,否则请保持响应扫描关闭;目前特意不支持流式响应脱敏。
- 为没有稳定公共前缀的内部 token 或提供者添加自定义规则。
- 在库模式下,请在 SDK 调用之前调用 `redact_messages`、`redact_params` 或 `redact_payload`。
## 紧急请求追踪
`promptcloak serve --debug-requests` 会在脱敏之前记录原始请求体。仅当 echo 目标无法满足需求时,才将其用于本地 fixture 数据。Auth、target-key 和脱敏规则标头会被遮盖;但正文文本不会被遮盖。
## 开发
```
uv sync --extra dev
uv run scripts/audit_secrets.py
uv run pytest
uv run ruff check .
uv build
uv run promptcloak scan 'OPENAI_API_KEY='
```
测试覆盖率包括针对嵌套的 OpenAI/Responses/Claude 样式 payload、动态上游标头、审计日志、紧急追踪、响应扫描、目标允许列表、文本正文和提供者形状的伪造 token 的单元测试和 e2e fixture。Fixture 在源码中进行了拆分,因此不会提交任何真实或连续的伪造密钥。
在提交 issue 或 pull request 之前,请参阅 [CONTRIBUTING.md](CONTRIBUTING.md)、[SECURITY.md](SECURITY.md) 和 [SECURITY_AUDIT.md](SECURITY_AUDIT.md)。切勿在公开的项目页面中发布真实的密钥。
## 状态
PromptCloak 发布的 GitHub releases 包含源码、wheel、Helm chart、Homebrew formula 和 GHCR 镜像制品。
标签:AI安全, Chat Copilot, OpenAI兼容, 网络安全, 运行时操纵, 逆向工具, 隐私保护