bvolpato/promptcloak

GitHub: bvolpato/promptcloak

PromptCloak 是一款本地运行的 OpenAI 兼容代理与 Python 库,在提示词发送到云端模型前自动扫描并脱敏其中的密钥、密码与 token 等敏感信息。

Stars: 1 | Forks: 0

# PromptCloak **本地 OpenAI 兼容代理和 Python 库,可在提示词离开您的机器之前对其进行脱敏处理。** PromptCloak 可以位于编码代理、SDK 或应用程序与任何 OpenAI 兼容的后端之间。它也可以作为小型 Python 过滤器在进程内运行。它在本地扫描值,对 API 密钥、密码、token、私钥、JWT 和自定义规则进行脱敏,然后转发或返回清理后的 payload。 无遥测。无回传通信。无需存储完整密钥。 ![PromptCloak 站点预览](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/dd6182eee7055750.png) 网站:`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兼容, 网络安全, 运行时操纵, 逆向工具, 隐私保护