# 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](/docs/guides/direct-api) guide.

## Prerequisites

- A firewall key (`sfw_live_xxxxxxxx`). [Sign in](/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:

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

## The two-keys model

Two keys are in play, and they never collide:

| Provider | Firewall key (stripped before upstream) | Provider key (forwarded untouched) |
| --- | --- | --- |
| OpenAI | `X-SFW-Key` | `Authorization: Bearer sk-…` |
| Anthropic | `X-SFW-Key` | `x-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.

```python
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

```python
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`:

```bash
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](/docs/guides/detect-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](/docs/guides/detect-compromised-agent#agent-security-the-action-gate) 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.

```bash
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:

```bash
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](/docs/guides/detect-compromised-agent).
