The agent uses secrets
without ever seeing them.
A security-native fork of the OMP terminal coding agent. The model holds only a handle — {{vault:bitwarden/9f3a…}} — while a broker executes with the real value in a heap the model cannot read. Containment by architecture, not by filter.
01 — the problem
Every credential your agent touches
lands in the transcript.
→ critical leak — the key is now in the model’s context, the session log, and the provider’s retention window.
→ secure execution — the model passes a handle; the sidecar resolves it off-context; output returns scrubbed.
02 — three seams
Redaction is a boundary,
not a search-and-replace.
Outbound obfuscate
Every value registered in secrets.yml or via addSecret() is replaced with a deterministic #ABCD# placeholder before a single token reaches the provider. The JSONL session log is sealed the same way, before it hits disk.
Inbound deobfuscate
Tool calls resolve placeholders back to real values inside the broker’s heap — never in the model’s. The agent requests a capability; the broker performs it; only the typed result crosses back.
Operator restore
You still see everything, tagged: hunter2 [redacted from LLM — source:bitwarden, visible-to:human, ttl:session]. Provenance is part of the value — who can see it, where it came from, when it dies.
03 — the broker
A capability surface,
not a credential store.
The agent never calls a secret-bearing CLI directly. It calls the broker — closed PATH allowlist, 20-variable env blacklist, Landlock fs-write sandbox, array-form argv, output scrubbing on the way back.
run_with_secretResolve a handle, inject the value into a hardened subprocess env, scrub the output.
run_with_chainMulti-step resolution — Bitwarden → SSH → Infisical — intermediate values never surface.
browser_with_secretThe broker types credentials into its own isolated page. The agent’s browser can’t read it back.
human_terminalAn operator-only PTY. Output renders to your screen and never enters the agent’s context.
/redactRegister a vault item for redaction in one step — resolve, then obfuscator-register by value.
/bw-unlockUnlock Bitwarden into the daemon’s vault via a masked TUI dialog. Paste works; the model sees nothing.
04 — the sidecar daemon
The vault lives in another heap.
Unlock once, everything shares it.
A detached daemon holds the credential vault in its own process memory — PR_SET_DUMPABLE=0, mlock-attempted, token-gated, speaking a capability-only protocol over a unix socket (and Tailscale TCP for your other machines).
the secret never crosses back into the agent’s context
05 — the chain
Bitwarden, then the server,
then every service on it.
The real flow: run_with_chain resolves the SSH credential from your laptop’s Bitwarden, SSHes into the VPS with a restricted agent holding only scoped keys — never your personal agent — then pulls each service’s secrets from the Infisical instance on that server. Three secrets cross; the agent sees none of them.
06 — the rotation loop
“Rotate all my ten-year-old passwords.”
That was the brief. It works.
Lease the old password
A TTL-scoped borrow held in the daemon’s vault. If the flow fails, the lease dies with the old credential still valid.
Log in via the broker’s page
The broker types the old password into its own isolated browser page. The agent orchestrates; it observes only URLs and selectors.
Generate the new one out-of-band
CSPRNG-generated in the broker’s heap. It never enters the model’s context, the chat, or the log.
Set it, then verify a fresh login
Change, then prove the new credential works on a clean page. On failure, the worker reverts to the old password automatically.
Write the vault back, retire the lease
bw encode → bw edit over stdin pipes — never argv. The old lease is revoked; both values join the scrub taint.
07 — providers as adapters
Two methods. Any vault.
resolve() and isAvailable() — that’s the whole interface. Fail-closed everywhere; a missing CLI is a clean no, never a crash.
08 — the guarantee, honestly stated