Dynamic upstreams
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"
Verify
Start locally, point your client at 127.0.0.1:8000, and use an
echo target for redaction checks. Model replies are not reliable evidence.
brew tap bvolpato/tap
brew install promptcloak
promptcloak init
export OPENROUTER_API_KEY="<openrouter-upstream-key>"
promptcloak serve
FAKE_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" \
-d '{"messages":[{"content":"GEMINI_API_KEY='"$FAKE_KEY"'"}]}'
# upstream body: GEMINI_API_KEY=[REDACTED_SECRET]
Install
Use PromptCloak as a local proxy for agents, or import it before direct SDK calls.
brew tap bvolpato/tap
brew install promptcloak
promptcloak init
export OPENROUTER_API_KEY="<openrouter-upstream-key>"
promptcloak serve
curl http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages":[{"content":".env has sk-..."}]}'
from promptcloak import redact_messages
safe = redact_messages([
{"role": "user", "content": "API_KEY=<secret>"}
])
Proxy mode
PromptCloak redacts JSON and text bodies, then forwards the original route and provider-specific fields.
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"
target:
default_base_url: https://openrouter.ai/api/v1
api_key: ${OPENROUTER_API_KEY}
api_key_header: authorization
redaction:
engine: detect-secrets
redact_mode: full
/v1/chat/completions
/v1/responses
/v1/completions
/v1/models
/v1/messages
Library
Import PromptCloak helpers before OpenAI, LiteLLM, LangChain, Anthropic, or custom HTTP client calls.
from openai import OpenAI
from promptcloak import redact_messages
client = OpenAI()
client.chat.completions.create(
model="gpt-5.5",
messages=redact_messages(messages),
)
from litellm import completion
from promptcloak import redact_params
messages = [{"role": "user", "content": "API_KEY=<secret>"}]
completion(**redact_params(
model="openrouter/openai/gpt-5.5",
messages=messages,
))
from promptcloak import redact_params
client.responses.create(**redact_params(
model="gpt-5.5",
input="OPENAI_API_KEY=<secret>",
))
from langchain_openai import ChatOpenAI
from promptcloak import redact_messages
llm = ChatOpenAI(model="gpt-5.5")
llm.invoke(redact_messages([
("human", "token=<secret>"),
]))
from anthropic import Anthropic
from promptcloak import redact_messages
Anthropic().messages.create(
model="claude-opus-4-8",
max_tokens=1024,
messages=redact_messages(messages),
)
from llama_index.llms.openai import OpenAI
from promptcloak import redact_messages
llm = OpenAI(model="gpt-5.5")
llm.chat(redact_messages(messages))
Agent configs
# ~/.config/promptcloak/config.yaml
target:
forward_client_authorization: true
compat:
responses_to_chat: true
# ~/.codex/openrouter-promptcloak.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
# PromptCloak forwards localhost auth and bridges Responses to Chat.
{
"provider": {
"promptcloak": {
"npm": "@ai-sdk/openai-compatible",
"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}"
}
}
}
}
}
export ANTHROPIC_BASE_URL="http://127.0.0.1:8000"
export ANTHROPIC_API_KEY="${PROMPTCLOAK_LOCAL_API_KEY:-placeholder}"
export DISABLE_TELEMETRY=1
Smoke tests
Use an echo target to inspect exactly what upstream receives. Do not ask a model to repeat secrets back as a redaction test.
FAKE_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" \
-d '{"messages":[{"content":"GEMINI_API_KEY='"$FAKE_KEY"'"}]}' \
| jq -r '.json.messages[0].content'
Deploy
Keep PromptCloak bound to localhost for personal use. If you expose it to a network, set an allowlist and local proxy 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.5
curl -fsS http://127.0.0.1:8000/healthz
helm install promptcloak \
https://github.com/bvolpato/promptcloak/releases/download/v0.1.5/promptcloak-0.1.5.tgz \
--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
curl -fsS http://127.0.0.1:8000/healthz
brew tap bvolpato/tap
brew install promptcloak
promptcloak version
uv add \
https://github.com/bvolpato/promptcloak/releases/download/v0.1.5/promptcloak-0.1.5-py3-none-any.whl
from promptcloak import redact_payload
safe_payload = redact_payload(payload)
Coverage
Deterministic patterns cover provider keys, personal access tokens, JWTs, signed URLs, private key blocks, URL credentials, and assignment-style secrets.
Security model