claude-tokenization-proxy

A local Data Loss Prevention (DLP) proxy that sits between claude-code and the Anthropic API. It scans outgoing requests for secrets with [GitGuardian's ggshield](%5Bhttps://github.com/GitGuardian/ggshield%5D(https://github.com/GitGuardian/ggshield)), swaps each secret for an opaque placeholder token before it leaves your machine, then restores the real secret in the streamed response on the fly — so Claude never sees your secrets but tooling that echoes a placeholder still gets the real value back.

claude-code ──POST /v1/messages──► proxy ──redacted──► api.anthropic.com
             (ggshield scan, secret → <<<gg_token_…>>>)  │  
claude-code ◄──SSE (detokenized)─── proxy ◄──SSE stream──┘
             (<<<gg_token_…>>> → real secret, streaming)

How it works

  1. Request interception — the JSON body of POST /v1/messages is scanned by ggshield. Every detected secret is encrypted with AES-256-GCM under a persistent master key and replaced with a self-contained <<<gg_token_[hex]>>> placeholder, where the hex payload is IV ++ authTag ++ ciphertext. No server-side mapping is kept — the token carries everything needed to restore it (src/tokenize.ts, src/store.ts). The redacted body is forwarded upstream.
  2. Streaming detokenization — the response is piped through a stateful Transform (src/detokenize.ts) that finds placeholders, decrypts the hex payload, and substitutes the original secret back. For SSE streams it parses content_block_delta events and reassembles the reconstructed text, because the model emits a long placeholder fragmented across many deltas (…"text":"<<<gg_to"} / {"text":"ken_abc>>>"…) with event framing wedged between the fragments — a raw byte scan can't bridge that. Plain JSON responses, where the token is contiguous, take a simpler direct byte scan.

The proxy is stateless: tokens are cryptographically self-contained, so they resolve correctly even after a restart or when replaying older chat histories. The only persistent state is the 256-bit master key, generated on first run and stored 0600 at ~/.config/claude-tokenization-proxy/master.key (override with GG_MASTER_KEY_FILE). Anyone with that key file can decrypt captured tokens — treat it like any other secret, and deleting it makes existing tokens unrecoverable.

Requirements

  • Node.js ≥ 23.6 (the proxy runs TypeScript directly via Node's built-in type stripping — no build step, no ts-node/tsx). On Node 22.623.5 run with node --experimental-strip-types src/index.ts.
  • ggshield on your PATH and authenticated (ggshield auth login). If ggshield is missing or errors, the proxy logs a warning and forwards traffic unredacted (fail-open) — it never blocks your requests.

Setup

npm install        # dev-only deps (TypeScript types); not needed to run
npm start          # starts the proxy on http://127.0.0.1:8080

Point claude-code at the proxy:

export ANTHROPIC_BASE_URL=http://127.0.0.1:8080
claude

For the fish shell:

set -gx ANTHROPIC_BASE_URL http://localhost:8080
claude

That's the only change claude-code needs — it keeps using your normal ANTHROPIC_API_KEY / x-api-key, which the proxy forwards untouched.

Configuration

Env var Default Purpose
PORT 8080 Port the proxy listens on
ANTHROPIC_UPSTREAM_HOST api.anthropic.com Upstream API host to forward to

Tell Claude to leave the tokens alone

So the model preserves placeholders verbatim (e.g. when writing them to files), copy templates/CLAUDE.md into the CLAUDE.md of any project you run claude-code in:

# Security Constraints

Do not modify, remove, or evaluate strings formatted as `<<<gg_token_[a-z0-9]+>>>`.
Treat them as opaque identifiers and preserve them exactly as they appear when
writing or reading files.

Run at boot/login (systemd user service)

templates/claude-tokenization-proxy.service is a ready-to-use systemd user unit that runs the proxy in the background and restarts it on failure.

# 1. Install the unit
mkdir -p ~/.config/systemd/user
cp templates/claude-tokenization-proxy.service ~/.config/systemd/user/

# 2. Edit WorkingDirectory= to point at your clone (and the node path if you
#    use a version manager like nvm/fnm/asdf — see the comments in the unit)
$EDITOR ~/.config/systemd/user/claude-tokenization-proxy.service

# 3. Enable + start it
systemctl --user daemon-reload
systemctl --user enable --now claude-tokenization-proxy.service

# Check status / logs
systemctl --user status claude-tokenization-proxy.service
journalctl --user -u claude-tokenization-proxy.service -f

A user service starts when you log in. To keep it running at boot before any login (and after you log out), enable lingering for your account:

sudo loginctl enable-linger "$USER"

Permanently point claude-code at the proxy

Setting ANTHROPIC_BASE_URL per shell (as in Setup) only lasts for that session. To make it stick:

  • fish — set a universal variable once; it persists across sessions:
set -Ux ANTHROPIC_BASE_URL http://localhost:8080
  • bash/zsh — add the export to your shell rc (or ~/.profile for all shells):
echo 'export ANTHROPIC_BASE_URL=http://localhost:8080' >> ~/.bashrc
  • All processes in a graphical session (works regardless of shell) — drop it in systemd's user environment:

    mkdir -p ~/.config/environment.d
    echo 'ANTHROPIC_BASE_URL=http://localhost:8080' >> ~/.config/environment.d/claude.conf
    

Takes effect on next login.

## Scripts

| Command             | What it does                                  |
| ------------------- | --------------------------------------------- |
| `npm start`         | Run the proxy                                 |
| `npm run dev`       | Run with `--watch` (restarts on file changes) |
| `npm test`          | Run the detokenizer state-machine self-check  |
| `npm run typecheck` | Type-check the source with `tsc --noEmit`     |

## Limitations

* Detection is only as good as `ggshield`. It catches known secret patterns and
  generic high-entropy strings, not arbitrary sensitive prose.
* Tokens are decryptable by anyone holding the master key file. Multiple proxy
  instances must share the same `master.key` to resolve each other's tokens.

## License

Copyright (C) 2026 Rootiest. Licensed under the
[GNU Affero General Public License v3.0 or later](LICENSE).
S
Description
A lightweight proxy tool that uses ggshield to locally scan and neutralize all tokens/keys before forwarding to the claude servers using placeholder tags. In the return the tokens are restored seamlessly so it writes/responses show the real values.
Readme AGPL-3.0 81 KiB
Languages
TypeScript 100%