How it works
Why Pretense mutates secrets rather than redacting them, and how the round trip stays byte-exact.
Mutation, not redaction
Redaction destroys. A redacted secret cannot come back, so the model sees a hole where your code was and the answer degrades with it.
Pretense swaps each secret and PII value for a deterministic synthetic before the request leaves your machine, and renames the code names inside fenced code blocks the same way. What reaches the provider is the synthetic. On the way back the code names are restored byte for byte, so what reaches you reads as your own code. Secrets and PII are never put back into the reply: they stay synthetics, because a reply that echoed one could carry your real key into a link an attacker chose.
your code ──▶ mutate ──▶ provider sees synthetics
│
▼
reversal map (stays on your machine)
│
your result ◀── reverse ◀── provider responseThe round trip
Reversal is byte-exact or it is a bug. You do not have to run it yourself: the proxy mutates every request on the way out and restores your code names on the way back, so the round trip happens on each call. Secret and PII synthetics are never put back.
pretense run claude pretense audit --limit 20
The audit log records what was swapped, request by request, so you can confirm the outbound side rather than take it on trust.
The same engine runs over a single file through mutate and reverse. Both take flags that decide where the file and its map are written, and those are in the CLI reference.
What gets mutated
| Kind | Example | Action |
|---|---|---|
| API keys and tokens | sk-…, AKIA…, ghp_… | Swapped, stays a token in the reply |
| PII | SSN, email | Swapped, stays a token in the reply |
| Identifiers | variables, functions, classes | Renamed in fenced code, restored in the reply |
| Comments and strings | // comments, string literals | Preserved verbatim |
Through the proxy, Pretense swaps secret and PII values and also renames the function, class and variable names inside fenced code blocks, so the provider sees neither your credentials nor your real symbol names. Each name gets the same synthetic everywhere in the request, which keeps the structure of your code intact for the model. Text outside a fence keeps its names as written; only its secret and PII values are swapped.
Two paths keep names as written by design. A local runner you declare under air-gap sees real names, because the default air-gap policy, secrets-pii-only, skips the rename for that runner alone. The file-level mutate command swaps secret values only. What init records is a code graph of those names, not a rename of them.
Where the map lives
The way back is a reversal map, encrypted with AES-256-GCM and written owner-only into ~/.pretense/maps/. It is not part of the request, so it is not something the provider receives.
Mutating a file in place writes that file's map and prints the exact command that restores it, so the way back exists before you need it. A mutation you cannot reverse is a bug, not a mode.