{"slug": "show-hn-tokenhush-keeps-your-secrets-out-of-what-claude-code-sends", "title": "Show HN: Tokenhush – keeps your secrets out of what Claude Code sends", "summary": "Tokenhush, an Apache-2.0 local HTTP gateway released by developer fregie, sits between AI coding tools and vendor APIs on 127.0.0.1 to replace detected secrets in outbound request bodies with session-scoped __PII_<type>_<digest>__ placeholders and restore the originals in responses. The tool ships 6 built-in detectors — prefix, jwt, pem, luhn, and email on by default, with entropy opt-in because its false positives on real agent traffic broke function calling — and provides ready-to-paste base-URL snippets for 14 AI coding tools via `tokenhush env <tool>`. Written in pure Go with CGO_ENABLED=0, Tokenhush installs no root certificate, performs no TLS termination or MITM, and disables telemetry by default, with its two optional outbound requests (update check and rule sync) each switchable off.", "body_md": "*What the model receives with Tokenhush running: detected secrets across many files at once, every value replaced by a placeholder.*\n\nTokenhush is a local, loopback-only HTTP gateway. It sits between your AI coding tool and the vendor API. It replaces detected secrets in the outbound request body with session-scoped placeholders, forwards the cleaned request, and restores the originals in the response, so your tool still gets the real values back. The model only ever sees placeholders. It listens on `127.0.0.1` only and installs no root certificate.\n\n```\nYour tool sends      OPENAI_API_KEY=<real key>\nThe model receives   OPENAI_API_KEY=__PII_api_key_ae9c0b46a8f3__\nYour tool gets back  OPENAI_API_KEY=<real key>\n```\n\n**At a glance**\n\n- **Reversible redaction** — every detected secret becomes a session-scoped`__PII_<type>_<digest>__` placeholder in the request body, restored to the original value in the response. The model only ever sees placeholders.\n- **6 built-in detectors** —`prefix` ,`jwt` ,`pem` ,`luhn` , and`email` on by default;`entropy` opt-in.\n- **14 AI coding tools** — each gets a ready-to-paste base-URL snippet from`tokenhush env <tool>` .\n- **Loopback only** — binds`127.0.0.1` (plus`[::1]` when available); no TLS termination, no root certificate, no MITM.\n- **Pure Go, `CGO_ENABLED=0`** — one direct dependency, a single binary for macOS, Linux, and Windows.\n- **Apache-2.0** — no telemetry by default; the two optional outbound requests (update check, rule sync) are each switchable off.\n\n[Quick start](#-quick-start) · [Why Tokenhush](#-why-tokenhush) · [Features](#-features) · [How it works](#-how-it-works) · [Verify it works](#-verify-it-works) · [CLI](#-cli) · [Configuration](#-configuration) · [Documentation](#-documentation)\n\nFive detectors run by default, replacing each match with a session placeholder before the request leaves your machine:\n\n| Detector | Catches | \n|---|---|\n| `prefix` | Vendor key shapes — `sk-` ,`AKIA` ,`ghp_` ,`glpat-` ,`xox*` ,`AIza` ,`npm_` | \n| `jwt` | JSON Web Tokens | \n| `pem` | PEM private-key headers ( `-----BEGIN … PRIVATE KEY-----` ) | \n| `luhn` | Credit-card numbers that pass the Luhn check | \n| `email` | Email addresses ending in a known public suffix ( `.com` ,`.co.uk` , …) | \n\nA sixth detector, `entropy` (high-entropy strings), is off by default and opt-in per workload because its false positives on real agent traffic broke function calling.\n\n**Need more? Rules are extensible.** Three paths, all through the same `Rule` contract in `pkg/filter`:\n\n- **Signed rule packs.**`tokenhush rules sync` fetches Ed25519-signed packs that add detections; a non-weakening floor refuses any pack that disables a built-in detector, drops a required category, or carries an`allow` action.\n- **Declared sensitive keys.** A rule document or pack may list key names (for example`password` ); the value of a matching key is redacted at any object depth.\n- **Compile-time plugins.** Implement the eight-method`Rule` interface in your own Go package; built-ins, signed packs, and third-party rules all enter the same registry and evaluate in one deterministic order.\n\nContract and limits: [docs/plugins.md](https://github.com/fregie/tokenhush/blob/main/docs/plugins.md). Switches: [Configuration](#-configuration).\n\nAny client that lets you set a custom OpenAI-compatible or Anthropic base URL works. Four steps: install, start the gateway, point one tool at it, and route requests to your provider.\n\n| Platform | One-line install | \n|---|---|\n| macOS | `brew install --cask fregie/tap/tokenhush` | \n| Linux | `curl -fsSL https://raw.githubusercontent.com/fregie/tokenhush/main/install.sh \\| bash` | \n| Windows | `irm https://raw.githubusercontent.com/fregie/tokenhush/main/install.ps1 \\| iex` | \n\nThe Linux and Windows installers resolve the release, download the matching archive, verify its sha256 against the release `checksums.txt`, and install the binary to a per-user directory (`~/.local/bin` on Linux, `%LOCALAPPDATA%\\Programs\\tokenhush` on Windows). No admin rights, no package manager. Pin a release with `--version X.Y.Z` (Linux) or `-Version X.Y.Z` (Windows).\n\nThe release wave has shipped, so the installers download and verify the published binary for your platform and need no Go toolchain. The macOS cask tracks the latest published release.\n\nPrefer to build it yourself?\n\n```\ngo install github.com/fregie/tokenhush/cmd/tokenhush@main\n```\n\n`@latest` still resolves to the older published tag, so use `@main` for this line. Put the binary on your `PATH` so the examples below work verbatim. Full install paths, service wrappers, and release status: [docs/deployment.md](https://github.com/fregie/tokenhush/blob/main/docs/deployment.md).\n\n```\ntokenhush run\n```\n\nIt stays in the foreground, listens on `http://127.0.0.1:8787` by default, and exits on Ctrl-C. On start it prints a banner: the loopback endpoint, the effective upstream routing (configured entries plus the built-in fallbacks), and the two base-URL forms to point a tool at. Leave it running and open a second terminal.\n\nTokenhush works with any client that lets you override its base URL. These 14 ship with a ready-to-paste snippet from `tokenhush env <tool>`:\n\n| Your tool | What to run | \n|---|---|\n| [Claude Code](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#claude) | `eval \"$(tokenhush env claude)\"` then`claude` | \n| [Codex CLI](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#codex) | `tokenhush env codex` — paste into`~/.codex/config.toml` (API-key mode only) | \n| [Aider](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#aider) | `eval \"$(tokenhush env aider)\"` then`aider` | \n| [Cline](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#cline) | `tokenhush env cline` — set the OpenAI Compatible base URL | \n| [Roo Code](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#roo) | `tokenhush env roo` — set the OpenAI Compatible base URL | \n| [opencode](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#opencode) | `tokenhush env opencode` — paste into`opencode.json` | \n| [Qwen Code](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#qwen) | `eval \"$(tokenhush env qwen)\"` then`qwen` | \n| [Charm Crush](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#crush) | `tokenhush env crush` — paste into`crush.json` | \n| [Zed](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#zed) | `tokenhush env zed` — paste into`settings.json` | \n| [Continue.dev](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#continue) | `tokenhush env continue` — paste into`~/.continue/config.yaml` | \n| [Open WebUI](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#openwebui) | `eval \"$(tokenhush env openwebui)\"` , then start the server | \n| [Goose](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#goose) | `eval \"$(tokenhush env goose)\"` then`goose` | \n| [OpenHands](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#openhands) | `eval \"$(tokenhush env openhands)\"` | \n| [Kilo Code](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md#kilo) | `tokenhush env kilo` — set the OpenAI Compatible base URL | \n\nTwo rules cover every tool:\n\n- Anthropic-style clients take the bare origin: `http://127.0.0.1:8787` .\n- OpenAI-compatible clients take `/v1` :`http://127.0.0.1:8787/v1` .\n\nUsing something else? Set its OpenAI-compatible base URL to `http://127.0.0.1:8787/v1`, or its Anthropic base URL to `http://127.0.0.1:8787`. Step-by-step guides for every tool, including the exact file to edit, are in [docs/tool-setup.md](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md).\n\nWith no config, OpenAI-compatible paths go to `https://api.openai.com` and Anthropic paths go to `https://api.anthropic.com`. **Using a relay or any other endpoint? Set the upstream first**, or the gateway will forward to the wrong provider.\n\nCreate `tokenhush.yaml` in the config directory (or point `tokenhush run --config PATH` at it):\n\n```\nupstreams:\n  - match: /v1/chat/completions\n    target: https://your-provider.example.com\n```\n\n- Use the provider **origin** (plus any prefix that comes before`/v1` ), with no trailing slash and**no `/v1`** . Your tool already sends`/v1/chat/completions` , and the gateway appends the request path.\n- Keep your provider's API key in the tool's own config. The gateway forwards auth headers untouched and redacts only the request **body** .\n\nRouting is by **request path**, not provider name, and one path maps to one upstream. A relay that serves many models behind `/v1` is fine: the model is chosen by the request body, not by the route.\n\nWorked routing examples: [docs/configuration.md#routing-requests-to-upstreams](https://github.com/fregie/tokenhush/blob/main/docs/configuration.md#routing-requests-to-upstreams). Every key in the config file: [docs/configuration.md#configuration-reference](https://github.com/fregie/tokenhush/blob/main/docs/configuration.md#configuration-reference), or the [Configuration](#-configuration) section below.\n\nAI coding tools need your code to be useful, so they read a lot: open files, the whole repo, config, and the keys lying around it. A lot of that leaves your machine with every request. Opt-outs exist, but they are easy to get wrong or forget, and they vary from tool to tool. Once a request is sent, there is no undo.\n\nTokenhush adds one checkpoint in front of the tool. It reads each request, replaces anything that looks like a secret, and forwards the cleaned version. You keep working the way you always have. You just stop shipping your secrets along with it.\n\n- **Every nested field is walked.** Tokenhush walks the entire outbound JSON request body, so secrets buried in nested objects and arrays are seen, not just top-level fields. Responses, including SSE streams, are buffered whole before anything is committed.\n- **Six built-in detectors, five on by default.**`prefix` (known key shapes:`sk-` ,`AKIA` ,`ghp_` ,`glpat-` ,`xox*` ,`AIza` ,`npm_` ),`jwt` ,`pem` (PEM private-key headers),`luhn` (Luhn-checked card numbers), and`email` are on. The sixth,`entropy` (high-entropy strings), is off by default because its false positives on real agent traffic, long tool names and session ids, broke function calling. Turn it on only for a workload that carries no images or long random identifiers.\n- **Precise email, parameterized by rule options.** The`email` detector fires only when the address's domain ends at a label boundary with a known public suffix (`.com` ,`.co.uk` ), so subdomains count and a look-alike such as`evilcorp.com` is rejected when the narrower`.corp.com` is the configured suffix in`replace` mode (an additive`.corp.com` still carries the built-in`.com` , so`evilcorp.com` would match). The built-in suffix table is compiled in, frozen, and always on. A rule document or signed pack may carry a typed, strictly validated`options` object: an unknown option key is a typed error, and the only detector option today is`email` , whose`suffixes` list adds suffixes to the built-in set and whose`replace` flag swaps that set out — permitted only for a non-remote local document, and refused by the floor for a remote pack.\n- **Placeholders are stable for the session.** A match becomes`__PII_<type>_<digest>__` , for example`__PII_api_key_ae9c0b46a8f3__` . The secret-to-placeholder mapping lives in memory only, for this session. Restarting drops it.\n- **Loopback only, with checks.** The gateway binds`127.0.0.1` , plus`[::1]` when the host has an IPv6 loopback. The`Host` header is always checked,`Origin` is checked for browser-style requests, and the control API sits behind a per-run bearer token stored`0600` . It fails closed rather than open.\n- **A 14-tool `env` helper.**`tokenhush env <tool>` prints a ready-to-paste snippet for`claude` ,`codex` ,`aider` ,`cline` ,`roo` ,`opencode` ,`qwen` ,`crush` ,`zed` ,`continue` ,`openwebui` ,`goose` ,`openhands` , and`kilo` .\n- **Signed rule sync, with a non-weakening floor.**`tokenhush rules sync` fetches an Ed25519-signed rule pack and verifies its signature, freshness, serial (no rollback), and the signed revocation list before use. The floor rejects exactly four things: a pack that disables a built-in detector, a pack that drops a required category, a rule that carries an`allow` action, and a rule that sets the email`replace` flag. So a pack may add detections and extend the built-in email suffix set, but can never weaken the built-ins. Packs load at the next start, never hot, and any problem falls back to the built-in defaults with a warning.\n- **Named sensitive keys are redacted by value.** A signed pack or compiled document may declare`sensitive_keys` (strict sub-object:`keys` up to 256 names,`case_sensitive` default false); the value of a matching immediate object member key, for example`password` , is redacted at any object depth on the request path. No inner substitution is minted, a`Block` rule or blocklist hit on the leaf still blocks, and allowlists still win. The container-value and k8s/docker-env sibling shapes stay unmatched and are recorded in[docs/security.md](https://github.com/fregie/tokenhush/blob/main/docs/security.md) .\n- **Small, portable, extensible.** Pure Go, built with`CGO_ENABLED=0` . Extension points are compile-time: the`Rule` contract in`pkg/filter` is how a built-in detector, a signed pack, and a third-party rule all enter the same registry.\n\n- **Outbound:** the gateway walks the JSON body, runs the enabled detectors, and turns each match into a session placeholder before forwarding upstream.\n- **Inbound:** the whole response, SSE included, is buffered and decoded first — nothing streams token-by-token — then placeholders this session minted are swapped back to the originals, and only your tool receives them. A foreign placeholder is returned unchanged.\n\nImportant\n\nPlaceholders are **never** filled back in on the way out. Only your client gets the originals. That is what blocks prompt-injection tricks that try to make the gateway echo a secret back to the model.\n\nThe fastest check needs only the gateway's own log. Run `tokenhush run`, watch its stderr while your tool works. Startup prints the endpoint and the routing, every value it redacts produces one masked line, every response-side restore produces one count line, and every locally generated request-side refusal produces one refusal line:\n\n```\ntokenhush: redacted request api_key (len=32) sk-p…j0\ntokenhush: restored response placeholders=1\ntokenhush: refused request body_too_large\n```\n\nAll three lines are metadata only and go to stderr only, never persisted. The redaction line carries the detector type, the matched byte length and a masked form, never the full value; a `private_key` match names its PEM header kind and an `email` match shows only its domain; the restore line carries only a count; the refusal line carries only the refusal code, plus a classified `reason=` or `rule_id=` where the refusal already carries one, and exactly one is written per locally generated request-side refusal. `tokenhush status` reports the redaction counts as JSON:\n\n```\ntokenhush status --json\n```\n\nFor a full round trip, run a local echo upstream on `127.0.0.1:9999` so you can see exactly what left the gateway:\n\n``` python\npython3 - <<'PY'\nimport http.server, sys\nclass Echo(http.server.BaseHTTPRequestHandler):\n    def do_POST(self):\n        body = self.rfile.read(int(self.headers.get(\"Content-Length\", 0)))\n        sys.stderr.write(\"upstream received: \" + body.decode() + \"\\n\"); sys.stderr.flush()\n        self.send_response(200); self.send_header(\"Content-Type\", \"application/json\")\n        self.send_header(\"Content-Length\", str(len(body))); self.end_headers()\n        self.wfile.write(body)\n    def log_message(self, *args): pass\nhttp.server.HTTPServer((\"127.0.0.1\", 9999), Echo).serve_forever()\nPY\n```\n\nPoint a throwaway gateway at it (leaving your real config alone), then start it:\n\n```\nexport TOKENHUSH_HOME=\"$(mktemp -d)\"\nmkdir -p \"$TOKENHUSH_HOME/config\"\ncat > \"$TOKENHUSH_HOME/config/tokenhush.yaml\" <<'YAML'\nlisten:\n  host: 127.0.0.1\n  port: 8787\nupstreams:\n  - match: /v1/chat/completions\n    target: http://127.0.0.1:9999\nYAML\ntokenhush run\n```\n\nSend one request with a fake secret in it:\n\n```\ncurl -sS http://127.0.0.1:8787/v1/chat/completions \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"model\":\"echo\",\"messages\":[{\"role\":\"user\",\"content\":\"my email is me@example.com\"}]}'\n```\n\nThree things to check:\n\n1. The echo upstream terminal prints the body with the address replaced by `__PII_email_<digest>__` . The secret left as a placeholder.\n2. The gateway terminal prints two lines: `tokenhush: redacted request email (len=14) ****@example.com` and`tokenhush: restored response placeholders=1` .\n3. The `curl` output contains the original address again, restored by the gateway on the response path. The upstream never saw the secret, and the client never saw the placeholder.\n\n`tokenhush status --json` reports `\"redactions\": 1` for that request. The full recipe, including how to read the status document, is in [docs/verify.md](https://github.com/fregie/tokenhush/blob/main/docs/verify.md).\n\n*The client-side view: a token is pasted into the tool, and the assistant reports that it only ever received a `__PII_custom_...__` placeholder.*\n\n| Approach | What it gives you | What it does not | \n|---|---|---|\n| Trust `.gitignore` and provider opt-outs | No extra software. You keep secrets out of the files you remember to exclude, and you can disable training or logging features per provider. | You have to get every pattern right and keep it right. A tool that reads the repo, an `.env` you forgot to exclude, or a key pasted into a prompt still leaves the machine. There is no undo after a request is sent. | \n| Turn off the features that send too much | Fewer bytes leave, sometimes a lot fewer. | You lose capability, the setting can drift or be reset by an update, and it does nothing about secrets that reach the request anyway. | \n| A local MITM proxy with a root certificate | Can inspect and rewrite traffic from any client on the machine, including ones with no base-URL setting. | It terminates TLS, so you install a root certificate and add a permanently trusted party to your machine. That is a large change in trust for a secret-scrubbing feature. | \n| Tokenhush | A loopback HTTP gateway your tool points at. It replaces secrets with reversible placeholders in the request body and restores them in the response. No TLS termination, no root certificate, and the mapping lives in memory only. | It only covers tools you can point at a base URL (14 helpers ship). It does not redact the response path, does not catch encoded secrets, and does not cover clients that ignore base URLs. | \n\n**Does Tokenhush see my keys?**\nYes, in process and in memory. It has to read each request body to find and replace secrets. What it never does is persist them: no request or response bodies, no detected secrets, and no placeholder-to-secret mapping are written to disk. The redaction log is masked and goes to stderr only.\n\n**Does it write secrets to disk?**\nNo. The only files it writes are metadata: `run.json` (`pid`, `port`, `addrs`, `started_at`), the `0600` control token, the verified rules cache, and the update anti-rollback mark. Bodies and mappings stay in memory.\n\n**Does it slow me down?**\nIt runs in the request path on loopback, walks the request body, and forwards it to the provider. It does not terminate TLS and adds no network hop beyond the one your tool already makes to the provider. Measured overhead is not published yet, so treat any figure you see as unverified.\n\n**Does it work offline?**\nThe data path is local: the gateway binds loopback and talks to your provider, which needs network anyway. The only two requests Tokenhush itself can make to the vendor, update check and rule sync, are both switchable off. Nothing else leaves the machine.\n\n**Will it break my tool's function calling?**\nThe five default detectors are the conservative set. `entropy` is off by default precisely because its false positives on long tool names and session ids broke function calling in testing; leave it off unless your workload carries no images, data URLs, or long random identifiers. Placeholders are stable per session and restored on the response path, so the tool still receives the values it sent.\n\n**Does it work with a company HTTP proxy?**\nTokenhush does not add a proxy of its own, and the strict config schema has no proxy key. The gateway makes a normal outbound HTTPS connection to the provider origin you configure, so it needs the same outbound reachability your tool has. If access in your environment goes through a corporate proxy, treat the gateway like any other CLI on the machine.\n\n**Where do the two vendor requests go, and can I turn them off?**\nBoth go to `updates.tokenhush.com`, and both are command-scoped: update check runs only on `tokenhush update`, rule sync only on `tokenhush rules sync`, and each short-circuits before any network call when its switch is set. Retention for both is 30 days. Switch them off with `TOKENHUSH_NO_UPDATE_CHECK=1` and `TOKENHUSH_NO_RULE_SYNC=1`. `tokenhush privacy` prints the disclosure. Beyond those two, the only traffic leaving the machine is your own requests to your provider.\n\n- **No MITM and no root certificate.** Tokenhush never terminates TLS. That is also why Cursor agent traffic, the ChatGPT and Claude desktop apps, and browser web UIs are not covered: they do not honour a configurable base URL, and covering them would need system-level interception.\n- **The response path does not redact.** Response-scoped rules can allow, warn, or block only. Responses, including SSE streams, are buffered whole before any byte is committed, so a block is a`502` with nothing already sent and there is no token-level streaming. A response over the`response_buffer_bytes` cap is a`502` and one past the`response_timeout` deadline is a`504` , both before commit.\n- **Encoded secrets are not caught.** A secret that is base64-, hex-, or URL-encoded before it leaves is not detected; the rewrite has no normalization pass by design. A non-identity request`Content-Encoding` is refused with 415 rather than decoded for detection.\n- **A secret in a JSON object key is not caught.** Only values are walked.\n- **There is no `doctor` command, no allowlist-mutation command, and no service command.** Plugins are compile-time only, so there is no runtime plugin loading.\n\n```\ntokenhush run          start the gateway in the foreground\ntokenhush rules        sync signed detection rules or roll back\ntokenhush update       check for and apply a signed self-update\ntokenhush status       read the running gateway's metadata\ntokenhush env <tool>   print a tool setup snippet\ntokenhush version      print version and build information\ntokenhush privacy      show the vendor-bound egress disclosure\n```\n\n| Command | What it does | Flags | \n|---|---|---|\n| `tokenhush run` | Starts the gateway in the foreground. Default listen `127.0.0.1:8787` . Exits on Ctrl-C. | `--config PATH` ,`--port N` (1..65535),`--log-level debug\\|info\\|warn\\|error` ,`--log-redactions` (default true;`--log-redactions=false` silences the redaction and restore logs) | \n| `tokenhush rules` | `sync [--check]` verifies and activates the signed rule pack;`rollback` returns to the previous verified serial, or the built-in defaults. | `sync --check` | \n| `tokenhush update` | Checks for and applies a signed self-update. Homebrew and Scoop installs delegate to their package manager; a self-managed install self-replaces. | `--check` | \n| `tokenhush status` | Reads the running gateway's metadata. Human form is `key: value` lines;`--json` emits the frozen status document. When nothing is running it prints`not running` and exits 1. | `--json` | \n| `tokenhush env <tool>` | Prints a ready-to-paste setup snippet for one of the 14 tools. | `--config PATH` ,`--port N` | \n| `tokenhush version` | Prints `tokenhush v<version> <os>/<arch> <goversion> (commit …, built …)` , where`<version>` is the release version. | none | \n| `tokenhush privacy` | Prints the vendor-bound egress disclosure: exactly two categories, each with its switch, host, and retention. | `--json` | \n\nEvery command exits `0` on success, `1` when a check or operation fails, and `2` on a usage error (unknown command or tool, bad flag value).\n\n`tokenhush run` stays in the foreground and exits on Ctrl-C. There is no built-in service command, so for auto-start use your OS's own tools: a launchd agent on macOS, a systemd user unit on Linux, or a Task Scheduler entry on Windows.\n\nTokenhush reads `tokenhush.yaml`. A missing file means defaults, and the schema is closed: an unknown key is an error, not a warning.\n\n```\nlisten:            {host: 127.0.0.1, port: 8787}\nlog:               {level: info}\ndetectors:         {prefix: true, email: true, luhn: true, jwt: true, pem: true, entropy: false}\nallowlist:         [\"literal\"]\nupstreams:         [{match: \"/v1/chat/completions\", target: \"https://api.openai.com\"}]\nscan_budget_bytes: 33554432\ndetector_timeout:  30s\nmax_body_bytes:    67108864\nresponse_buffer_bytes: 33554432\nresponse_timeout:  5m\n```\n\n| Location | Configuration | Data | \n|---|---|---|\n| macOS | `~/Library/Application Support/tokenhush/config/` | `~/Library/Application Support/tokenhush/Data/` | \n| Linux | `${XDG_CONFIG_HOME:-~/.config}/tokenhush/` | `${XDG_DATA_HOME:-~/.local/share}/tokenhush/` | \n| Windows | `%AppData%\\tokenhush\\` | `%LOCALAPPDATA%\\tokenhush\\` | \n\nSet `TOKENHUSH_HOME` to move both under one root.\n\n| Key | Meaning | \n|---|---|\n| `listen.host` /`listen.port` | Only `127.0.0.1` ,`::1` , or`localhost` ;`0.0.0.0` is rejected. Port 1..65535, default 8787. | \n| `log.level` | `debug` ,`info` ,`warn` , or`error` , default`info` . | \n| `detectors` | The six switches: `prefix` ,`email` ,`luhn` ,`jwt` ,`pem` ,`entropy` . Five are on by default;`entropy` is off by default. | \n| `allowlist` | Literals that are never redacted. | \n| `upstreams` | A **list** of`{match, target}` entries, not a map.`match` is a path prefix;`target` is an origin with no trailing slash. | \n| `scan_budget_bytes` | Per-leaf, per-detector scan budget, default `33554432` (32 MiB). A primitive detector scans at most this many bytes of one leaf. | \n| `detector_timeout` | Per-detector time backstop, default `30s` . | \n| `max_body_bytes` | Memory guard on the total request body, default `67108864` (64 MiB). A body over it is refused with 403`body_too_large` at the shared read seam, before any walk or upstream dial, and is never truncated or partially forwarded. | \n| `response_buffer_bytes` | Total cap on one buffered response, default `33554432` (32 MiB). Over the cap is a`502` before commit. | \n| `response_timeout` | Overall bound on reading one response, default `5m` . Past the deadline is a`504` before commit; the cap wins if both trip. | \n\nUnmatched paths fall back to the built-ins: `/v1/messages` goes to Anthropic, and `/v1/chat/completions` and `/v1/responses` go to OpenAI. `GET /v1/models` is the one named exception and defaults to OpenAI. Any other unknown path is an explicit error, never a silent misroute. Routing is by request path; the model is chosen by the request body.\n\nThe full reference — every key with its default, the complete built-in routing table, the match rules and precedence, and worked routing examples — is in [docs/configuration.md](https://github.com/fregie/tokenhush/blob/main/docs/configuration.md).\n\nTokenhush binds loopback only: `127.0.0.1` always, plus `[::1]` when the host has an IPv6 loopback. The Host allowlist is always enforced, and `Origin` is checked for browser-style requests.\n\nThe control surface is exactly `GET /status`, behind a per-run bearer token stored `0600`. There is no allowlist-mutation endpoint and no second endpoint. A non-GET request gets a JSON 405 with `Allow: GET`; any other GET gets a JSON 404.\n\nNothing is persisted: no request or response bodies, no detected secrets, and no placeholder-to-secret mapping. Only metadata is written, to `run.json`, the control token, the verified rules cache, and the update anti-rollback mark. The design fails closed: a detector failure refuses the request rather than forwarding it unredacted, and a non-identity `Content-Encoding` is rejected with 415 rather than decoded.\n\nThe only traffic Tokenhush itself can send to the vendor is exactly two switchable categories, both to `updates.tokenhush.com` with 30-day retention: update-check (switch off with `TOKENHUSH_NO_UPDATE_CHECK=1`) and rule-sync (switch off with `TOKENHUSH_NO_RULE_SYNC=1`). `tokenhush privacy` prints the disclosure. For the threat model and every invariant, see [docs/security.md](https://github.com/fregie/tokenhush/blob/main/docs/security.md); for the generated disclosure, see [docs/generated/network-egress.md](https://github.com/fregie/tokenhush/blob/main/docs/generated/network-egress.md); to report a vulnerability, see [SECURITY.md](https://github.com/fregie/tokenhush/blob/main/SECURITY.md).\n\n| Platform | One-line install | \n|---|---|\n| macOS | `brew install --cask fregie/tap/tokenhush` | \n| Linux | `curl -fsSL https://raw.githubusercontent.com/fregie/tokenhush/main/install.sh \\| bash` | \n| Windows | `irm https://raw.githubusercontent.com/fregie/tokenhush/main/install.ps1 \\| iex` | \n\nThe Linux and Windows installers verify the archive's sha256 against the release `checksums.txt` before installing anything, need no admin rights, and accept a pinned version (`--version X.Y.Z` / `-Version X.Y.Z`). To build from source instead, use Go 1.25+: `go build -o tokenhush ./cmd/tokenhush`, or `go install github.com/fregie/tokenhush/cmd/tokenhush@main`.\n\nThe installers download and verify the published binary for your platform, so no Go toolchain is needed. The full set of install paths, service wrappers, and release status is in [docs/deployment.md](https://github.com/fregie/tokenhush/blob/main/docs/deployment.md).\n\n| Platform | Targets | Notes | \n|---|---|---|\n| macOS | arm64, amd64 | Pure Go, `CGO_ENABLED=0` , no C toolchain needed. | \n| Linux | arm64, amd64 | Pure Go, `CGO_ENABLED=0` . | \n| Windows | arm64, amd64 | Pure Go, `CGO_ENABLED=0` . | \n\n| Document | What's inside | \n|---|---|\n| [docs/configuration.md](https://github.com/fregie/tokenhush/blob/main/docs/configuration.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/configuration.zh-CN.md) | Every `tokenhush.yaml` key, upstream routing, and where the file lives. | \n| [docs/tool-setup.md](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/tool-setup.zh-CN.md) | Per-tool setup for the 14 tools. | \n| [docs/verify.md](https://github.com/fregie/tokenhush/blob/main/docs/verify.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/verify.zh-CN.md) | The echo-upstream verification recipe in full. | \n| [docs/deployment.md](https://github.com/fregie/tokenhush/blob/main/docs/deployment.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/deployment.zh-CN.md) | Install and build paths, service wrappers, release status. | \n| [docs/architecture.md](https://github.com/fregie/tokenhush/blob/main/docs/architecture.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/architecture.zh-CN.md) | Layered architecture, the rule abstraction, and the data path. | \n| [docs/security.md](https://github.com/fregie/tokenhush/blob/main/docs/security.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/security.zh-CN.md) | Security model, the eight invariants, and the residual-risk register. | \n| [docs/plugins.md](https://github.com/fregie/tokenhush/blob/main/docs/plugins.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/plugins.zh-CN.md) | The `Rule` extension point and its restrictions. | \n| [docs/generated/network-egress.md](https://github.com/fregie/tokenhush/blob/main/docs/generated/network-egress.md) /[中文](https://github.com/fregie/tokenhush/blob/main/docs/generated/network-egress.zh-CN.md) | The two switchable vendor-bound egress categories, generated from `egress.yaml` . | \n| [CONTRIBUTING.md](https://github.com/fregie/tokenhush/blob/main/CONTRIBUTING.md) /[中文](https://github.com/fregie/tokenhush/blob/main/CONTRIBUTING.zh-CN.md) | How to build, test, and contribute. | \n| [SECURITY.md](https://github.com/fregie/tokenhush/blob/main/SECURITY.md) /[中文](https://github.com/fregie/tokenhush/blob/main/SECURITY.zh-CN.md) | Vulnerability disclosure policy. | \n| [CHANGELOG.md](https://github.com/fregie/tokenhush/blob/main/CHANGELOG.md) /[中文](https://github.com/fregie/tokenhush/blob/main/CHANGELOG.zh-CN.md) | Release history for the from-scratch core (v0.5.0 onward). | \n| [LICENSE](https://github.com/fregie/tokenhush/blob/main/LICENSE) | Apache License 2.0. | \n\nThis repository is the from-scratch core, first released as v0.5.0. It exposes the seven-command surface (`run`, `rules`, `update`, `status`, `env`, `version`, `privacy`), a strict validated `tokenhush.yaml`, and the security invariants documented in [docs/security.md](https://github.com/fregie/tokenhush/blob/main/docs/security.md). CI runs unit tests plus the guard suites on Linux, macOS, and Windows.\n\nContributions are welcome. See [CONTRIBUTING.md](https://github.com/fregie/tokenhush/blob/main/CONTRIBUTING.md) for development setup, testing, and pull request guidelines.", "url": "https://wpnews.pro/news/show-hn-tokenhush-keeps-your-secrets-out-of-what-claude-code-sends", "canonical_source": "https://github.com/fregie/tokenhush", "published_at": "2026-09-24 03:59:58+00:00", "updated_at": "2026-09-24 04:33:57.163087+00:00", "lang": "en", "topics": ["ai-tools", "ai-safety", "developer-tools", "ai-agents"], "entities": ["Tokenhush", "fregie", "Claude Code", "OpenAI", "Anthropic", "GitHub", "Go"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-tokenhush-keeps-your-secrets-out-of-what-claude-code-sends", "markdown": "https://wpnews.pro/news/show-hn-tokenhush-keeps-your-secrets-out-of-what-claude-code-sends.md", "text": "https://wpnews.pro/news/show-hn-tokenhush-keeps-your-secrets-out-of-what-claude-code-sends.txt", "jsonld": "https://wpnews.pro/news/show-hn-tokenhush-keeps-your-secrets-out-of-what-claude-code-sends.jsonld"}}