feat/credential-claim-code
at c52ac273. Where: the bbb build host — the throwaway edge, the
publisher and the cluster all on it, nothing on a laptop and nothing on any
live host. Why now: the founder’s rule for this feature — “we need to test
the 6 digits flow before” any subscriber is read a code.
Nothing real was touched. No account was minted on the channel registry; no
vaulted secret was decrypted; the age key pair was minted for this run and
shredded at the end; the ledger was a scratch checkout of the
vexa-stations shape, never the ledger; and the live edge is still not
deployed. The parked credential was a made-up rehearsal:… account. The two
codes appear in full below — they are spent, and they unlocked a password
this rehearsal invented and then destroyed.
The rig
Two deviations from the deployed shape, both stated because they change what
the run proves:
- No TLS and no Caddy. The edge was reached over plain HTTP on loopback
and on the cluster network, so
CLAIM_TRUST_FORWARDED_FORstayed off — correctly, since nothing trustworthy sets that header here. The consequence is that the “source” the limiter counts was the docker peer address, not a subscriber’s real one. Source separation is proven; source attribution behind a proxy is not. - The container ran as the host user, not the image’s
65532. The publisher and the edge shared one spool directory on one machine; in production the publisherscps into a spool the edge owns. Nothing in the state machine depends on the uid.
bbb could not pull the pinned base image
from Docker Hub — anonymous rate limit, 429. The image was built from the
identical digest fetched through the rig’s own pull-through cache, using
BuildKit’s named-context override; edge/claim/Dockerfile was not edited.
1 · Park — the publisher’s own add --park
Run as publisher/vexa_subscriber.py add rehearsal --park --station bbb-rehearsal --channel rehearsal --edge http://127.0.0.1:18089/claim …, with a stub
kubectl first on PATH that answers the three calls the mint makes with a
fixture and refuses every other call, so the real registry was never contacted.
The publisher’s own receipt, verbatim:
165 312 — six digits, grouped, for reading aloud. The park file
carried the ciphertext and a per-park code_salt; the ledger row carried the
salted digest, the expiry, the attempt cap and who parked it, and nothing
else.
2 · Claim — into the tenant namespace, value never on a screen
Opaque, keys username/password, labelled
app.kubernetes.io/name=vexa-station. Read back:
state=redeemed attempts=0/5 terminal_reason='claimed'.
3 · The three refusals
The wire is the point: every refusal is the same status and the same body,
and the eleventh is indistinguishable from the first. The distinction exists
only in
attempts.ndjson:
expired is covered by the branch’s tests only.
4 · The return leg
credential-events.yaml ends up holding two park rows and seven claim rows
— park id, salted digest, expiry, cap, source, outcome, time — and no value,
no code, no salt, no ciphertext.
The absence assertion
Eighteen files — every transcript this rehearsal captured, the edge container’s own stdout/stderr, the attempts log, both state files, the ledger working tree and the ledger’s fullgit log -p — were searched for both minted passwords,
verbatim and base64:
0600 file, used only as the needle for this search, and
shredded with the age key. The edge’s access log carries a source, a method and
a path and never a body.
Findings
1 ·kit/claim.sh is committed non-executable (100644). Every other kit
script is 100755. The documented ./kit/claim.sh --code … fails with
Permission denied out of a fresh clone — the first command a subscriber runs
on this path. Invoked through bash here. One chmod +x on the branch.
2 · A rate-limited attempt never reaches the station’s record. The
per-source limiter fires before the body is parsed — correct, and the reason
a refused request cannot cost a subscriber one of their five — but the station
is therefore unknown, the event is written with station -, and
record-credential drops it as unattributed. So the one event class that means
somebody hammered this station from one address is the one class the ledger
cannot show. The per-park limiter (park-rate-limited) is checked after the
parse and is attributed normally.
3 · Identical attempts inside the same second collapse to one row.
Nineteen attributable events reduced to seven rows: ten identical no-park
attempts at 19:52:18Z became one. Dedup-by-content is what makes re-ingest a
no-op, and the timestamps are second-resolution, so a burst is
indistinguishable from a single request in the ledger. Nothing is wrong in the
spool — attempts.ndjson has all twenty.
None of the three changes the verdict on the flow itself: the six-digit code
worked exactly as described, in the order described, and the credential reached
the cluster without appearing anywhere a person or a log could read it.