Files
vh 0193b31aad fix(secrets-broker): bw is not concurrency-safe — serialise, and never return an empty secret with exit 0
Reported by svos-dev after parallelising four vault reads in SVOS's systemd
wrapper. Reproduced here and it is worse than reported: four concurrent secret get
calls for distinct items returned empty strings with exit code 0, zero of four
succeeding against their one of four. No error, no timeout, no diagnostic.

The shape is the problem, not the race. A caller treating an empty optional secret
as 'not configured' degrades silently and never learns otherwise - it cost SVOS the
ability to page the operator while the process logged a clean startup line.

Root cause is session establishment, not item reads. Every invocation runs bw
unlock, and concurrent unlocks against the shared appdata dir invalidate each
other. The damage then surfaces downstream as an empty listing or an empty item
body, which is why a per-call lock is useless: by the time the read runs the
session it holds is already dead. So the lock wraps the whole command instead.

Three changes. The command-level lock makes concurrent callers queue. cmd_get now
refuses an empty value rather than printing it, since a stored secret is never
legitimately zero-length. And find() no longer coerces empty stdout to '[]' - that
turned a broken read into a confident 'no such secret', the same silent-wrong-answer
shape one layer up.

Verified: four parallel reads of four real items now return all four correctly,
serialised at the honest ~17s each. A name that genuinely does not exist still
fails loudly, so the guard did not simply mute the negative case.

Also replaces the copy at ~/.local/bin/secret with a symlink to this file. It was a
plain copy in sync by luck, and every edit here silently left the live tool behind.
2026-09-15 08:49:11 -07:00
..

secrets-broker

Per-dev-box credential store + backup over the PFI Vaultwarden (vaultwarden.phasefinal.com). Lets the CC sessions on a dev box stash and look up secrets — API keys, tokens, env.sh / .env files, TLS keys — that shouldn't live in git and are otherwise single-copy on the box.

Not a fleet service. Each dev box runs its own copy of this stack against its own local secrets; items are hostname-namespaced (<hostname>/…) in the shared infra-ops org so they don't collide. No daemon — the secret CLI shells out to bw per call (~a few seconds; fine for occasional session use).

Files

File What
secret the CLI (put / get / list / backfill) — copy this to each box
secrets-broker.contract.md the spec
~/.config/secrets-broker/bootstrap.env per-box service-account creds, 0600, never committed

Set up on a new dev box

  1. Install bw (user-prefix, no sudo):
    npm install -g @bitwarden/cli --prefix "$HOME/.local"   # -> ~/.local/bin/bw
    
  2. Provision creds (operator): the service account already exists (infra-ops@phasefinal.com); drop its creds into the bootstrap file:
    mkdir -p ~/.config/secrets-broker && chmod 700 ~/.config/secrets-broker
    $EDITOR ~/.config/secrets-broker/bootstrap.env   # RBW_EMAIL, RBW_MASTER_PW, BW_CLIENTID, BW_CLIENTSECRET
    chmod 600 ~/.config/secrets-broker/bootstrap.env
    
    (RBW_* names are historical — bw reads them the same.)
  3. Copy the CLI and point at the vault:
    cp secret ~/.local/bin/secret          # or run in place
    bw config server https://vaultwarden.phasefinal.com
    
  4. Back up this box's secrets:
    secret backfill --dry-run   # review what it would store
    secret backfill             # write + round-trip-verify each
    

Usage

secret put myproj/.env --file ./env.sh --folder $(hostname)   # upsert a secret
secret put api/some-token --stdin                             # from stdin
secret get myproj/.env                                        # -> note body (stdout)
secret get certs/foo.pem --field content_b64 --file foo.pem   # binary -> 0600 file
secret list --prefix $(hostname)/                             # names + metadata only
secret backfill [--dry-run]                                   # this box's local secrets

Notes

  • bootstrap.env is the one secret that can't be vaulted (secrets-zero) — it's excluded from backfill. Keep it 0600; it's this box's crown jewel.
  • Values are only ever printed by an explicit get; list / --dry-run show names + metadata (sha256, synced_at, source_path) only.
  • Upsert is idempotent (keyed by name) — re-running backfill refreshes, never duplicates. Safe to re-run if a run is interrupted.
  • The Vaultwarden DB is in the pg_dump backup set; the store itself is durable.