Tawa docs

Claude Code

Protect a Claude Code session without changing any code — point it at a local relay instead of api.anthropic.com.

Every other integration on this site works by calling the Tawa SDK from inside your own application. tawa-claude-proxy is different: it's a standalone relay that sits between the Claude Code CLI and Anthropic, so you get scanning without editing Claude Code itself or writing any integration code at all.

Claude Code reads ANTHROPIC_BASE_URL once at startup and routes every request through whatever's listening there — including the requests made inside its own agentic sub-loops, not just the first one. Point that at tawa-claude-proxy instead of api.anthropic.com directly, and every request is scanned before it goes anywhere near Anthropic:

                    ┌─▶ your Tawa account's hosted
                    │   detection (request scan)
                    │
Claude Code ──ANTHROPIC_BASE_URL──▶ tawa-claude-proxy ──▶ api.anthropic.com
                    │                                            │
                    │              (streamed response)  ◀────────┘
                    └─▶ your Tawa account's hosted
                        detection (tool-call scan + model's own
                        reply, before either reaches Claude Code)

Newer than the SDK integrations

This is a newer integration path than the TypeScript/Python SDKs, verified end-to-end against real infrastructure (a real local-stack tawa-gateway, real api.anthropic.com) rather than against a long-lived, real-world Claude Code session by the Tawa team. Per-user identifiers and Policy Studio rules aren't wired in yet — see Scope today.

What gets scanned

Both directions of every POST /v1/messages are inspected.

Inbound, before it reaches Anthropic: the relay looks at the newest message in the transcript — Claude Code resends the whole conversation on every turn, but only ever appends one new message before its next request, so scanning just that one keeps the cost of scanning flat instead of growing with the conversation.

  • If it's a fresh human turn, it's scanned as user_prompt.
  • If it's a tool_result being fed back into context, it's scanned as tool_output. This is the indirect-injection vector worth taking seriously: a file, webpage, or command output a tool reads can carry instructions aimed at the model, and this is what catches that before it re-enters context.

Outbound, before Claude Code (or you) can act on it: two kinds of content block in the streamed response are buffered and scanned, for two different reasons — everything else (thinking blocks, message-level events) streams through live, unbuffered.

  • A proposed tool call — any tool_use block — is scanned via Tawa's agent tool-call route the instant it finishes streaming, before Claude Code ever sees it and can execute it. This costs no extra latency Claude Code wasn't already going to spend: it can't act on a tool call until its arguments have fully streamed in as valid JSON anyway. This is the differentiated part of the whole integration — a framework-level SDK guardrail only ever sees one model call in isolation, with no visibility into what a coding agent does with the response, because tool execution happens locally and opaquely to any SDK-level hook. This is the one place in Tawa's current integrations positioned to catch a hijacked model proposing a destructive or exfiltrating action before it runs.
  • The model's own visible reply — any text block — is scanned too, on by default. This is a different threat shape from a tool call: a hijacked model can exfiltrate something it read (a secret in a file, credentials from a tool result) by simply printing it in its answer, no tool call involved at all. By default this is scanned in streaming checkpoints (every ~400 characters of new text, the whole reply-so-far is scanned and released) rather than held until the reply finishes — closer to normal streaming, at the cost of one scan call per checkpoint instead of one per reply. Both the checkpoint size and whether output-text scanning runs at all are configurable — see Scanning the model's own reply for the full cost/delay trade-off.

Any direction: a BLOCK, HARD_BLOCK, or SANDBOX verdict stops it — a plain Anthropic-shaped error on the request side (Claude Code never reaches api.anthropic.com), or a protocol-level event: error SSE frame on the response side that ends the stream before the blocked tool call or reply ever reaches the client. Every other verdict (ALLOW, REVIEW, SOFT_BLOCK, AMBIGUOUS, OOD) forwards through by default: there's no human sitting in a local CLI's loop to hand a REVIEW verdict to, so only a real block stops anything by default — and that default is itself overridable per deployment. A REVIEW the gateway has already collapsed to BLOCK for the app's content-type setting is a block here. See Configuration for how that's decided, and how to change it.

Everything not buffered forwards byte-for-byte — this proxy never re-serializes an SSE event from parsed fields, only inspects a parsed copy and forwards the original bytes.

RAG and retrieval

There's no separate "RAG" code path, deliberately. Claude Code has no distinct retrieval concept of its own — a vector search, a grep, a web fetch, or an actual RAG tool all surface identically as a tool_result block re-entering context, already covered by the inbound tool_output scanning above. A retrieval-specific MCP tool gets scanned the same way every other tool output does, with no special-casing needed.

Scope today

What's covered:

  • Every user prompt and every tool result read back into context.
  • Every proposed tool call, scanned before Claude Code can execute it.
  • The model's own visible reply, scanned before it reaches Claude Code (on by default, toggleable).
  • Any of Claude Code's agentic sub-loops, since they all go through the same ANTHROPIC_BASE_URL override.

What isn't built yet:

  • Parallel/interleaved tool calls. The gate tracks multiple concurrent buffered blocks correctly (keyed by content-block index), but this is untested against a real parallel-tool-call response — Claude Code has fine-grained tool streaming off by default behind a custom base URL, so blocks are generated sequentially in the common case.
  • A /compact false-positive risk: a compaction request's payload doesn't fit the "scan the newest message" model cleanly. Diagnosed but not yet fixed.
  • Anything besides Claude Code — a Codex CLI relay is planned (it speaks OpenAI's Responses API, a different wire protocol entirely) but not built.
  • /v1/messages/count_tokens and /v1/models — both optional per Claude Code's own gateway docs, skipped for now.

Get started

On this page