Secrets broker for coding agents
Your vault can print a value. keyless cannot — so nothing in your transcript ever holds one.
$ keyless run -s DATABASE_URL -- psql psql (16.2) connected. # from your vault. printed nowhere.
It reads from the vault you already run — Proton Pass or Infisical. · keyless get is not a typo you can make; it does not exist. · Two states, no third — INJECTED or DEGRADED. · MIT · Rust · macOS · five direct dependencies.
These pairs come from a real setup with both vaults already in place, where the secrets leaked anyway. Nothing about that setup was wrong. Each pair is the same moment, twice, and the vault is present in both halves.
The secret was encrypted at rest, in the right vault, with the right access. Then something needed the value — a person debugging, a script, an agent — and every vault has a verb for that. It writes to stdout, and stdout is the transcript.
$ pass-cli item view billing-db password postgres://demo:not-a-real-password@db.example.com/app $ psql "postgres://demo:not-a-real-password@…" psql (16.2) connected. # the vault was never breached. it was ASKED.
Correct credentials, correct permissions, correct vault. The value is in the transcript now — and in whatever that transcript syncs to. No amount of vault hygiene takes it back.
$ keyless run -s DATABASE_URL -- psql psql (16.2) connected. # resolved through the vault's own injection path. # nothing printed it, because nothing here can.
Same vault, same secret, same permissions. keyless has no verb that would print it — so no agent, and no habit, can reach one.
A credential on a command line is readable by anything that can list processes. One routine listing put four live tokens into a transcript — no paste, no mistake, no warning.
$ <a routine process listing> 4812 deploy --token not-a-real-token-7b3e91 4855 sync --key not-a-real-key-4f2a9c 4901 mailer --pass not-a-real-pass-d0177a 4933 worker --auth not-a-real-auth-9e42bc
Four live values, in one command's output, in the transcript. Nothing was typed wrong.
$ <the same routine listing> 4812 keyless run -s DEPLOY_TOKEN -- deploy 4855 keyless run -s SYNC_KEY -- sync 4901 keyless run -s MAIL_PASS -- mailer 4933 keyless run -s WORKER_AUTH -- worker
The same listing, the same four jobs. It reads out four names — which is exactly what a listing should be able to see.
Rotate, re-enter, re-wire, tell the team. The work is not the leak — the work is the treadmill the leak starts.
ROTATE DEPLOY_TOKEN value reached a transcript ROTATE SYNC_KEY value reached a transcript ROTATE MAIL_PASS value reached a transcript ROTATE WORKER_AUTH value reached a transcript ROTATE DATABASE_URL value reached a transcript …and each one is a re-wire, and a message to the team
A rotation is not the fix. It is the bill for a leak that already happened — and it arrives again next week.
$ keyless ls DEPLOY_TOKEN resolvable SYNC_KEY resolvable MAIL_PASS resolvable WORKER_AUTH resolvable DATABASE_URL resolvable # nothing to rotate. no value ever reached a transcript.
ls names what it can resolve. It prints no values, because there is no verb on this tool that prints one.
Every value on this page is invented and says so in its own text. Nothing here is a real credential, and a test scans this file on every commit to keep it that way.
keyless stores nothing. It resolves through the vault you already run, using that vault's own injection path — the safe one it already shipped. What it will not do is put that vault's read verb anywhere an agent can reach it.
Resolved through the Pass CLI's own injection path — the verb that hands a secret to a process without printing it. keyless keeps no copy of anything it reads.
Resolved through the Infisical CLI's own injection path, per project and per environment. Your access rules stay exactly where they are.
Your vault ships a printing verb whatever keyless does. The guarantee is that keyless never builds a command containing one.
a test asserts none of these words ever appears in the command keyless builds — for either store, in both directions
The other half of the same property. A narrower door onto your vault is worth nothing if the door has a window.
$ keyless get DATABASE_URL error: unrecognized subcommand 'get' $ keyless put DATABASE_URL --value … error: unexpected argument '--value' found
both observed live — the absence is the design
If you have no vault at all, this is where the secret is: plaintext, copied between machines, committed by accident. keyless helps here too. But it is not the interesting case, and it is not the one that brought this tool into existence — that one had both vaults already.
A value on a command line is recorded by the shell, the history file and the transcript alike — which is the documented reason keyless put has no --value flag. You are prompted, or you pipe.
This is the one thing that will bite you, so it gets a section rather than a footnote. Read the banner, not the exit code.
$ keyless run -s TYPOED_NAME -- deploy DEGRADED: TYPOED_NAME did not resolve. Running the command anyway, with an untouched environment. deploy: 401 Unauthorized $ echo $? 0 ← the exit code cannot tell you
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 separates a working secret from a missing one. Only the DEGRADED banner does — which is why it is a banner and not a debug line.
a trade, stated out loud: keyless will not be the reason your pipeline stops
✗ keyless run -s TOKEN -- curl -H "$TOKEN" … # your shell expands it FIRST → arrives empty ✗ keyless run -s TOKEN -- curl -H '$TOKEN' … # keyless expands nothing → arrives literal ✓ keyless run -s TOKEN -- sh -c 'curl -H "$TOKEN" …' # only the INNER shell expands
both wrong spellings inject correctly and still send nothing useful — neither fault is visible to the broker, because by the time it runs the argument is already empty or already literal
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.
keyless filters what comes back out through the pipe. Six shapes were driven at it, and these verdicts are what was observed rather than what was hoped.
fold -w1maskedxxd -p wrapped at 60 columnsnot caughtsudo. If you are an admin on your own machine,
this is a boundary against your sessions — not against you.0600. The install asks for sudo exactly once, to
create that user, and nothing afterwards does.attacks get past the hook pack that rides alongside — each one named in the repository, and pinned by a suite that fails in both directions.
There is no "leaks prevented" counter on this page, and there never will be. A security product's success is a non-event. keyless cannot show you the leak that did not happen, and a number nobody can audit is exactly the wrong thing to put on a page like this one.
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.
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
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.
run · ls · items · fields · new · put · doctor
Six of them name things. One of them runs a command with a value in its environment. None of them prints a value.
github.com/nbstr/keyless · MIT
The 15 ignored tests are the entire live Proton Pass suite, which needs a real account — CI asserts ignored == 15 precisely so that stays visible rather than quietly drifting. · There is no telemetry, and a test scans the binary for endpoint strings to keep it that way.