SecretNAT: your AI needs the shape of a secret, not its value
NAT, but for credentials: swap real secrets for reversible look-alike tokens before the request is built. We grepped the wire to prove it.
Every AI coding tool has the same uncomfortable property: to help you, it reads your code. And your code is full of secrets — API keys in a .env, a database URL with the password inline, a bearer token in a curl command you pasted to ask "why is this 401ing?" The moment any of that lands in a prompt, it has left your machine.
One-way redaction handles part of this: scrub the value, send a hole. But a hole has a cost — the model loses context, and no local tool can ever get the value back through the conversation. Sometimes the agent genuinely needs to work with a secret: write it into a config, use it in a migration, thread it into an API call.
So in rysh I built the reversible version. It's called SecretNAT, and the idea is stolen wholesale from networking.
NAT, but for credentials
NAT — Network Address Translation — swaps a private IP for a public one on the way out and reverses the mapping on the way back. Your fridge talks to the internet; the internet never learns your fridge's real address.
SecretNAT does the same thing at the model boundary: real secrets are swapped for reversible, look-alike tokens before the request is built, and the real value is restored only where a local tool actually needs it. The model provider only ever sees the token. It's on by default.
The "look-alike" part is the trick. In semantic mode, tokens keep the shape of the original:
| Real | What goes over the wire |
|---|---|
sk_live_4eC39HqLyjWDarjtT1zdp7dc |
sk_live_SNAT000001 |
ghp_AbCdEf…0123456789 |
ghp_SNAT000002 |
| a 3-segment JWT | eyJSNAT000003.SNATPAYLOAD.SNATSIGNATURE |
postgres://svc:hunter2@db/app |
password swapped, the rest intact |
Why shape matters: the model's usefulness comes from context. sk_live_ means something — it's a production Stripe key, and a good model will warn you about it, refuse to log it, treat it differently from sk_test_. Replace it with [REDACTED] and that reasoning dies. Replace it with sk_live_SNAT000001 and the model reasons at full fidelity about a credential it does not possess.
There's also a private mode (SECRET_TOKEN_001-style tokens) for when you don't even want the provider to know which vendors you use.
The proof, because you're a skeptic (good)
I don't want "the key never leaves" taken on faith. Here's the test we ran, which you could reproduce: a transparent logging proxy in front of the real Anthropic API, teeing every request body to a file before forwarding. Point rysh at the proxy. Paste a (dummy) live-format Stripe key into a prompt and ask the model about it.
The model answered correctly — it discussed a live Stripe key, what sk_live_ implies, the works. Then, grep the actual bytes that left the machine:
$ grep -c sk_live_51H8DEMOKEYNOTREAL wire.log # the real key
0
$ grep -oE 'sk_live_SNAT[0-9]+' wire.log # what the model saw
sk_live_SNAT000001
Zero occurrences of the real key on the wire. What went out was sk_live_SNAT000001 — same prefix, same shape, reversible by rysh, meaningless to anyone else.
My favorite detail: in one run the model's answer analyzed the suffix and called it "a placeholder-looking value." It told on itself. It never saw the real thing.
The reversible half
Tokenizing outbound text is the easy part. What makes it survivable day-to-day is the reverse direction: when a local tool actually needs the real value — writing your .env, running the migration, calling the real endpoint — SecretNAT restores it just-in-time, in memory, on your machine, on a transient copy that never re-enters the conversation. Tool output is then re-sanitized at the source, before it hits the display buffer or the persisted transcript.
That last bit is the hygiene that's easy to get wrong: rysh persists conversations so you can resume them. Sanitizing only at the HTTP boundary would still write real secrets to disk via tool output. Sanitizing at the source means the cleartext value exists for the duration of one tool call, and nowhere else.
The token↔value mapping itself lives only in memory — and the struct's serializer is deliberately built to fail if anything ever tries to persist it. Belt, meet suspenders.
(How you inspect and reveal that mapping locally — ##snat status, ##snat list, ##snat get, and the ##rst alias — is its own article: you keep the keys.)
SecretNAT is not your AI gateway — it's the other half
If your org runs an AI security gateway — egress policy, DLP, prompt-injection scanning, org-wide audit of model traffic — keep it. SecretNAT doesn't compete with it. They guard different halves of the same request:
- SecretNAT guards the endpoint. Data-level and reversible, running where the prompt is assembled. It prevents the secret from ever entering the request.
- The gateway guards the wire. Flow-level policy and audit across all model traffic, enforced org-wide.
Run both and you get credentials that can't leak on traffic you can govern. That's actual defense-in-depth, not two products fighting over one job.
What it is NOT (don't let me imply otherwise)
- Not a vault or a KMS. No encryption-at-rest, no rotation, no HSM. It's a translation layer in front of a model — use it with your secrets manager, never instead of it.
- Not a defense against a malicious local tool. Restored values exist in memory at tool-execution time, by design. The bounded, testable claim is exactly this: the model provider never receives the value.
- Heuristic on the detected tier. Common credential shapes (Stripe, GitHub, AWS, JWTs, PEM blocks, DB-URL passwords, bearer tokens, and more) are caught by pattern; exotic formats can slip. For secrets you truly care about, register them — the named tier is deterministic.
- Built on Claude, self-hostable, not open source. rysh runs on Anthropic Claude with your own key; I'm not claiming a better model — the value is the governance around it. You can run it on your own infra. The code isn't public.
Poke holes in the threat model in the comments. "The provider never receives the value" is a claim I want stress-tested, not applauded.
rysh is in private beta. If your team has ever said "we can't paste our keys into a model," this feature exists because of that sentence — and our design-partner program (free access, direct founder line, roadmap influence) is how you get it: apply here.
Related: ##snat list and ##snat get: you keep the keys · Client-side secret redaction