Secrets broker for coding agents

Run the command.
Never read the secret.

Your vault can print a value. keyless cannot — so nothing in your transcript ever holds one.

the whole interface
$ keyless run -s DATABASE_URL -- psql
psql (16.2)  connected.
# from your vault. printed nowhere.
your store Proton Pass
Infisical
keyless resolves via the vault’s own run
the child process psql, with the value in its environment
nothing to the right of this line ever receives the value
stdoutnothing
scrollbacknothing
shell historynothing
the transcriptnothing
the child's environthe value

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.

why this exists

A vault, used
correctly.

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 vault did its job. The read verb undid it.

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.

your vault, read verb
$ 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.

stdoutthe value
scrollbackthe value
shell historythe value
the transcriptthe value
the child's environthe value
the same vault, via keyless
$ 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.

stdoutnothing
scrollbacknothing
shell historythe name
the transcriptthe name
the child's environthe value

Nobody typed a secret. The machine printed four.

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.

values on the command line
$ <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.

stdout4 values
scrollback4 values
shell historythe listing
the transcript4 values
any process table4 values
names on the command line
$ <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.

stdout4 names
scrollback4 names
shell historythe listing
the transcript4 names
any process table4 names

Every leak costs a rotation. Then it costs everyone else one.

Rotate, re-enter, re-wire, tell the team. The work is not the leak — the work is the treadmill the leak starts.

the treadmill
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.

values to rotate5
places to re-wire5
people to tellthe team
transcripts to purgenot possible
next weekagain
the treadmill stops
$ 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.

values to rotate0
places to re-wire0
people to tellnobody
transcripts to purgenone carry one
next weekthe same

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.

your vault is the foundation

Not another
vault.

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.

keyless reads from this

Proton Pass

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.

keyless reads from this

Infisical

Resolved through the Infisical CLI's own injection path, per project and per environment. Your access rules stay exactly where they are.

The verbs it will not call on your vault

Your vault ships a printing verb whatever keyless does. The guarantee is that keyless never builds a command containing one.

  • view · show · getProton Pass — printing an entry
  • export · read · revealInfisical — printing a value

a test asserts none of these words ever appears in the command keyless builds — for either store, in both directions

And the verbs it does not have itself

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

the lesser case

The plaintext file on disk

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.

the sharp edge

A green run
can be a lie.

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

keyless never refuses your command.

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

and one rule of syntax, because it catches everyone once
 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

what it does not catch

A filter,
not a control.

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.

Output masking, driven at the real binary

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.

  • the raw value, echoedmasked
  • contiguous hexmasked
  • one byte per write — fold -w1masked
  • gzipnot caught
  • xxd -p wrapped at 60 columnsnot caught
  • HTTP Basic — user and secret base64'd togetherlength-dependent
  • The child gets the real value. It can write it to a file, POST it anywhere, or print it. Masking filters the pipe; it does not constrain the process.
  • Nothing here survives sudo. If you are an admin on your own machine, this is a boundary against your sessions — not against you.
  • One part is a control, not a habit. The daemon's store is owned by a uid you are not, at mode 0600. The install asks for sudo exactly once, to create that user, and nothing afterwards does.
  • macOS only for the daemon. CI runs the Linux link and requires it to fail, on four named XNU symbols. Linux is unshipped because it is untested, and the build says so out loud.
4

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.

the number that is not here

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.

install

Dry-run
by default.

The installer prints every command it would run, in order, and changes nothing. --commit is the only thing that makes it act.

the client
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

and the uid boundary
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.

the seven verbs, and there is no eighth

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.

observed, not quoted
  • cargo test464 passed, 0 failed, 15 ignored
  • cargo fmt --checkexit 0 — zero bytes out
  • hooks/tests/run.py613 checks, all green
  • hooks/tests/mutate.py41 / 41 mutations caught
  • packages in the lock file24
  • values printed, ever0

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.