Skip to content

docs(security): the Local pathway after Amendment A: enrol before rotating, TOTP first, the generated-credential lock exception (BACKLOG #1133) - #1820

Merged
wshallwshall merged 3 commits into
mainfrom
b121-e15-post-1770-pathway-docs
Sep 29, 2026
Merged

wshallwshall merged 3 commits into
mainfrom
b121-e15-post-1770-pathway-docs

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

BACKLOG #1133 (ASVS 6.1.3). Docs, comments, guard tests, and one console notice text fix.

The ninth vault re-read of 6.1.3 (vault PR 2091) held the cell on 8 sentences that engine PR 1770 (ADR 0197 Amendment A wave 1) made false. Under the shipped require_mfa, a covered local account with no TOTP must enrol TOTP before it may change its password, and its first factor must be TOTP. A generated credential arms no sign-in lock, and a combined sign-in owes nothing more.

Fixed

  • The 8 counted items:
    • Table A must-change row: names _ENROL_FIRST_ROUTES and the order prove, enrol, rotate.
    • Table A factor-binding row.
    • The pending-session sentence.
    • The passkey-only reset account, with where such an account can still exist.
    • CONFIGURATION.md's require_mfa row.
    • DELETE /me/mfa versus passkey removal, and the MFA section.
    • The lockout rule's generated-credential exception, in four places.
    • "every local sign-in" is now "every password-only local sign-in".
  • Whole-document search of SECURITY.md, CONFIGURATION.md and CONNECTIONS.md on the first-sign-in order, first factor, factor removal, must-change confinement and lockout. Eight more SECURITY.md sentences were fixed, including PR 1770's own paragraph, the require() ladder, the /me/password row, the /ui exceptions, the WebAuthn section and the recovery paragraph ("at least three" routes reach set_password). About 25 more were checked and found true.
  • Extra corrections:
    • The Kerberos audit claim: the route's own 400s write no audit row.
    • "env only" AD bind and OIDC secrets: a file value is accepted with a WARNING. This is docs only, by Manager decision; no settings behaviour changed.
    • The WebAuthn advice.
    • Stale docstrings and comments in auth/service.py, api/auth_routes.py and the web console.
  • Console: /ui/account?m=enroll_first no longer offers a passkey where the code refuses one. It shows exactly when must_enrol_before_rotating is true. The test is red on main and green here. A line was added under Unreleased in the console package CHANGELOG.

Guard tests (tests/test_docs_security_pathways.py)

  • Code probes: the exact route sets, the three lockout_arms arms, the _login_local combined assignment, and on a real store that change_password and passkey registration refuse a covered no-factor account while disable_mfa refuses to remove covered TOTP.
  • Doc assertions, one or more per item. main's docs go red. Reverting any single item makes its own assertion go red.

Follow-ups for the ledger (code, not in this PR)

  • POST /ui/mfa (routes/core.py ui_mfa_submit) should redirect a must-change enrol-first session instead of charging a ceremony attempt.
  • routes/account.py _factor_first: for a covered, passkey-only pending session, the console answers "enrol first" (303, unaudited) where the JSON plane answers X-MFA-Required (403, audited). A reorder would make them agree. The docs now state the difference.

QA line: two xhigh review rounds.

  • Round 1: 11 findings; 9 applied, 1 partly applied, 1 declined. The declined one was the settings.py "env only" policy comments.
  • Round 2: 4 findings, all applied as doc corrections. The _factor_first code reorder was declined as out of scope and is listed above.
  • The round-2 fix commit is small and follows the reviewer's own replacement text. Please give it a read.

Local:

  • ruff and mypy (engine, tests, console) are clean at 8fe350a.
  • 3310+ passed over the 58 doc-reading and console files, with 6 load-only failures under -n 8, all in untouched files and all passing alone.
  • After round 2: 430 passed on the 8 targeted files.
  • merge-tree against main exits 0; a built conflicting control exits 1.

wshallwshall added 3 commits September 29, 2026 17:34
…ating, TOTP first, the generated-credential lock exception

BACKLOG #1133 (ASVS 6.1.3, ninth re-read). The number is here and not in
the subject because the claim gate refused the commit: #1133 is unclaimed
and an isolated Builder cannot take a claim.

The ninth 6.1.3 re-read held the cell at partial on eight sentences that
engine PR 1770 left stale. Under the shipped require_mfa a covered local
account with no TOTP enrols TOTP before it may rotate, a passkey cannot be
its first factor, TOTP cannot be removed, and a generated credential arms
no sign-in lock. SECURITY.md and CONFIGURATION.md now say so, with the
Kerberos audit sentence, the file-secret warning and stale comments fixed.

The console's enrol-first notice now offers TOTP alone to a covered
account, matching the service's refusal of a first passkey.

Guards: tests/test_docs_security_pathways.py, ninth-sweep pair.
BACKLOG #1133 (ASVS 6.1.3). Number kept out of the subject for the same
claim-gate reason as the previous commit.

The console password page redirects to enrolment rather than showing the
refusal detail; the generated credential comes from an administrator's
account creation and both resets, not the first-Administrator command; the
factor reset is a third route to set_password under a lock; the TOTP-first
rule is scoped to accounts the requirement covers; /ui/mfa's handling of an
enrol-first session is disclosed. The guards gain the missing lockout arm,
the uncovered disable_mfa branch and a sturdier item 1 needle.
BACKLOG #1133 (ASVS 6.1.3). Number kept out of the subject for the claim-gate
reason given in the first commit of this branch.

/ui/mfa's POST refuses any code for an account with no TOTP rather than
checking one; a pending covered passkey-only session is sent to enrolment by
the console password page, unaudited, while POST /me/password answers
X-MFA-Required; the factor reset writes no password on a directory account;
both administrator resets refuse a self-reset.
@wshallwshall wshallwshall added the qa Builder QA record posted; not a merge gate label Sep 29, 2026
@wshallwshall

Copy link
Copy Markdown
Collaborator Author

QA line: code-review xhigh, two rounds. Round 1: 11 findings, 9 applied, 1 partly, 1 declined (settings.py env-only policy comments). Round 2: 4 findings (1 wrong /ui/mfa sentence, 1 narrow console-vs-JSON contradiction, 2 nits), all applied as doc corrections; the _factor_first code reorder declined as out of scope and listed as a follow-up. The round-2 fix commit 43849ea is unreviewed and small. Guard asserts red on main and per-item red on single reverts. Local: ruff and mypy clean; 430 passed on the 8 targeted files after round 2.

@wshallwshall
wshallwshall added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 0f5ae18 Sep 29, 2026
42 of 44 checks passed
@wshallwshall
wshallwshall deleted the b121-e15-post-1770-pathway-docs branch September 29, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

qa Builder QA record posted; not a merge gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant