Tawa docs

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

VariableDefaultMeaning
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_URLhttps://gateway.tawaguard.comWhere scanning actually runs. Override for local development against a local-stack gateway.
TAWA_PROXY_LISTEN_ADDR127.0.0.1:8787Where 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_URLhttps://api.anthropic.comUpstream the proxy forwards to. Override for testing against a mock Anthropic endpoint — not something you'd normally change in production.
TAWA_PROXY_FAIL_MODEopenWhat happens when the scan call to Tawa itself fails (network error, gateway outage, malformed response) — see below.
TAWA_PROXY_BLOCK_ACTIONSBLOCK,HARD_BLOCK,SANDBOXComma-separated verdict tiers that stop a request, tool call, or reply — see What triggers a block.
TAWA_PROXY_SCAN_OUTPUT_TEXTtrueScan the model's own visible reply before it reaches Claude Code — see Scanning the model's own reply.
TAWA_PROXY_OUTPUT_CHUNK_CHARS400Streaming checkpoints vs. standard (whole-reply) scanning for output text — see Streaming checkpoints vs. standard mode.
RUST_LOGinfoStandard 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_BLOCK

An 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=false

An 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=0

This 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_id in 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_overrides in 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 (language in the SDKs) — not passed through.
  • A fully custom block predicate (shouldBlock in the SDKs takes an arbitrary function of the whole ScanResult) — TAWA_PROXY_BLOCK_ACTIONS above covers picking which verdict tiers block, but not arbitrary logic (e.g. "block by confidence score" or "block based on rationale").

If you need any of these today, they're available through the TypeScript or Python SDK directly — just not through this proxy yet.

On this page