Secrets broker for coding agents
keyless puts a secret into one child process's environment and nowhere else — not stdout,
not scrollback, not the transcript. There is deliberately no verb that prints a value, and there never
will be. That absence is the whole product.
Two states, no third — INJECTED or DEGRADED. A partial injection does not exist. keyless get is not a typo you can make; it does not exist. MIT licensed · Rust · five direct dependencies · 24 packages in the lock file.
A credential reaches a command in four shapes. This page will not tell you how often it happens on someone else's laptop — count it on yours.
Each one puts the value itself on the command line, where the shell, the history file and the transcript all record it.
https://user:VALUE@hostexport TOKEN=VALUE--token VALUEAuthorization: Bearer VALUEall four are one primitive wide — spawn a child with the secret in its environment
Standard tools, nothing installed. Each line prints a count and never prints a matching line — the same rule the tool follows.
# swap in ~/.bash_history, or any transcript directory H=~/.zsh_history grep -acE 'https?://[^ /]*:[^ /@]*@' "$H" # url grep -acE '[A-Z_]{4,}=[^ ]{12,}' "$H" # env grep -acE '\-\-(token|key|secret|password)[= ][^ -][^ ]*' "$H" # flag grep -acE '[Aa]uthorization: *(Bearer|Basic) [^ "]+' "$H" # header
four counts. that is your number, and it is the only one that should persuade you
The counts are lines, not secrets — a shape is checkable without anyone reading a value, which is why this page counts shapes and never values. · A correctly wrapped keyless run matches none of the four, so the recipe does not count the fix as the problem. · The CLI-flag shape is the documented reason keyless put has no --value flag.
The instrument is driven by the command, not by a secret. Nothing here asks you to paste a credential — asking is the offence, whatever the client-side code does.
# single-quote the body, and keep it inside sh -c keyless run -s TOKEN -- sh -c 'curl -H "$TOKEN" https://api.example.com'
Only the INNER shell expands, and it expands from the environment keyless just handed it. This is the one spelling where the value reaches the request.
The exit code is 0 in all four rows. keyless never refuses: an unresolvable name warns on stderr, runs the child with an untouched environment, and forwards its exit code. A missing credential is a 401 at exit 0 — read the DEGRADED banner, because the exit code cannot tell you.
Two of these four spellings inject correctly and still send nothing useful.
The calling shell expands "$TOKEN" before keyless is ever executed, and
keyless does not expand its own arguments. Neither fault is visible to the broker —
by the time it runs, the argument is already empty or already literal. Single-quote the body and
keep it inside sh -c, and the rule holds for every command you will ever wrap.
The masker generates twenty encodings of a secret, in a fixed order, and deduplicates. For a plain alphanumeric value, half of them are the same string.
Rendered in your browser, by the same twenty encoders in the same order. The repo's test table is an independent oracle — its literals are generated by Python's standard library, not by the code under test. · Split at every byte is still caught: the masker holds back bytes that could begin a needle and releases them the instant they cannot. Observed live against fold -w1. It is a prefix withhold, content-aware — not a suffix carry.
Three tokens defeat it: sh -c 'echo $TOKEN > /tmp/x'. By design. The
threat model is a capable agent taking a shortcut, not an adversary.
fold -w1maskedxxd -p wrapped at 60 columnsnot caughtuser:pass base64'd togetherlength-dependentThe username is folded into the same base64 block, so whether the secret survives as a recognisable needle depends on how the bytes align.
ab: — 3 bytescaughtabcde: — 6 bytescaughtapiuser: — 8 bytesprints in the clearone username length in three, proven — not estimated
keyless run never refuses.
An unresolvable name warns on stderr, runs the child with an untouched environment, and forwards the child's exit code. So a missing credential is a 401 at exit 0. Nothing in the exit status distinguishes a working secret from a missing one; only the DEGRADED banner does.
sudo. If you are an admin on your own machine, this is
a boundary against your sessions — not against you.secrets.json at mode 0600, user _keyless. The install asks
for sudo exactly once, to create that user, and nothing afterwards does. The audit log
is readable by you and not writable by you, and each row is hashed as
sha256(previous || row).#[ignore]d and never run in CI — the entire live Proton Pass
suite, which needs a real account. CI asserts ignored == 15 precisely so that stays
visible rather than quietly drifting.keyless cannot show you the leak that did not
happen, and a number nobody can audit is exactly the wrong thing to put on this page.Every verdict above was driven at the real binary, not read from a document. · Three of five masking limits carry a test. The other two do not, and this page will not claim otherwise.
A Python hook pack rides alongside the binary and guards the paths an agent actually takes. An attack corpus is driven at it, and the suite fails in both directions — if a row that is not on the published survivor list gets through, and if a row on it is now blocked. A stale limit is a lie in the other direction.
The pack also rewrites a credential-shaped literal into a ${NAME} reference before a file write reaches disk. Of four probe literals, three were rewritten — a GitHub token, an AWS key id, a Slack token — and an invented value matching no vendor pattern was left alone. Every literal in that probe is a decoy. The pack replaces what it recognises and names the shapes it matched. It does not guess.
The installer prints every command it would run, in order, and changes nothing.
--commit is the only thing that makes it act.
git clone https://github.com/nbstr/keyless cd keyless cargo install --path . keyless --version # proves it is on your PATH
requires rust 1.89 or later · cargo install writes to ~/.cargo/bin, which has to be on your PATH — the --version line is there so you find that out now rather than three commands later
cargo build --release # the installer copies from target/release ./install/install.sh # prints the plan, changes nothing sudo ./install/install.sh --commit
It needs sudo exactly once, to create a user. Nothing afterwards does.
uninstall.sh reverses all of it — the launchd job, the files, and the account —
and is dry-run too.
MIT licensed · github.com/nbstr/keyless
run · ls · items · fields · new · put · doctor
$ keyless get FOO error: unrecognized subcommand 'get' $ keyless put FOO --value hunter2 error: unexpected argument '--value' found
both observed live — the absence is the design
There is no telemetry, and a test scans the binary for endpoint strings to keep it that way.