Transparent proxy

Point your existing OpenAI or Anthropic SDK at the firewall and get PII sanitization and injection defense with no code changes beyond the base URL. The firewall pseudonymizes PII on the way in, forwards the request to your provider, and restores the real values in the response — returning your provider's native response body so your existing SDK types keep working unchanged.

This is the right path when you call a provider SDK directly and want the firewall to be invisible to the rest of your code. If you instead control the request lifecycle yourself, see the Direct API guide.

Prerequisites

  • A firewall key (sfw_live_xxxxxxxx). Sign in and open Keys to mint one — it is shown only once.
  • Your provider key (OpenAI sk-… or Anthropic sk-ant-…), used exactly as you use it today.
  • The firewall's base URL. This guide uses https://your-firewall.example.com.

Export your firewall key so the snippets below pick it up:

export SFW_KEY=sfw_live_xxxxxxxx...your-full-key

The two-keys model

Two keys are in play, and they never collide:

ProviderFirewall key (stripped before upstream)Provider key (forwarded untouched)
OpenAIX-SFW-KeyAuthorization: Bearer sk-…
AnthropicX-SFW-Keyx-api-key: sk-ant-…

Your firewall key ($SFW_KEY) authenticates you to the firewall and is stripped before the request leaves — it never reaches the provider. Your provider key rides in the SDK's normal auth header and is forwarded untouched; the firewall never stores it. Always send the firewall key as X-SFW-Key on the proxy so your provider key keeps sole use of Authorization.

Steps

1. Repoint the OpenAI SDK

Set the base URL to the firewall's OpenAI proxy and add the X-SFW-Key header. Your api_key stays your real OpenAI key.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://your-firewall.example.com/proxy/openai/v1",
    default_headers={"X-SFW-Key": os.environ["SFW_KEY"]},
    # api_key stays your real OpenAI key — forwarded to OpenAI untouched
)
client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Summarize this ticket from John Smith..."}],
)

2. Or repoint the Anthropic SDK

import os
from anthropic import Anthropic

client = Anthropic(
    base_url="https://your-firewall.example.com/proxy/anthropic",
    default_headers={"X-SFW-Key": os.environ["SFW_KEY"]},
)
client.messages.create(
    model="claude-haiku-4-5-20251001",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Summarize this ticket from John Smith..."}],
)

3. Point at any OpenAI-compatible provider (optional)

To route through an OpenAI-compatible gateway (OpenRouter, Requesty, a Bedrock gateway), override the upstream with X-Upstream-Url:

curl -s -X POST https://your-firewall.example.com/proxy/openai/v1/chat/completions \
  -H "X-SFW-Key: $SFW_KEY" \
  -H "X-Upstream-Url: https://openrouter.ai/api" \
  -H "Authorization: Bearer $OPENROUTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"...","messages":[{"role":"user","content":"..."}]}'

How the response comes back

The proxy does not wrap responses in a firewall verdict. It returns your provider's native response body (OpenAI or Anthropic), byte-for-byte except that PII is restored in the content — so your SDK deserializes it exactly as before.

  • A blocked request comes back as a provider-shaped error (for example a 403 carrying a firewall extension and an X-SFW-Trace-Id), so your SDK surfaces it as a normal API error you already handle.
  • Every response is screened for signs of a compromised model (jailbreak personas, leaked system prompts or credentials) and flagged — or blocked, if you raise the output policy. See Detect a compromised agent.

Streaming behavior

Streaming (stream: true) works unchanged. Because a single pseudonym can split across two SSE chunks, the proxy buffers the tail of each chunk and rejoins split replacements before emitting — so restored PII is never corrupted at a chunk boundary. You receive a normal provider stream.

One caveat for tool-using agents: the action gate runs on non-streaming responses only. A streaming tool-call turn is not gated. See the compromised-agent guide for the fail-closed lever.

Multi-turn session tracking

Some attacks are benign per turn but malicious across a conversation (recon → capability enumeration → extraction). Pass a stable conversation id with the X-SFW-Session-Id header and the firewall carries a decaying injection-pressure signal across the session, so a later finding is corroborated instead of judged in isolation.

curl -s -X POST https://your-firewall.example.com/proxy/openai/v1/chat/completions \
  -H "X-SFW-Key: $SFW_KEY" \
  -H "X-SFW-Session-Id: conv-8f3a2b" \
  -H "Authorization: Bearer $OPENAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"..."}]}'

Without a session id, inspection is fully per-turn (no cross-turn memory) and existing integrations are unchanged. Session ids are scoped per key and tenant (the same string cannot collide across keys) and validated (≤ 200 chars, no | or control characters); invalid values are ignored rather than raising an error.

On the proxy path, keep sending your normal full message history. The goal-alignment judge reads the user's goal from the user turns in the request, so trimming user turns client-side weakens it.

The drift baseline (system goal)

Semantic-drift detection compares each input against the model's intended goal. On the proxy, the goal is auto-derived from the system message in your request — there is nothing extra to add. If you send no system message, set a System goal on the key's Configuration tab as a fallback.

Verify it is working

Send a request whose content contains obvious PII and confirm the model never sees the real value:

curl -s -X POST https://your-firewall.example.com/proxy/openai/v1/chat/completions \
  -H "X-SFW-Key: $SFW_KEY" \
  -H "Authorization: Bearer $OPENAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Reply with the exact email address in this sentence: contact john.smith@acme.com."}]}'

The model echoes a pseudonymized address, not john.smith@acme.com, and the response you receive has the real address restored. You can also confirm activity in the dashboard's Activity tab.

Troubleshooting

  • 401/auth error from the firewall — the X-SFW-Key header is missing or wrong. It is separate from your provider key.
  • Auth error from the provider — your provider key is not being forwarded. Confirm the SDK still sends its normal auth header (Authorization for OpenAI, x-api-key for Anthropic) and that you did not overwrite it with the firewall key.
  • Requests hit the wrong upstream — for gateways other than OpenAI/Anthropic, set X-Upstream-Url explicitly (step 3).
  • PII split or garbled in a stream — this should not happen thanks to chunk buffering; if it does, verify you are hitting the /proxy/... path and not the provider directly.
  • Tool-call turns are not being gated — the action gate does not run on streaming responses; do not stream tool-call turns, or fail closed. See the compromised-agent guide.