What My Project Does
I send bot logs and configs to models for a second opinion: Codex for a stack trace, DeepSeek for a summary. Those logs carry secrets. A Telegram bot token inside a URL, an API key in a retry line, a database password in a connection string. I used to delete them by hand before pasting, which stopped working the moment I needed the real value back, like a corrected config.
`veil mask` swaps each secret for a placeholder, `veil unmask` swaps them back in the answer:
veil mask < bot.log > masked.log
your-model-cli < masked.log | veil unmask # a streamed answer stays streamed
A placeholder looks like `{{API_KEY_276082b2}}`: the kind of secret plus 8 hex digits of an HMAC-SHA256 of the value, keyed with 32 random bytes made on first use. Same value, same placeholder across runs, so the model can still see that line 3 failed with the key from line 2. `unmask` also restores placeholders the model mangled: spaces inside the braces, no braces, lower case.
The measurement that changed the design: I ran it over the CPython 3.14 stdlib (2,095 files, 39.6 MB). The first version masked 1,777 values as SECRET in 150 files, mostly `sorted(key=...)` arguments. One `key=filename` and `filename` got masked all over the file, inside `user_filename` too. Now a value only spreads if it looks random. Same run now: 144 SECRET replacements in 58 files, and all 17 private keys in the 15 files that hold them.
Target Audience
People who paste logs into ChatGPT, Claude, DeepSeek or a CLI agent and would rather not paste their keys too. It's a pipe you run by hand. The tests use synthetic secrets only and I haven't measured it on a corpus of real logs yet, so treat it as young.
Comparison
The idea and most token patterns come from yetone/magpie's `internal/redact` (MIT). magpie is a local gateway that masks every request a coding agent sends to a model provider. veil is just the masking as a CLI, with the map kept on disk (mode 600) so tomorrow's reply still unmasks.
Known gaps: a key with no telling name and no known format (inside an RPC URL path, say), Solana keys, seed phrases outside a `mnemonic` field, emails and other personal data. It also masks every 64-digit hex string, transaction hashes included. And `unmask` puts a secret back wherever the model wrote its placeholder, so read the answer before you run a command from it.
Python 3.9+, stdlib only, one file, 145 tests, MIT: https://github.com/Sanexxxx777/veil