Opaque secret container for credentials, keys, and other sensitive values.
Carry sensitive values through the system without leaking them via
logs, REPL output, exception traces, pretty-printers, or accidental
stdout. Unwrapping requires an explicit expose call — auditable,
greppable, intentional.
Secret is a deftype (not defrecord) — it is intentionally NOT a map.
Keyword lookup ((:value s)), get, and map destructuring all return
nil. The underlying field is reachable only via Java interop
((.value s)), which is greppable and easy to ban via lint.
expose — the ONE blessed unwrap. Every call site is a leak risk.(.value <Secret>) — interop escape hatch; ban in lint.Registered for print-method, print-dup, and clojure.pprint/simple-dispatch.
toString overrides Object — covers exception messages, log lines.
Hash is constant (0); equality is constant-time. Never serializable.
Opaque secret container for credentials, keys, and other sensitive values. ## Purpose Carry sensitive values through the system without leaking them via logs, REPL output, exception traces, pretty-printers, or accidental stdout. Unwrapping requires an explicit `expose` call — auditable, greppable, intentional. ## Type `Secret` is a deftype (not defrecord) — it is intentionally NOT a map. Keyword lookup (`(:value s)`), `get`, and map destructuring all return nil. The underlying field is reachable only via Java interop (`(.value s)`), which is greppable and easy to ban via lint. ## Audit surface - `expose` — the ONE blessed unwrap. Every call site is a leak risk. - `(.value <Secret>)` — interop escape hatch; ban in lint. ## Print safety Registered for `print-method`, `print-dup`, and `clojure.pprint/simple-dispatch`. `toString` overrides Object — covers exception messages, log lines. Hash is constant (0); equality is constant-time. Never serializable. ## Non-goals (v1) - Secure memory wiping. JVM strings cannot be reliably zeroed; this would be theater for SSH-pass / API-token use cases. Use char[] wrappers if you need defense against heap dumps. - Encryption at rest. This wraps live values in memory, not storage.
GNU pass (password-store) ISecretBackend implementation.
Resolves entries from the user's pass store into opaque Secret
values. Never logs the resolved content. Stderr is captured but
sanitized in error returns.
->PassBackend — defrecord constructor (preferred via DIP)pass-show — convenience: full output as one Secretpass-show-line — convenience: first non-empty line onlypass-available? — convenience: Result<bool> backend health:pass/not-found — entry does not exist:pass/empty — entry exists but resolved to blank:pass/exec-fail — pass binary failed (binary missing, gpg lock, etc.):pass/timeout — pass invocation exceeded deadlineErrors include the lookup :key (NOT the resolved value) for diagnostics.
GNU pass (password-store) ISecretBackend implementation. Resolves entries from the user's `pass` store into opaque Secret values. Never logs the resolved content. Stderr is captured but sanitized in error returns. ## API - `->PassBackend` — defrecord constructor (preferred via DIP) - `pass-show` — convenience: full output as one Secret - `pass-show-line` — convenience: first non-empty line only - `pass-available?` — convenience: Result<bool> backend health ## Backend errors - `:pass/not-found` — entry does not exist - `:pass/empty` — entry exists but resolved to blank - `:pass/exec-fail` — `pass` binary failed (binary missing, gpg lock, etc.) - `:pass/timeout` — pass invocation exceeded deadline Errors include the lookup `:key` (NOT the resolved value) for diagnostics.
Protocols for the secrets subsystem.
ISecretBackend is the DIP seam between consumers (probe, hive-mcp,
etc.) and concrete secret stores (GNU pass, Bitwarden CLI, 1Password,
Vault, age-encrypted files, etc.).
New backends implement this protocol; consumers depend only on the protocol. No call site needs to change to swap or add backends.
A backend MUST:
Result<Secret> from fetch — never raw values, never
plaintext bound in a map outside the Secret type.:line-only? and :timeout-ms opts (may ignore others).available? cheaply (no network round-trip if avoidable).backend-id keyword (e.g. :pass,
:bw, :op, :vault, :age).Protocols for the secrets subsystem. ## ISP boundary `ISecretBackend` is the DIP seam between consumers (probe, hive-mcp, etc.) and concrete secret stores (GNU pass, Bitwarden CLI, 1Password, Vault, age-encrypted files, etc.). New backends implement this protocol; consumers depend only on the protocol. No call site needs to change to swap or add backends. ## Backend contract A backend MUST: - Return `Result<Secret>` from `fetch` — never raw values, never plaintext bound in a map outside the Secret type. - Surface `:line-only?` and `:timeout-ms` opts (may ignore others). - Report `available?` cheaply (no network round-trip if avoidable). - Use a stable, namespaced `backend-id` keyword (e.g. `:pass`, `:bw`, `:op`, `:vault`, `:age`).
Backend registry + uniform resolution for ISecretBackend implementations.
Consumers depend on this ns + protocols.clj. They never need to know
which concrete backend (pass, bw, op, vault, age) is in use.
At process boot, register the backends you need: (registry/register! (pass/make-pass-backend)) (registry/register! (bw/make-bw-backend {:session ...}))
At call sites, resolve via a SecretRef map: (registry/fetch {:backend :pass :key "vps/r1/root"}) (registry/fetch {:backend :pass :key "vps/r1/ip" :opts {:line-only? true}})
Returns Result<Secret>.
{:backend <keyword> ;; backend-id (e.g. :pass, :bw) :key <string> ;; opaque key passed to the backend :opts <map?>} ;; optional per-fetch opts
validate-ref checks structure WITHOUT touching the backend — pure,
safe to run during config loading.
Backend registry + uniform resolution for ISecretBackend implementations.
Consumers depend on this ns + protocols.clj. They never need to know
which concrete backend (`pass`, `bw`, `op`, `vault`, `age`) is in use.
## Workflow
1. At process boot, register the backends you need:
(registry/register! (pass/make-pass-backend))
(registry/register! (bw/make-bw-backend {:session ...}))
2. At call sites, resolve via a SecretRef map:
(registry/fetch {:backend :pass :key "vps/r1/root"})
(registry/fetch {:backend :pass :key "vps/r1/ip" :opts {:line-only? true}})
Returns Result<Secret>.
## SecretRef shape
{:backend <keyword> ;; backend-id (e.g. :pass, :bw)
:key <string> ;; opaque key passed to the backend
:opts <map?>} ;; optional per-fetch opts
## Validation
`validate-ref` checks structure WITHOUT touching the backend — pure,
safe to run during config loading.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |