Files
hq/04-ISSUES/070-an-operator-cannot-deliver-a-pair-credential/00-report.md
T

55 lines
3.1 KiB
Markdown

---
status: resolved
opened: 2026-09-20
located-in: [mesh-controller internal/inventory (secret), mesh-controller cmd/mesh-controller (secret accept)]
fixed-by: ADR 0092; mesh-controller feat/multiple-fixes (secret accept --provider; origin on the pair; remake and rotate refused)
amended-design: 03-DESIGN/01-to-be/24-the-secrets-vault.md
---
# An operator cannot deliver a pair credential, so the vault's third species has no entry
## Symptom, as observed
[ADR 0085](../../02-DECISIONS/0085-a-secret-is-a-provision.md) names three species of secret and
gives the vault two: a module's own secret, which the mesh mints, and an **operator-delivered**
secret — a credential for something outside the mesh, which only a person can supply. The design
([24](../../03-DESIGN/01-to-be/24-the-secrets-vault.md)) says the vault *"takes custody of one an
operator delivered"*.
Under 0085 a secret from the vault is a pair credential between the consumer and the vault. The
controller has one command that takes a value from a person — `secret accept` — and it writes
**only a module's own secret**: one node, one module, one name, sealed to that node's key alone.
There is no command that accepts a value *into a pair*: sealed to the consumer's node **and** to
the vault's node, recorded as `accepted` so a later push does not replace it with a minted one.
The primitive exists. The sealing package's `Accept` takes a value and two keys, and the licence
adapters already use it to carry an operator's token to both ends of a licence. Nothing exposes it
for an ordinary provision.
So today an operator-delivered secret can be held in only one of two wrong ways: as an own secret
(un-audited, un-rotatable — the gap 0085 opened to close), or not at all.
## Why it matters beyond this instance
- **The vault is one third short of its decision** until this exists, and the third that is
missing is the one with a real leak history — external keys are the secrets that end up in
transcripts and env files.
- **The `made`/`accepted` distinction is only recorded for own secrets.** A pair credential has no
origin column, so even once a value can be accepted into a pair, `rotate` would mint over it —
generating "32 random bytes where a working credential was", the exact fault `secret accept`
was written to prevent for own secrets. Rotating an accepted pair credential must refuse, or
must ask for the replacement value, and neither is designed.
- **The consumer's login is derived, and an external service did not derive it.** An accepted
external key has no `as` the far side knows; a pair credential assumes both ends agree on one.
For the vault this is harmless (the vault creates nothing under that login), but the model
leaks through in what the consumer is told.
## Open questions
- Is this `secret accept <consumer-node> <module> secret --from …` growing a provider end, or a
new verb on the pair?
- Does an accepted pair credential refuse `rotate`, or does `rotate` become "accept a new value
and deliver it" for that pair?
- Should the origin (`made` / `accepted`) become a fact of every pair credential, so the vault's
ledger can show which of its secrets a person supplied?