diff --git a/README.md b/README.md index 8f9dc80..53acc6f 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ value back. claude-code ──POST /v1/messages──► proxy ──redacted──► api.anthropic.com (ggshield scan, secret → <<>>) │ claude-code ◄──SSE (detokenized)─── proxy ◄──SSE stream──┘ - (<<>> → real secret, streaming) + (real secret ← <<>>, streaming) ``` ## How it works @@ -20,8 +20,7 @@ claude-code ◄──SSE (detokenized)─── proxy ◄──SSE stream── 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 - `<<>>` placeholder, where the hex payload is `IV ++ authTag ++ - ciphertext`. No server-side mapping is kept — the token carries everything + `<<>>` 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 @@ -43,10 +42,10 @@ unrecoverable. ## Requirements -- **Node.js ≥ 23.6** (the proxy runs TypeScript directly via Node's built-in +* **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.6–23.5 run with `node --experimental-strip-types src/index.ts`. -- **`ggshield`** on your `PATH` and authenticated (`ggshield auth login`). If +* **`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. @@ -76,9 +75,9 @@ That's the only change `claude-code` needs — it keeps using your normal ## Configuration -| Env var | Default | Purpose | -| ----------------------- | ------------------ | -------------------------------- | -| `PORT` | `8080` | Port the proxy listens on | +| 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 @@ -90,7 +89,7 @@ project you run `claude-code` in: ```markdown # Security Constraints -Do not modify, remove, or evaluate strings formatted as `<<>>`. +Do not modify, remove, or evaluate strings formatted as `<<>>`. Treat them as opaque identifiers and preserve them exactly as they appear when writing or reading files. ``` @@ -128,50 +127,45 @@ sudo loginctl enable-linger "$USER" ## Permanently point claude-code at the proxy -Setting `ANTHROPIC_BASE_URL` per shell (as in [Setup](#setup)) only lasts for +Setting `ANTHROPIC_BASE_URL` per shell (as in [Setup](%23setup)) only lasts for that session. To make it stick: -- **fish** — set a universal variable once; it persists across sessions: +* **fish** — set a universal variable once; it persists across sessions: + +```fish +set -Ux ANTHROPIC_BASE_URL http://localhost:8080 +``` - ```fish - set -Ux ANTHROPIC_BASE_URL http://localhost:8080 - ``` +* **bash/zsh** — add the export to your shell rc (or `~/.profile` for all shells): -- **bash/zsh** — add the export to your shell rc (or `~/.profile` for all - shells): +```bash +echo 'export ANTHROPIC_BASE_URL=http://localhost:8080' >> ~/.bashrc +``` - ```bash - echo 'export ANTHROPIC_BASE_URL=http://localhost:8080' >> ~/.bashrc - ``` - -- **All processes in a graphical session** (works regardless of shell) — drop +* **All processes in a graphical session** (works regardless of shell) — drop it in systemd's user environment: + +```bash +mkdir -p ~/.config/environment.d +echo 'ANTHROPIC_BASE_URL=http://localhost:8080' >> ~/.config/environment.d/claude.conf +``` - ```bash - mkdir -p ~/.config/environment.d - echo 'ANTHROPIC_BASE_URL=http://localhost:8080' >> ~/.config/environment.d/claude.conf - ``` - - Takes effect on next login. +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` | +| 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 +* Detection is only as good as `ggshield`. It catches known secret patterns and generic high-entropy strings, not arbitrary sensitive prose. -- Re-inserted secrets are assumed to be escape-neutral (typical API - keys/tokens). A secret literally containing a backslash or double-quote could - mis-escape inside the response JSON. See the `ponytail:` note in - `src/detokenize.ts`. -- Tokens are decryptable by anyone holding the master key file. Multiple proxy +* 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 diff --git a/templates/CLAUDE.md b/templates/CLAUDE.md index b74a26a..1f756cb 100644 --- a/templates/CLAUDE.md +++ b/templates/CLAUDE.md @@ -1,3 +1,3 @@ # Security Constraints -Do not modify, remove, or evaluate strings formatted as `<<>>`. Treat them as opaque identifiers and preserve them exactly as they appear when writing or reading files. +Do not modify, remove, or evaluate strings formatted as `<<>>`. Treat them as opaque identifiers and preserve them exactly as they appear when writing or reading files.