Configuration
Every environment variable tawa-claude-proxy reads, how blocking is decided, and what SDK-level options aren't wired up here yet.
The proxy is configured entirely through environment variables — there's no config file.
Environment variables
| Variable | Default | Meaning |
|---|---|---|
TAWA_API_KEY | (required) | Your Tawa API key, sent to the scan endpoint. Separate from ANTHROPIC_API_KEY — this one is never sent to Anthropic, and your real Anthropic key is never sent to Tawa. |
TAWA_GATEWAY_URL | https://gateway.tawaguard.com | Where scanning actually runs. Override for local development against a local-stack gateway. |
TAWA_PROXY_LISTEN_ADDR | 127.0.0.1:8787 | Where the proxy itself listens. The Docker image overrides this default to 0.0.0.0:8787 so container port-forwarding can reach it. |
TAWA_PROXY_ANTHROPIC_BASE_URL | https://api.anthropic.com | Upstream the proxy forwards to. Override for testing against a mock Anthropic endpoint — not something you'd normally change in production. |
TAWA_PROXY_FAIL_MODE | open | What happens when the scan call to Tawa itself fails (network error, gateway outage, malformed response) — see below. |
TAWA_PROXY_BLOCK_ACTIONS | BLOCK,HARD_BLOCK,SANDBOX | Comma-separated verdict tiers that stop a request, tool call, or reply — see What triggers a block. |
TAWA_PROXY_SCAN_OUTPUT_TEXT | true | Scan the model's own visible reply before it reaches Claude Code — see Scanning the model's own reply. |
TAWA_PROXY_OUTPUT_CHUNK_CHARS | 400 | Streaming checkpoints vs. standard (whole-reply) scanning for output text — see Streaming checkpoints vs. standard mode. |
RUST_LOG | info | Standard tracing-subscriber log level filter — not Tawa-specific, but useful for debug-level troubleshooting. |
Fail-open vs. fail-closed
TAWA_PROXY_FAIL_MODE decides what happens if the call to Tawa's scan
endpoint fails — not what happens when a scan succeeds and returns a
block, which always blocks regardless of this setting.
open(default): if the scan call itself fails, the request forwards to Anthropic anyway. A Tawa outage degrades you back to unprotected Claude Code rather than a broken one.closed: if the scan call fails, the request is rejected instead of forwarded. Choose this if you'd rather Claude Code stop working outright than run a single turn unscanned.
What triggers a block
By default, a scan verdict of BLOCK, HARD_BLOCK, or SANDBOX stops
it — an Anthropic-shaped 400 error on the request side (Claude Code never
reaches Anthropic), or a protocol-level event: error SSE frame on the
response side that ends the stream before a blocked tool call or reply
reaches the client. Every other verdict (ALLOW, REVIEW, SOFT_BLOCK,
AMBIGUOUS, OOD) forwards through by default. This mirrors the SDKs' own
action severity ladder (see How Tawa works),
narrowed to a binary forward-or-block decision because there's no human in
a local CLI's loop to hand an ambiguous verdict to — the same threshold
applies to the inbound request scan and both outbound scans (tool call and
model reply).
REVIEW and the review flag
The relay blocks or forwards on the action the gateway returns. The
gateway collapses an engine REVIEW to ALLOW or BLOCK per your app's
setting for each content type, so a REVIEW on a type set to block
arrives as BLOCK and stops, whatever TAWA_PROXY_BLOCK_ACTIONS says. By
default that applies to user prompts and tool calls, and not to model
replies. To let such results through for an app, set that content type to
allow in the dashboard. TAWA_PROXY_BLOCK_ACTIONS=...,REVIEW only has an
effect against a gateway that predates the
review flag.
Override which tiers actually block with TAWA_PROXY_BLOCK_ACTIONS — a
comma-separated list of any of ALLOW, REVIEW, SOFT_BLOCK, BLOCK,
HARD_BLOCK, SANDBOX, AMBIGUOUS, OOD:
# Also stop on SOFT_BLOCK and REVIEW, not just the hard-block family:
TAWA_PROXY_BLOCK_ACTIONS=BLOCK,HARD_BLOCK,SANDBOX,SOFT_BLOCK,REVIEW
# Narrower than the default — only a HARD_BLOCK stops anything:
TAWA_PROXY_BLOCK_ACTIONS=HARD_BLOCKAn unset or blank value falls back to the default set. An unrecognized entry (a typo, or a future verdict tier this proxy doesn't know about yet) is silently ignored rather than rejected at startup — consistent with the proxy's fail-open-by-default posture elsewhere.
This is a proxy-local decision about which verdict tiers stop a request — it's separate from (and not a substitute for) the detection policy itself. Policy Studio rules and PII settings are resolved server-side from your API key's project, and this proxy never touches them.
Scanning the model's own reply
By default, the model's own visible reply (any text content block in the
streamed response) is scanned too — via the same scan route as analyze(),
tagged llm_response provenance — before it reaches Claude Code. This
catches a different threat shape than tool-call gating: a hijacked model can
exfiltrate something it read (a secret in a file, credentials from a tool
result) just by printing it in its answer, with no tool call involved at
all.
Turn it off entirely if that's not worth it for a given workflow — e.g. you only care about stopping tool calls, not what the model says:
TAWA_PROXY_SCAN_OUTPUT_TEXT=falseAn empty reply (or one that's all whitespace) is never sent for scanning
regardless of this setting — there's nothing to evaluate. tool_use gating
is unaffected either way; this only controls the text side.
This differs from the Vercel AI SDK integration's
scanResponses option in one respect: both scan a streamed reply in
chunks rather than waiting for the whole thing by default, but this
proxy's chunking is separately tunable — see below — down to (or up from)
the SDK's fixed windowing.
Streaming checkpoints vs. standard mode
Once output-text scanning is on, TAWA_PROXY_OUTPUT_CHUNK_CHARS decides
how it's scanned — this is the actual cost/delay trade-off, and it's worth
understanding before turning the feature on for a team.
- Streaming checkpoints (default,
400): every time roughly that many new characters have streamed in, the proxy scans everything accumulated so far (the whole utterance up to that point, not just the new fragment — an isolated fragment is a weaker detection signal) and releases whatever's cleared to Claude Code. The reply keeps feeling like normal streaming instead of arriving in one dump at the end, at the cost of one scan call per checkpoint — a reply long enough to cross the checkpoint several times costs several scans, not one. - Standard mode (
TAWA_PROXY_OUTPUT_CHUNK_CHARS=0): no mid-stream checkpoints. The entire reply is held until the model finishes generating it, scanned exactly once, then released (or blocked) as a single piece. Fewer scan calls, but a human watching the reply stream sees nothing at all until the whole thing has both finished generating and cleared a scan — for a long reply, that's a real, felt delay, not a rounding error.
# Smaller checkpoints: closer to un-gated streaming, more scan calls
TAWA_PROXY_OUTPUT_CHUNK_CHARS=150
# Standard mode: one scan per reply, but nothing streams until it's done
TAWA_PROXY_OUTPUT_CHUNK_CHARS=0This is a real cost multiplier, not just a latency knob
Every checkpoint is its own scan call against your Tawa account. A reply
that costs 1 scan in standard mode costs roughly reply length ÷ chunk size scans chunked — at the 400-char default, a several-thousand-
character reply alone can turn what would otherwise be a couple of scans
for a whole exchange (one request scan, maybe one tool-call scan) into
several times that. Smaller chunk sizes multiply this further. Pick a
chunk size (or 0 for standard mode) based on how much that trade-off is
worth for your usage, not just the default.
tool_use blocks are never chunked regardless of this setting — a proposed
tool call is always scanned exactly once, at the instant its arguments
finish streaming, since there's no partial-JSON checkpoint that would mean
anything.
Session correlation
If Claude Code sends an x-claude-code-session-id header, the proxy passes
it through to Tawa as session_id on every scan from that session — purely
to correlate related scans in Tawa's own event stream/audit trail. It's
opaque to the proxy; nothing about it is parsed or validated.
Not yet configurable here
Scope note
The TypeScript and Python SDKs expose several per-call options that this proxy doesn't thread through yet, because its only configuration surface today is the environment variables above (set once at process start, not per-request). Documenting this now as a known gap rather than leaving it undiscovered — filling these in is the natural next step for this integration, not done yet.
- Per-user identifiers (
cuid/tenant_idin the SDKs — see Identifiers) — there's no way yet to attribute a proxy's scans to a specific end user or tenant. - Policy Studio directives (
policy_overridesin the SDKs — see Policy rules and Enforcing a policy) — your org's Policy Studio rules aren't evaluated or enforced by this proxy. - Language hints (
languagein the SDKs) — not passed through. - A fully custom block predicate (
shouldBlockin the SDKs takes an arbitrary function of the wholeScanResult) —TAWA_PROXY_BLOCK_ACTIONSabove covers picking which verdict tiers block, but not arbitrary logic (e.g. "block by confidence score" or "block based onrationale").
If you need any of these today, they're available through the TypeScript or Python SDK directly — just not through this proxy yet.