docs: document the opt-in account lockout (NIST SP 800-63B §5.2.2) - #8
Merged
Merged
Conversation
This was referenced Aug 20, 2026
lukk 0.5.0 ships `features.lockout` and none of it was documented — the flag, `max_attempts`/`release_after`, the `lukk-lockout-migrations` group, `lukk:release`, the 423, `AccountLocked`/`AccountReleased`, or the `LockoutRepository` seam. The page leads with the distinction that actually matters, because the two are easy to conflate: the existing throttles bound a RATE and decay, the lockout bounds a RUN and persists. It also states the trade-off rather than burying it — a hard lockout is a denial-of-service primitive, which is why it is off by default — and recommends `release_after=3600`, which gives up the strict §5.2.2 reading but lands exactly on OWASP ASVS V2.2.1 and lifts itself. Two pre-existing gaps fixed along the way, since the new content reads as wrong next to them: - the `features` toggle list was missing `email_verification`, `password_reset` and `registration` - the login rate limit documented `ip_max_attempts` but never `account_max_attempts`, which is the cap the lockout is actually measured against (20/60s ≈ 1,200 failures an hour, indefinitely)
Follows lukk#23. Three things the lockout page now gets right: - `confirm` joins `login` and `two_factor` as a lockout purpose, with the reasoning for why step-up counts at all (it re-verifies the same password) and why `confirm-passkey` is throttled but never locked. - The `login` subject is identity, not the submitted string — and the `idn:` fallback is explained as load-bearing rather than a leftover, since an identifier naming no account must still count or 423-vs-422 becomes an existence oracle. - A successful login and a password reset both release a confirm lock. Also adds a Throttling section to the confirmation page, the `confirm` row to the rate-limits table, a security-checklist line, and the lockout + throttle identity entries the roadmap's Shipped list was missing.
Two accepted trade-offs now live where a user evaluating lukk will actually find them, rather than in a repo-internal register. **The grace window is a deviation from RFC 9700 §4.14.2 / OAuth 2.1**, and the rotation page now says so in those words. Both specs describe rotation as invalidate-and-detect — a replayed invalidated token "will revoke the active refresh token" — and neither provides for a tolerance window. Within `grace_seconds` lukk detects the replay and deliberately does not revoke. Previously this was written as a trade-off the spec accommodates. It isn't, and saying so is the honest version. What bounds it: strict invalidation logs out any client with two tabs open (a direct client can't be forced to single-flight); only the immediately-previous token is tolerated, since grace is measured from each token's own `rotated_at`; and Okta ships the same 30s default, with Auth0 and fosite doing the equivalent. **Look-alike identifiers share a rate-limit bucket** — the second limitation. Worth stating that it does NOT affect the account lockout, which keys on the resolved user id and so meets NIST SP 800-63B §5.2.2's "single account" scoping literally; the decaying throttle sits underneath that clause rather than implementing it. Also documents what 0.5.0 added and the site never described: the RefreshFamilyForked event (and why it's advisory — revoking on a fork means revoking on suspicion), RefreshTokenReused no longer firing for ordinary post-logout retries, per-guard refresh cookies and TTLs under cookie_mode, the 409 on re-enrolling confirmed two-factor, the reserve-before-verify lockout semantics, and the production guard against an array cache store.
stsepelin
force-pushed
the
docs/account-lockout
branch
from
August 21, 2026 12:40
539a54a to
cac68df
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the missing docs for
features.lockout, shipped in lukk #22. Nothing about it was documented — not the flag, the settings, the migration group,lukk:release, the423, the events, or the repository seam.New page —
/account-lockout(Features)Leads with the distinction that's easy to conflate, since the docs already used the word "lockout" loosely for the 429 throttle:
lukk_lockouts, persistent429423 LockedThen: setup + the publish group,
max_attempts/release_afterwith the trade-off stated rather than buried (a hard lockout is a DoS primitive — which is why it's off by default), thelogin/two_factorpurposes, the recovery-code exemption, all four release paths,lukk:release, pruning, both events, and theLockoutRepositoryswap seam with its atomicity warning.It recommends
release_after=3600: that gives up the strict §5.2.2 reading, but lands exactly on OWASP ASVS V2.2.1 — a bar the package doesn't otherwise clear — and the lock lifts itself.Cross-links
configuration (rate limits + feature toggles), authentication, two-factor, installation, events, security (standards mapping + checklist), customization, deployment.
Two pre-existing gaps fixed
The new content reads as wrong sitting next to these:
featurestoggle list was missingemail_verification,password_reset,registrationip_max_attemptsbut neveraccount_max_attempts— which is the cap the lockout is actually measured against (20/60s ≈ 1,200 failures an hour, indefinitely)Notes
pnpm docs:buildpasses withignoreDeadLinks: false, so every internal link and anchor resolves.main, so it will conflict with docs: document clientIpHeader + fix the throttle guidance it invalidates #7 inconfiguration.md(therate_limitscode block — docs: document clientIpHeader + fix the throttle guidance it invalidates #7 addsipv6_prefix, this addsaccount_max_attempts). Trivial to resolve; merge docs: document clientIpHeader + fix the throttle guidance it invalidates #7 first.Greptile Summary
This PR adds comprehensive documentation for the opt-in persistent account-lockout feature and connects it throughout the existing authentication, configuration, operations, events, customization, and security guidance.
Confidence Score: 4/5
The PR appears safe to merge, with two non-blocking documentation consistency issues in the configuration description and customization cross-reference.
The new feature documentation is internally coherent overall, but one sentence no longer matches the expanded login configuration shape and the advertised full contract list omits the new lockout repository seam.
Files Needing Attention: docs/configuration.md, docs/account-lockout.md, docs/customization.md
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Authentication attempt] --> B{Lockout enabled?} B -->|No| C[Apply rate limits] B -->|Yes| D{Existing lock held?} D -->|Yes| E[Return 423 Locked] D -->|No| C C --> F{Authentication succeeds?} F -->|Yes| G[Release counter] F -->|No| H[Record consecutive failure] H --> I{Failure cap reached?} I -->|No| J[Return authentication error] I -->|Yes| K[Persist lock and dispatch AccountLocked]Comments Outside Diff (1)
docs/configuration.md, line 62 (link)The introduction says login adds a third key, but the changed example now adds both
ip_max_attemptsandaccount_max_attempts, leaving the prose inconsistent with the canonical configuration shape.Prompt To Fix With AI
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "docs: document the opt-in account lockou..." | Re-trigger Greptile