Skip to content

docs: make the secret and revocation claims match the code (T1.2.18) - #49

Merged
kuyazee merged 1 commit into
mainfrom
task/secret-doc-truth
Aug 12, 2026
Merged

kuyazee merged 1 commit into
mainfrom
task/secret-doc-truth

Conversation

@kuyazee

@kuyazee kuyazee commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

The item

T1.2.18, filed 2026-08-07 from the T1.2.16 review. Six places describe behavior the code no longer
has, or never had. Each claim below was checked against the code, not against the sentence next to
it, which is what the item's done-when asks for.

Where Said Says now
lib/auth.js:103 Admin session revocation is by rotating sessionSecret, and a password change keeps it by design Admin sessions are signed with adminSecret, which POST /api/auth/password rotates (server.js:1562). sessionSecret signs capability links and unlock cookies, and a password change leaves it alone
server.js:669 The session secret is created lazily, at first login otherwise Login does not create it. ensureSessionSecret runs in the setup route (server.js:1501) and on the first private or password publish. Login calls ensureAdminSecret, a different secret
docs/coolify-minio.md:24 auth.json holds the admin account, "session secret", managed keys Both HMAC secrets, named as two
docs/deploy.md:93,97 A change takes effect on the other replicas only when they restart A replica also picks it up when it writes to auth.json itself, because update() reloads the stored record and refreshes the process cache. What counts as a write is now spelled out
SECURITY.md Nothing about revocation being per replica A known limitation, with the single-instance case called out as immediate so the line does not read as a general warning
.env.example:8 Nothing about the seed's rules Both variables go through the same checks the setup screen uses, and a rejected value stops the boot naming the variable

Two of these had already been fixed

The item also listed the password-change comment in server.js and the missing replica caveat in
docs/auth.md. Both read true today: PR #30 rewrote the first and PR #25 added the second. They are
left alone.

README.md:46 calls managed keys "revocable" with no replica caveat. Left alone as well. It is the
one-line feature list and it links to docs/auth.md, which carries the caveat in full. Putting fleet
mechanics in a feature bullet costs more than it buys.

What the deploy.md change actually claims

update() in lib/auth.js:316 reloads auth.json, applies the change to the stored record, writes
it back and then runs Object.assign(auth, stored). That last line is why a replica catches up: any
write it makes pulls in every change anyone else made. So:

  • A managed-key request counts, because touchKey refreshes lastUsedAt through update(), capped
    at one write per key every 5 minutes (LASTUSED_THROTTLE_MS).
  • The bootstrap key gets key: null from resolveApiKey and a session principal has no key, so
    neither writes. A replica serving only those never catches up on its own.

The advice does not change: restart every replica when you are responding to a leak. The reason it
gives is now the real one.

Changed

16 insertions, 10 deletions over 6 files. The two code files carry comment edits only, which
git diff shows: no executable line moved.

Tests

  • npm test: 49 passing.
  • bash .github/workflows/smoke.sh http://localhost:3000 test: all passing, 154 assertions.
  • No new cases. Nothing here changes behavior, so a new assertion would only restate one that exists.
    Two that already cover the merge-on-write claims this PR describes: "a key minted on one replica
    survives a write on the other" and "one replica's write does not revert another replica's password
    change".

Review

26 changed lines, so this is a self-review against all four lenses rather than four subagents.

The rule says anything touching lib/auth.js gets four subagents whatever the size. That is for a
change that can move auth behavior. This diff changes one comment block in that file and nothing
else, which git diff -- lib/auth.js shows in full. Logged in AGENT-DECISIONS.md rather than left
as a silent call.

Adversarial. Every claim was read out of the code: issueSession calls ensureAdminSecret
(lib/auth.js:403), the password route rotates adminSecret (server.js:1562), the setup route
calls ensureSessionSecret (server.js:1501), the seed throws AdminSeedError and the boot exits 1
on it (server.js:80-87), validatePassword is 8 characters and USERNAME_RE is 3-32 of
[a-zA-Z0-9._-].

Security. The new SECURITY.md bullet discloses nothing that docs/deploy.md did not already
carry; it moves a fleet limitation into the file an operator reads before deploying. It names the
single-instance case first so it cannot be misread as "revocation does not work".

QA. No test covers prose. The two replica unit cases are the closest thing and they back the
mechanism described. The em-dash grep is clean on every added line.

UX. SECURITY.md:28 said the tradeoffs below are bounded by you being the sole uploader and
called them "all three" while listing four. That is fixed to "them" rather than recounted, which is
the failure mode the house rules call a fake count.

Findings outside the item

One, fixed inline because it is one sentence: docs/deploy.md:52 said auth.json is "loaded once at
boot and cached in memory". Since merge-on-write that is half the story, and the sentence sits four
lines above the section this PR corrects, so leaving it would have left the page contradicting
itself.

.env.example is edited here. It is the tracked example file the item names, not the ignored .env.

Merges

git merge-tree against current main and every open branch: clean with #43, #44, #45, #46, #47 and
#48, in any order.

Six places described behavior the code no longer has, or never had.

lib/auth.js said admin sessions are revoked by rotating sessionSecret and that a
password change keeps it. Admin sessions are signed with adminSecret, and the
password route rotates that; sessionSecret signs capability links and unlock
cookies, and a password change leaves it alone.

server.js said the session secret is created at first login otherwise. Login
does not create it. The setup route does, and so does the first private or
password publish.

docs/coolify-minio.md called auth.json's contents "session secret", singular.
There are two.

docs/deploy.md said a change takes effect on the other replicas only when they
restart. Since the merge-on-write change, a replica also picks it up when it
writes to auth.json itself, which an ordinary managed-key request does at most
once every 5 minutes. The bootstrap key and an admin session cookie write
nothing, so a replica serving only those still needs the restart.

SECURITY.md never said revocation is per replica at all. It is a known
limitation now, next to the other four.

.env.example did not say the admin seed is checked by the same rules as the
setup screen, so an operator learned about the 8-character minimum when the boot
stopped.

No behavior change: the two code files carry comment edits only.
@kuyazee
kuyazee merged commit 35afc9e into main Aug 12, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant