diff --git a/CHANGELOG.md b/CHANGELOG.md index 25643d32e..67d03a5f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ service can change a gate's answer, and a pull request number is only knowable f ## 2026-10-19 +- `authentication.md`: the transport-authentication rule now labels its requirements (authenticate and check each of WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs on its own, not through the HTML login; verify webhook signatures and reject replays) as this guide's, keeps the Stripe signature and timestamp citations, and states that the HTML-login boundary, GraphQL, inference and management API coverage, and replay rejection beyond those checks are not established by the cited sources; those parts stay open under TODO row 1.294 (#696) - Twenty more guides move to the two-section layout (maintainer request, 2026-10-18), with each guide's own tool named first in its header; the header now says "1 more recorded basis" rather than "bases", and the headers of guides already in the layout are regenerated where that changes them; no guide body or digest changes. Guides: `firebase-supabase.md`, `pocketbase.md`, `ray.md`, `gitops-controllers.md`, `nomad-consul.md`, `self-hosted-ci-runners.md`, `workflow-orchestrators.md`, `kubernetes.md`, `nginx.md`, `apache.md`, `caddy.md`, `traefik.md`, `haproxy.md`, `lighttpd.md`, `free-certificates.md`, `self-signed.md`, `headers.md`, `cors.md`, `docker.md`, `container-hardening.md` (#693) - `authentication.md`: the fail2ban rule now cites fail2ban 1.1.1's shipped `sshd`, `nginx-http-auth` and `webmin-auth` filters and jails, which are disabled by default; enabling the relevant jails with matching log paths is labelled as this guide's advice, and coverage of any particular login panel and how easy it is to set up are stated as not established; TODO row 1.293 moves to DONE (#694) - `authentication.md`: the Cloudflare Access rule now cites Cloudflare's One-time PIN, identity-provider and generic OIDC pages for the login options, and its existing self-hosted web application scope now reads "published through Access", stating that use with any application unchanged is not established; no command changes; TODO row 1.292 moves to DONE (#692) diff --git a/DONE.md b/DONE.md index a5a701c73..7190bb756 100644 --- a/DONE.md +++ b/DONE.md @@ -10,6 +10,7 @@ because an item that simply vanishes gets proposed again six months later. | ID | Item | Ended | | --- | --- | --- | +| 1.294 | `authentication.md`: transport-authentication: qualify the transport checks, HTML-login boundary and webhook replay requirement. (M, M) | Done, #696. Labelled the requirements and source limits, retained the Stripe signature and timestamp citations, and shortened the claim; evidence for the HTML-login boundary, GraphQL/inference/management coverage and comprehensive replay rejection remains open in TODO.md under 1.294. | | 1.293 | `authentication.md`: fail2ban: source or narrow the coverage and setup of login panels beyond the cited sshd jail and failure filter. (M, M) | Done, #694. Cited fail2ban 1.1.1 nginx HTTP-auth and Webmin jails and filter patterns, retained the SSH requirement, and labelled log-selection advice and the limits on panel coverage and setup ease. | | 1.292 | `authentication.md`: cloudflare-access: source or narrow the claim that Cloudflare Access can front any application unchanged, including the One-time PIN and SSO options still not established by the cited pages. (M, M) | Done, #692. Cited the pinned one-time PIN, identity-provider options and generic OIDC pages for the login methods, narrowed the fronting statement to a self-hosted web application published through Access, and labelled unchanged any-application use as not established and this guide's assessment. | | 1.291 | `pocketbase.md`: source-limits: trace the PocketBase CLI wrapper and Appwrite document read authorization and privileged/API-key requester classification; establish the `--dev` default. (M, M) | Done, #690. Pinned the PocketBase v0.40.4 wrapper wiring, the `--dev` default (disabled unless probably started with go run) and its dev-mode logging effects, and the Appwrite 2.2.0 keys/privileged requester classification, API-key and admin-mode authorization-status handling, route scope validation and document-read call sites; the utopia-php/database role-to-permission enforcement stays open in TODO.md under the same ID. | diff --git a/TODO.md b/TODO.md index f993b4736..0d99165af 100644 --- a/TODO.md +++ b/TODO.md @@ -60,7 +60,7 @@ A real surface the guide never covers. Correct as far as it goes, and not far en | 1.283 | `agent-builders.md`: debug-remedy: establish whether the debug_permission change is available in the console UI; API remedy is already cited and UI availability is unestablished. (M, M) | `[gap]` | | 1.290 | `firebase-supabase.md`: local-guards: establish the unrecorded bash and other tool/parser versions for the historical authoring guard and syntax runs; the ShellCheck v0.11.0 release is cited, but does not attest to the historical host binary. (M, M) | `[gap]` | | 1.291 | `pocketbase.md`: source-limits, remaining dependency gap: trace how utopia-php/database matches requester roles against stored collection/document permissions (including documentSecurity) for document reads and what the disabled default authorization status bypasses; the Appwrite-repository call sites, PocketBase CLI wrapper and `--dev` default are cited. (M, M) | `[gap]` | -| 1.294 | `authentication.md`: transport-authentication: source or narrow the HTML-login boundary for non-HTTP transports, GraphQL/inference/management APIs and webhook replay rejection. (M, M) | `[gap]` | +| 1.294 | `authentication.md`: transport-authentication, remaining source gaps: establish the HTML-login boundary for non-HTTP transports and authentication coverage for GraphQL, inference and management APIs; establish comprehensive webhook replay rejection beyond signature and timestamp-tolerance checks, including repeats within the accepted window. These remain guide requirements, not established deployment behaviour. (M, M) | `[gap]` | | 1.295 | `authentication.md`: verify-secret-scan: source or narrow staged, all-ref and merge-commit coverage beyond the cited Gitleaks and `git log -p` material. (M, M) | `[gap]` | | 1.296 | `docker.md`: secrets: source runtime secret-delivery methods and `.env` exclusion from Git beyond the cited Docker build/history behaviour. (M, M) | `[gap]` | | 1.297 | `elasticsearch.md`: os-gcs-azure: establish deployed GCS/Azure client-side encryption applicability and minimum versions beyond the cited OpenSearch plugin tree. (M, M) | `[gap]` | diff --git a/VERSION b/VERSION index f8e358f8d..4413b49bd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.694 +1.0.696 diff --git a/authentication.md b/authentication.md index 1841065dd..63bf0735a 100644 --- a/authentication.md +++ b/authentication.md @@ -3,7 +3,7 @@ version_basis: { "schema": 1, "checked": "2026-10-19", "documentation_checked": "2026-09", - "body_sha256": "0ddaa867f7996dc266b18e3f31505a227196859f5e1986e4fc5cda8cfb7aff5b", + "body_sha256": "9b15280089a2f45c5b00ef282bc2a354357ed9560cd1ebbaa84a330d1eede9ce", "components": { "owasp": { "name": "OWASP authentication and password storage", @@ -303,7 +303,7 @@ version_basis: { "federated-allowlist": {"text": "After federated login, check the identity against an application allowlist of tenants, hosted domains, organization or group membership, or explicit users (this guide's recommendation); a Google sign-in alone does not make someone staff. For Google Workspace or Cloud organizations, check the hd claim in the validated ID token; an email domain or request hd parameter is insufficient. Microsoft common serves organizational and personal accounts; validate the token issuer, signing-key issuer and tid, then apply the application tenant allowlist. Vendor sources support the Google hd check and Microsoft token validation; the full allowlist and staff determination are this guide's recommendations.", "components": ["oidc", "google-oidc", "entra-multitenant"], "sources": ["oidc:scfd2790a544c", "google-oidc:s4f815acc2976", "entra-multitenant:s5e0242f87cb7", "entra-multitenant:s95719206c226"], "status": "REASONED"}, "cloudflare-access": {"text": "Cloudflare Access can front a self-hosted web application published through Access with one-time PIN or SSO login; create the Access application before the tunnel route, or it is available to anyone on the Internet. Validate the Access JWT at the origin, verify Tunnel origin TLS, and use Full (strict) for a directly proxied HTTPS origin where applicable; unchanged any-application use is not established.", "components": ["cloudflare-access"], "sources": ["cloudflare-access:sfddb92e438ca", "cloudflare-access:s7cafe1b17afd", "cloudflare-access:s81e5dd28d3e5", "cloudflare-access:s65c224a2e655", "cloudflare-access:sa012410f91eb", "cloudflare-access:sbf4ecbd5ffc1", "cloudflare-access:scb7d48f64a99", "cloudflare-access:sd606ae12de33", "cloudflare-access:s9f8bead847d2"], "status": "REASONED"}, "browser-session": {"text": "Prefer a server-side session in an HttpOnly cookie to tokens in browser storage.", "components": ["owasp", "owasp-docs"], "sources": ["owasp:sc5f1b11d42f4", "owasp-docs:s4f0aa80b1ef6"], "status": "REASONED"}, - "transport-authentication": {"text": "Authenticate WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; each needs its own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). GraphQL, inference and management APIs have no cited source. Verify webhook sender signatures and reject replays (guide requirement); Stripe documents signature and timestamp-tolerance checks, not comprehensive replay rejection. The fetched files document protocol behaviour, not a deployed application.", "components": ["owasp", "websocket-rfc", "sse-spec", "grpc-status", "stripe-webhooks"], "sources": ["owasp:sc5f1b11d42f4", "websocket-rfc:s07f03be17365", "websocket-rfc:s559dc201b043", "sse-spec:sf47a21cc6a1c", "grpc-status:s595c3c297157", "stripe-webhooks:s42eea6a7e527", "stripe-webhooks:s90f21ad845ca"], "status": "REASONED"}, + "transport-authentication": {"text": "Guide requirements: authenticate and check WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; do not rely on HTML login; verify webhook signatures and reject replays. HTML-login and GraphQL/inference/management coverage, comprehensive replay rejection beyond Stripe's signature/timestamp checks, and deployed protection remain unestablished (guide assessment).", "components": ["owasp", "websocket-rfc", "sse-spec", "grpc-status", "stripe-webhooks"], "sources": ["owasp:sc5f1b11d42f4", "websocket-rfc:s07f03be17365", "websocket-rfc:s559dc201b043", "sse-spec:sf47a21cc6a1c", "grpc-status:s595c3c297157", "stripe-webhooks:s42eea6a7e527", "stripe-webhooks:s90f21ad845ca"], "status": "REASONED"}, "offboarding": {"text": "When a person leaves, promptly revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Entra documents blocking new sign-ins and revoking refresh tokens, while application-issued sessions require revocation by the application; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but these passages do not establish immediate per-user revocation for either proxy. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). In a deployment test, first confirm that a matched request with each provider, proxy and application session and personal token succeeds against a protected resource. After revocation, replay each old credential at recorded intervals until it is refused and returns no protected data, and the elapsed time for every layer must not exceed its configured maximum time-to-revoke; these are test expectations, not observed results.", "components": ["owasp", "oauth", "owasp-docs", "entra-docs", "cloudflare-access", "oauth2-proxy", "authelia", "oauth-revocation"], "sources": ["owasp:sc5f1b11d42f4", "oauth:s4e6d674bf887", "owasp-docs:seb866a1f94e9", "entra-docs:s983f45259faa", "entra-docs:scb38e683ae3f", "entra-docs:s9ff4d6ed4cfe", "entra-docs:sca6feea4b601", "cloudflare-access:sf2dbab0fc574", "cloudflare-access:sa0e3eb928f4a", "oauth2-proxy:s32cedbc14f60", "authelia:sc844fe82794f", "oauth-revocation:s3383c9f41cc2"], "status": "REASONED"}, "verify-transports": {"text": "Use real protocol clients against every non-public path and transport: expect HTTP 401, 403 or a login redirect; gRPC UNAUTHENTICATED for missing or invalid credentials (PERMISSION_DENIED for an identified caller lacking permission); SSE refusal; and WebSocket upgrade rejection or protected-operation rejection, no protected data disclosure, and closure on first-message authentication failure. A bare curl GET does not test non-HTTP transports. These are test expectations, not observed responses.", "components": ["owasp", "grpc-status", "websocket-rfc", "sse-spec"], "sources": ["owasp:sc5f1b11d42f4", "grpc-status:s595c3c297157", "websocket-rfc:s07f03be17365", "websocket-rfc:s559dc201b043", "sse-spec:sf47a21cc6a1c"], "status": "REASONED"}, "verify-secret-scan": {"text": "A secret scanner must find no leaks in the working tree, staged changes and full history. Gitleaks documents directory, Git-history and pre-commit use, but this README does not establish a distinct staged-scan mode; verify staged, all-ref and merge-commit coverage with the chosen scanner and configuration. Git log patch grep is supplementary and misses uncommitted files, some refs, secrets without the search words, or changes that appear only in merge commits. The historical fixture result is not a scan of this repository.", "components": ["owasp", "gitleaks-source", "gitleaks-p3", "git-log"], "sources": ["owasp:sc5f1b11d42f4", "gitleaks-source:s44a3edaa543d", "gitleaks-p3:se5afdb7c63d2", "gitleaks-p3:s685ae6ce9b3d", "git-log:s1cb7f677894d", "git-log:sede4bb3fcdf3"], "status": "REASONED"}, @@ -399,7 +399,7 @@ Claims (full source mapping in the front matter): - federated-allowlist (REASONED; oidc, google-oidc, entra-multitenant): After federated login, check the identity against an application allowlist of tenants, hosted domains, organization or group membership, or explicit users (this guide's recommendation); a Google sign-in alone does not make someone staff. For Google Workspace or Cloud organizations, check the hd claim in the validated ID token; an email domain or request hd parameter is insufficient. Microsoft common serves organizational and personal accounts; validate the token issuer, signing-key issuer and tid, then apply the application tenant allowlist. Vendor sources support the Google hd check and Microsoft token validation; the full allowlist and staff determination are this guide's recommendations. - cloudflare-access (REASONED; cloudflare-access): Cloudflare Access can front a self-hosted web application published through Access with one-time PIN or SSO login; create the Access application before the tunnel route, or it is available to anyone on the Internet. Validate the Access JWT at the origin, verify Tunnel origin TLS, and use Full (strict) for a directly proxied HTTPS origin where applicable; unchanged any-application use is not established. - browser-session (REASONED; owasp, owasp-docs): Prefer a server-side session in an HttpOnly cookie to tokens in browser storage. -- transport-authentication (REASONED; owasp, websocket-rfc, sse-spec, grpc-status, stripe-webhooks): Authenticate WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; each needs its own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). GraphQL, inference and management APIs have no cited source. Verify webhook sender signatures and reject replays (guide requirement); Stripe documents signature and timestamp-tolerance checks, not comprehensive replay rejection. The fetched files document protocol behaviour, not a deployed application. +- transport-authentication (REASONED; owasp, websocket-rfc, sse-spec, grpc-status, stripe-webhooks): Guide requirements: authenticate and check WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; do not rely on HTML login; verify webhook signatures and reject replays. HTML-login and GraphQL/inference/management coverage, comprehensive replay rejection beyond Stripe's signature/timestamp checks, and deployed protection remain unestablished (guide assessment). - offboarding (REASONED; owasp, oauth, owasp-docs, entra-docs, cloudflare-access, oauth2-proxy, authelia, oauth-revocation): When a person leaves, promptly revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Entra documents blocking new sign-ins and revoking refresh tokens, while application-issued sessions require revocation by the application; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but these passages do not establish immediate per-user revocation for either proxy. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). In a deployment test, first confirm that a matched request with each provider, proxy and application session and personal token succeeds against a protected resource. After revocation, replay each old credential at recorded intervals until it is refused and returns no protected data, and the elapsed time for every layer must not exceed its configured maximum time-to-revoke; these are test expectations, not observed results. - verify-transports (REASONED; owasp, grpc-status, websocket-rfc, sse-spec): Use real protocol clients against every non-public path and transport: expect HTTP 401, 403 or a login redirect; gRPC UNAUTHENTICATED for missing or invalid credentials (PERMISSION_DENIED for an identified caller lacking permission); SSE refusal; and WebSocket upgrade rejection or protected-operation rejection, no protected data disclosure, and closure on first-message authentication failure. A bare curl GET does not test non-HTTP transports. These are test expectations, not observed responses. - verify-secret-scan (REASONED; owasp, gitleaks-source, gitleaks-p3, git-log): A secret scanner must find no leaks in the working tree, staged changes and full history. Gitleaks documents directory, Git-history and pre-commit use, but this README does not establish a distinct staged-scan mode; verify staged, all-ref and merge-commit coverage with the chosen scanner and configuration. Git log patch grep is supplementary and misses uncommitted files, some refs, secrets without the search words, or changes that appear only in merge commits. The historical fixture result is not a scan of this repository. @@ -435,7 +435,7 @@ TLS without authentication leaves a service open to the whole internet over an e 12. **OIDC and OAuth hygiene.** Authorization code flow with PKCE (`S256`, never `plain`); exact-match redirect URIs; fresh per-transaction `state` and `nonce`, verified and bound to the initiating browser session; ID tokens validated for signature (keys from the provider's JWKS, algorithm pinned, never `none`), issuer, audience, and expiry; short-lived access tokens with refresh-token rotation; tokens never in URLs. Prefer a server-side session in an `HttpOnly` cookie to tokens in browser storage. Link accounts by issuer plus subject, never by email alone. 13. **Enforce MFA where access is granted, not only where it is enrolled.** A user who enrolled a second factor but can still act with a password-only session is not protected. Require the factor in provider policy or in the app, and test it. 14. **Protect the control plane.** MFA on the Git host, the cloud account, the DNS registrar, the deployment platform, the secret manager, and the identity provider's administrator account. A takeover there bypasses every control inside the app. -15. **Authenticate every transport.** WebSockets, server-sent events, GraphQL, gRPC, webhooks (verify the sender's signature and reject replays), inference endpoints, and management APIs each need their own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). Stripe's webhook helper checks the signature and a timestamp tolerance; comprehensive replay rejection is an application requirement. Machine credentials follow [machine-auth.md](machine-auth.md). +15. **Authenticate every transport.** WebSockets, server-sent events, GraphQL, gRPC, webhooks (verify the sender's signature and reject replays), inference endpoints, and management APIs each need their own authentication check (this guide's requirement). Do not rely on the HTML login to protect them (this guide's requirement); the cited protocol sources do not establish how an HTML login protects these endpoints (this guide's assessment). Stripe's webhook helper checks the [signature](https://github.com/stripe/stripe-node/blob/v18.5.0/src/Webhooks.ts#L182-L216) and a [timestamp tolerance](https://github.com/stripe/stripe-node/blob/v18.5.0/src/Webhooks.ts#L357-L410); comprehensive replay rejection remains this guide's application requirement, not a guarantee from those checks (this guide's assessment). The cited protocol and Stripe sources do not establish GraphQL, inference or management API authentication boundaries or a deployed application's protection (this guide's assessment); those APIs still require their own authentication checks. Machine credentials follow [machine-auth.md](machine-auth.md). 16. **Offboard promptly.** When a person leaves, revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Microsoft Entra documents blocking new sign-ins and revoking refresh tokens, but says the application must revoke its own session; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but the cited passages do not establish immediate per-user revocation for those proxies. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). [deployment-lifecycle.md](deployment-lifecycle.md) has the lifecycle checks; [mfa.md](mfa.md) covers revoking the second factor along with the account. 17. **Set a password policy and reject breached passwords.** Require a minimum length of at least 8 characters when a second factor is enforced, otherwise at least 15 (NIST SP 800-63B-4); allow long passphrases and every character, and drop periodic-rotation and composition rules. Screen new and changed passwords against a known-breach corpus (for example the Pwned Passwords range API), because a correct breached password defeats both hashing and per-IP rate limits. 18. **Secure account recovery.** Password and MFA reset is a sensitive authentication surface: issue cryptographically random, single-use, short-lived, account-bound reset tokens; deliver them only to a pre-verified channel; keep the reset response non-enumerating; reauthenticate for a recovery-address or MFA-factor change; and on completion invalidate outstanding sessions and unused reset tokens. Recovery must never silently drop the MFA requirement. diff --git a/plugin/plugin.json b/plugin/plugin.json index 41158b038..492744bde 100644 --- a/plugin/plugin.json +++ b/plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "secureconfig", - "version": "1.0.694", + "version": "1.0.696", "description": "Deployment-exposure guides for services AI-assisted projects commonly run: TLS, authentication, MFA, secrets, and network exposure, each line traced to a vendor page.", "author": { "name": "Jeff Posluns", "url": "https://secureconfig.ai" }, "homepage": "https://secureconfig.ai", diff --git a/plugin/skills/secure-deployment-config/references/MANIFEST.sha256 b/plugin/skills/secure-deployment-config/references/MANIFEST.sha256 index adc90b199..00f945353 100644 --- a/plugin/skills/secure-deployment-config/references/MANIFEST.sha256 +++ b/plugin/skills/secure-deployment-config/references/MANIFEST.sha256 @@ -5,7 +5,7 @@ 86eca64127b4dca3510677732af318246214e54c8bcfd7662cf8d532520564ab cloudflare.md c63b3c46f99d8d1d5c60ef527994618c97a0300629692719ddba0e3eba4403d7 tailscale.md 57dc02171106069655b1ecf67ce77cd86ba505cd306469f48f544ed83482e845 tunnels.md -bd1d324fd0e232d952fa2a3ff46c4cd60cf11b9f1f811fb7d783258a6053cd58 authentication.md +5540325c9d47ef3110273bed4807893df9359b32dfe49091f403eda60576a755 authentication.md a4bd1d507a1f15812717e44ed1426892aa789a7b3a955128945bd1930618c453 mfa.md 82f9de47492cc8831a043625ec2553fd53033f56c048c2e04b3eb218981ee888 secrets.md 67ead6128e8115fe289b11db4010b4fc046b44fbf797bb7d3ac6c52f4e4d4959 vault.md diff --git a/plugin/skills/secure-deployment-config/references/authentication.md b/plugin/skills/secure-deployment-config/references/authentication.md index 1841065dd..63bf0735a 100644 --- a/plugin/skills/secure-deployment-config/references/authentication.md +++ b/plugin/skills/secure-deployment-config/references/authentication.md @@ -3,7 +3,7 @@ version_basis: { "schema": 1, "checked": "2026-10-19", "documentation_checked": "2026-09", - "body_sha256": "0ddaa867f7996dc266b18e3f31505a227196859f5e1986e4fc5cda8cfb7aff5b", + "body_sha256": "9b15280089a2f45c5b00ef282bc2a354357ed9560cd1ebbaa84a330d1eede9ce", "components": { "owasp": { "name": "OWASP authentication and password storage", @@ -303,7 +303,7 @@ version_basis: { "federated-allowlist": {"text": "After federated login, check the identity against an application allowlist of tenants, hosted domains, organization or group membership, or explicit users (this guide's recommendation); a Google sign-in alone does not make someone staff. For Google Workspace or Cloud organizations, check the hd claim in the validated ID token; an email domain or request hd parameter is insufficient. Microsoft common serves organizational and personal accounts; validate the token issuer, signing-key issuer and tid, then apply the application tenant allowlist. Vendor sources support the Google hd check and Microsoft token validation; the full allowlist and staff determination are this guide's recommendations.", "components": ["oidc", "google-oidc", "entra-multitenant"], "sources": ["oidc:scfd2790a544c", "google-oidc:s4f815acc2976", "entra-multitenant:s5e0242f87cb7", "entra-multitenant:s95719206c226"], "status": "REASONED"}, "cloudflare-access": {"text": "Cloudflare Access can front a self-hosted web application published through Access with one-time PIN or SSO login; create the Access application before the tunnel route, or it is available to anyone on the Internet. Validate the Access JWT at the origin, verify Tunnel origin TLS, and use Full (strict) for a directly proxied HTTPS origin where applicable; unchanged any-application use is not established.", "components": ["cloudflare-access"], "sources": ["cloudflare-access:sfddb92e438ca", "cloudflare-access:s7cafe1b17afd", "cloudflare-access:s81e5dd28d3e5", "cloudflare-access:s65c224a2e655", "cloudflare-access:sa012410f91eb", "cloudflare-access:sbf4ecbd5ffc1", "cloudflare-access:scb7d48f64a99", "cloudflare-access:sd606ae12de33", "cloudflare-access:s9f8bead847d2"], "status": "REASONED"}, "browser-session": {"text": "Prefer a server-side session in an HttpOnly cookie to tokens in browser storage.", "components": ["owasp", "owasp-docs"], "sources": ["owasp:sc5f1b11d42f4", "owasp-docs:s4f0aa80b1ef6"], "status": "REASONED"}, - "transport-authentication": {"text": "Authenticate WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; each needs its own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). GraphQL, inference and management APIs have no cited source. Verify webhook sender signatures and reject replays (guide requirement); Stripe documents signature and timestamp-tolerance checks, not comprehensive replay rejection. The fetched files document protocol behaviour, not a deployed application.", "components": ["owasp", "websocket-rfc", "sse-spec", "grpc-status", "stripe-webhooks"], "sources": ["owasp:sc5f1b11d42f4", "websocket-rfc:s07f03be17365", "websocket-rfc:s559dc201b043", "sse-spec:sf47a21cc6a1c", "grpc-status:s595c3c297157", "stripe-webhooks:s42eea6a7e527", "stripe-webhooks:s90f21ad845ca"], "status": "REASONED"}, + "transport-authentication": {"text": "Guide requirements: authenticate and check WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; do not rely on HTML login; verify webhook signatures and reject replays. HTML-login and GraphQL/inference/management coverage, comprehensive replay rejection beyond Stripe's signature/timestamp checks, and deployed protection remain unestablished (guide assessment).", "components": ["owasp", "websocket-rfc", "sse-spec", "grpc-status", "stripe-webhooks"], "sources": ["owasp:sc5f1b11d42f4", "websocket-rfc:s07f03be17365", "websocket-rfc:s559dc201b043", "sse-spec:sf47a21cc6a1c", "grpc-status:s595c3c297157", "stripe-webhooks:s42eea6a7e527", "stripe-webhooks:s90f21ad845ca"], "status": "REASONED"}, "offboarding": {"text": "When a person leaves, promptly revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Entra documents blocking new sign-ins and revoking refresh tokens, while application-issued sessions require revocation by the application; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but these passages do not establish immediate per-user revocation for either proxy. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). In a deployment test, first confirm that a matched request with each provider, proxy and application session and personal token succeeds against a protected resource. After revocation, replay each old credential at recorded intervals until it is refused and returns no protected data, and the elapsed time for every layer must not exceed its configured maximum time-to-revoke; these are test expectations, not observed results.", "components": ["owasp", "oauth", "owasp-docs", "entra-docs", "cloudflare-access", "oauth2-proxy", "authelia", "oauth-revocation"], "sources": ["owasp:sc5f1b11d42f4", "oauth:s4e6d674bf887", "owasp-docs:seb866a1f94e9", "entra-docs:s983f45259faa", "entra-docs:scb38e683ae3f", "entra-docs:s9ff4d6ed4cfe", "entra-docs:sca6feea4b601", "cloudflare-access:sf2dbab0fc574", "cloudflare-access:sa0e3eb928f4a", "oauth2-proxy:s32cedbc14f60", "authelia:sc844fe82794f", "oauth-revocation:s3383c9f41cc2"], "status": "REASONED"}, "verify-transports": {"text": "Use real protocol clients against every non-public path and transport: expect HTTP 401, 403 or a login redirect; gRPC UNAUTHENTICATED for missing or invalid credentials (PERMISSION_DENIED for an identified caller lacking permission); SSE refusal; and WebSocket upgrade rejection or protected-operation rejection, no protected data disclosure, and closure on first-message authentication failure. A bare curl GET does not test non-HTTP transports. These are test expectations, not observed responses.", "components": ["owasp", "grpc-status", "websocket-rfc", "sse-spec"], "sources": ["owasp:sc5f1b11d42f4", "grpc-status:s595c3c297157", "websocket-rfc:s07f03be17365", "websocket-rfc:s559dc201b043", "sse-spec:sf47a21cc6a1c"], "status": "REASONED"}, "verify-secret-scan": {"text": "A secret scanner must find no leaks in the working tree, staged changes and full history. Gitleaks documents directory, Git-history and pre-commit use, but this README does not establish a distinct staged-scan mode; verify staged, all-ref and merge-commit coverage with the chosen scanner and configuration. Git log patch grep is supplementary and misses uncommitted files, some refs, secrets without the search words, or changes that appear only in merge commits. The historical fixture result is not a scan of this repository.", "components": ["owasp", "gitleaks-source", "gitleaks-p3", "git-log"], "sources": ["owasp:sc5f1b11d42f4", "gitleaks-source:s44a3edaa543d", "gitleaks-p3:se5afdb7c63d2", "gitleaks-p3:s685ae6ce9b3d", "git-log:s1cb7f677894d", "git-log:sede4bb3fcdf3"], "status": "REASONED"}, @@ -399,7 +399,7 @@ Claims (full source mapping in the front matter): - federated-allowlist (REASONED; oidc, google-oidc, entra-multitenant): After federated login, check the identity against an application allowlist of tenants, hosted domains, organization or group membership, or explicit users (this guide's recommendation); a Google sign-in alone does not make someone staff. For Google Workspace or Cloud organizations, check the hd claim in the validated ID token; an email domain or request hd parameter is insufficient. Microsoft common serves organizational and personal accounts; validate the token issuer, signing-key issuer and tid, then apply the application tenant allowlist. Vendor sources support the Google hd check and Microsoft token validation; the full allowlist and staff determination are this guide's recommendations. - cloudflare-access (REASONED; cloudflare-access): Cloudflare Access can front a self-hosted web application published through Access with one-time PIN or SSO login; create the Access application before the tunnel route, or it is available to anyone on the Internet. Validate the Access JWT at the origin, verify Tunnel origin TLS, and use Full (strict) for a directly proxied HTTPS origin where applicable; unchanged any-application use is not established. - browser-session (REASONED; owasp, owasp-docs): Prefer a server-side session in an HttpOnly cookie to tokens in browser storage. -- transport-authentication (REASONED; owasp, websocket-rfc, sse-spec, grpc-status, stripe-webhooks): Authenticate WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; each needs its own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). GraphQL, inference and management APIs have no cited source. Verify webhook sender signatures and reject replays (guide requirement); Stripe documents signature and timestamp-tolerance checks, not comprehensive replay rejection. The fetched files document protocol behaviour, not a deployed application. +- transport-authentication (REASONED; owasp, websocket-rfc, sse-spec, grpc-status, stripe-webhooks): Guide requirements: authenticate and check WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; do not rely on HTML login; verify webhook signatures and reject replays. HTML-login and GraphQL/inference/management coverage, comprehensive replay rejection beyond Stripe's signature/timestamp checks, and deployed protection remain unestablished (guide assessment). - offboarding (REASONED; owasp, oauth, owasp-docs, entra-docs, cloudflare-access, oauth2-proxy, authelia, oauth-revocation): When a person leaves, promptly revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Entra documents blocking new sign-ins and revoking refresh tokens, while application-issued sessions require revocation by the application; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but these passages do not establish immediate per-user revocation for either proxy. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). In a deployment test, first confirm that a matched request with each provider, proxy and application session and personal token succeeds against a protected resource. After revocation, replay each old credential at recorded intervals until it is refused and returns no protected data, and the elapsed time for every layer must not exceed its configured maximum time-to-revoke; these are test expectations, not observed results. - verify-transports (REASONED; owasp, grpc-status, websocket-rfc, sse-spec): Use real protocol clients against every non-public path and transport: expect HTTP 401, 403 or a login redirect; gRPC UNAUTHENTICATED for missing or invalid credentials (PERMISSION_DENIED for an identified caller lacking permission); SSE refusal; and WebSocket upgrade rejection or protected-operation rejection, no protected data disclosure, and closure on first-message authentication failure. A bare curl GET does not test non-HTTP transports. These are test expectations, not observed responses. - verify-secret-scan (REASONED; owasp, gitleaks-source, gitleaks-p3, git-log): A secret scanner must find no leaks in the working tree, staged changes and full history. Gitleaks documents directory, Git-history and pre-commit use, but this README does not establish a distinct staged-scan mode; verify staged, all-ref and merge-commit coverage with the chosen scanner and configuration. Git log patch grep is supplementary and misses uncommitted files, some refs, secrets without the search words, or changes that appear only in merge commits. The historical fixture result is not a scan of this repository. @@ -435,7 +435,7 @@ TLS without authentication leaves a service open to the whole internet over an e 12. **OIDC and OAuth hygiene.** Authorization code flow with PKCE (`S256`, never `plain`); exact-match redirect URIs; fresh per-transaction `state` and `nonce`, verified and bound to the initiating browser session; ID tokens validated for signature (keys from the provider's JWKS, algorithm pinned, never `none`), issuer, audience, and expiry; short-lived access tokens with refresh-token rotation; tokens never in URLs. Prefer a server-side session in an `HttpOnly` cookie to tokens in browser storage. Link accounts by issuer plus subject, never by email alone. 13. **Enforce MFA where access is granted, not only where it is enrolled.** A user who enrolled a second factor but can still act with a password-only session is not protected. Require the factor in provider policy or in the app, and test it. 14. **Protect the control plane.** MFA on the Git host, the cloud account, the DNS registrar, the deployment platform, the secret manager, and the identity provider's administrator account. A takeover there bypasses every control inside the app. -15. **Authenticate every transport.** WebSockets, server-sent events, GraphQL, gRPC, webhooks (verify the sender's signature and reject replays), inference endpoints, and management APIs each need their own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). Stripe's webhook helper checks the signature and a timestamp tolerance; comprehensive replay rejection is an application requirement. Machine credentials follow [machine-auth.md](machine-auth.md). +15. **Authenticate every transport.** WebSockets, server-sent events, GraphQL, gRPC, webhooks (verify the sender's signature and reject replays), inference endpoints, and management APIs each need their own authentication check (this guide's requirement). Do not rely on the HTML login to protect them (this guide's requirement); the cited protocol sources do not establish how an HTML login protects these endpoints (this guide's assessment). Stripe's webhook helper checks the [signature](https://github.com/stripe/stripe-node/blob/v18.5.0/src/Webhooks.ts#L182-L216) and a [timestamp tolerance](https://github.com/stripe/stripe-node/blob/v18.5.0/src/Webhooks.ts#L357-L410); comprehensive replay rejection remains this guide's application requirement, not a guarantee from those checks (this guide's assessment). The cited protocol and Stripe sources do not establish GraphQL, inference or management API authentication boundaries or a deployed application's protection (this guide's assessment); those APIs still require their own authentication checks. Machine credentials follow [machine-auth.md](machine-auth.md). 16. **Offboard promptly.** When a person leaves, revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Microsoft Entra documents blocking new sign-ins and revoking refresh tokens, but says the application must revoke its own session; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but the cited passages do not establish immediate per-user revocation for those proxies. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). [deployment-lifecycle.md](deployment-lifecycle.md) has the lifecycle checks; [mfa.md](mfa.md) covers revoking the second factor along with the account. 17. **Set a password policy and reject breached passwords.** Require a minimum length of at least 8 characters when a second factor is enforced, otherwise at least 15 (NIST SP 800-63B-4); allow long passphrases and every character, and drop periodic-rotation and composition rules. Screen new and changed passwords against a known-breach corpus (for example the Pwned Passwords range API), because a correct breached password defeats both hashing and per-IP rate limits. 18. **Secure account recovery.** Password and MFA reset is a sensitive authentication surface: issue cryptographically random, single-use, short-lived, account-bound reset tokens; deliver them only to a pre-verified channel; keep the reset response non-enumerating; reauthenticate for a recovery-address or MFA-factor change; and on completion invalidate outstanding sessions and unused reset tokens. Recovery must never silently drop the MFA requirement. diff --git a/site/llms-full.txt b/site/llms-full.txt index 454fbfc7c..849f94e44 100644 --- a/site/llms-full.txt +++ b/site/llms-full.txt @@ -1942,7 +1942,7 @@ Claims (full source mapping in the front matter): - federated-allowlist (REASONED; oidc, google-oidc, entra-multitenant): After federated login, check the identity against an application allowlist of tenants, hosted domains, organization or group membership, or explicit users (this guide's recommendation); a Google sign-in alone does not make someone staff. For Google Workspace or Cloud organizations, check the hd claim in the validated ID token; an email domain or request hd parameter is insufficient. Microsoft common serves organizational and personal accounts; validate the token issuer, signing-key issuer and tid, then apply the application tenant allowlist. Vendor sources support the Google hd check and Microsoft token validation; the full allowlist and staff determination are this guide's recommendations. - cloudflare-access (REASONED; cloudflare-access): Cloudflare Access can front a self-hosted web application published through Access with one-time PIN or SSO login; create the Access application before the tunnel route, or it is available to anyone on the Internet. Validate the Access JWT at the origin, verify Tunnel origin TLS, and use Full (strict) for a directly proxied HTTPS origin where applicable; unchanged any-application use is not established. - browser-session (REASONED; owasp, owasp-docs): Prefer a server-side session in an HttpOnly cookie to tokens in browser storage. -- transport-authentication (REASONED; owasp, websocket-rfc, sse-spec, grpc-status, stripe-webhooks): Authenticate WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; each needs its own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). GraphQL, inference and management APIs have no cited source. Verify webhook sender signatures and reject replays (guide requirement); Stripe documents signature and timestamp-tolerance checks, not comprehensive replay rejection. The fetched files document protocol behaviour, not a deployed application. +- transport-authentication (REASONED; owasp, websocket-rfc, sse-spec, grpc-status, stripe-webhooks): Guide requirements: authenticate and check WebSockets, SSE, GraphQL, gRPC, webhooks, inference and management APIs separately; do not rely on HTML login; verify webhook signatures and reject replays. HTML-login and GraphQL/inference/management coverage, comprehensive replay rejection beyond Stripe's signature/timestamp checks, and deployed protection remain unestablished (guide assessment). - offboarding (REASONED; owasp, oauth, owasp-docs, entra-docs, cloudflare-access, oauth2-proxy, authelia, oauth-revocation): When a person leaves, promptly revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Entra documents blocking new sign-ins and revoking refresh tokens, while application-issued sessions require revocation by the application; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but these passages do not establish immediate per-user revocation for either proxy. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). In a deployment test, first confirm that a matched request with each provider, proxy and application session and personal token succeeds against a protected resource. After revocation, replay each old credential at recorded intervals until it is refused and returns no protected data, and the elapsed time for every layer must not exceed its configured maximum time-to-revoke; these are test expectations, not observed results. - verify-transports (REASONED; owasp, grpc-status, websocket-rfc, sse-spec): Use real protocol clients against every non-public path and transport: expect HTTP 401, 403 or a login redirect; gRPC UNAUTHENTICATED for missing or invalid credentials (PERMISSION_DENIED for an identified caller lacking permission); SSE refusal; and WebSocket upgrade rejection or protected-operation rejection, no protected data disclosure, and closure on first-message authentication failure. A bare curl GET does not test non-HTTP transports. These are test expectations, not observed responses. - verify-secret-scan (REASONED; owasp, gitleaks-source, gitleaks-p3, git-log): A secret scanner must find no leaks in the working tree, staged changes and full history. Gitleaks documents directory, Git-history and pre-commit use, but this README does not establish a distinct staged-scan mode; verify staged, all-ref and merge-commit coverage with the chosen scanner and configuration. Git log patch grep is supplementary and misses uncommitted files, some refs, secrets without the search words, or changes that appear only in merge commits. The historical fixture result is not a scan of this repository. @@ -1978,7 +1978,7 @@ TLS without authentication leaves a service open to the whole internet over an e 12. **OIDC and OAuth hygiene.** Authorization code flow with PKCE (`S256`, never `plain`); exact-match redirect URIs; fresh per-transaction `state` and `nonce`, verified and bound to the initiating browser session; ID tokens validated for signature (keys from the provider's JWKS, algorithm pinned, never `none`), issuer, audience, and expiry; short-lived access tokens with refresh-token rotation; tokens never in URLs. Prefer a server-side session in an `HttpOnly` cookie to tokens in browser storage. Link accounts by issuer plus subject, never by email alone. 13. **Enforce MFA where access is granted, not only where it is enrolled.** A user who enrolled a second factor but can still act with a password-only session is not protected. Require the factor in provider policy or in the app, and test it. 14. **Protect the control plane.** MFA on the Git host, the cloud account, the DNS registrar, the deployment platform, the secret manager, and the identity provider's administrator account. A takeover there bypasses every control inside the app. -15. **Authenticate every transport.** WebSockets, server-sent events, GraphQL, gRPC, webhooks (verify the sender's signature and reject replays), inference endpoints, and management APIs each need their own check. Do not rely on the HTML login to protect them (this guide's requirement; the cited sources do not establish it). Stripe's webhook helper checks the signature and a timestamp tolerance; comprehensive replay rejection is an application requirement. Machine credentials follow [machine-auth.md](machine-auth.md). +15. **Authenticate every transport.** WebSockets, server-sent events, GraphQL, gRPC, webhooks (verify the sender's signature and reject replays), inference endpoints, and management APIs each need their own authentication check (this guide's requirement). Do not rely on the HTML login to protect them (this guide's requirement); the cited protocol sources do not establish how an HTML login protects these endpoints (this guide's assessment). Stripe's webhook helper checks the [signature](https://github.com/stripe/stripe-node/blob/v18.5.0/src/Webhooks.ts#L182-L216) and a [timestamp tolerance](https://github.com/stripe/stripe-node/blob/v18.5.0/src/Webhooks.ts#L357-L410); comprehensive replay rejection remains this guide's application requirement, not a guarantee from those checks (this guide's assessment). The cited protocol and Stripe sources do not establish GraphQL, inference or management API authentication boundaries or a deployed application's protection (this guide's assessment); those APIs still require their own authentication checks. Machine credentials follow [machine-auth.md](machine-auth.md). 16. **Offboard promptly.** When a person leaves, revoke their provider membership, proxy sessions, application sessions, and personal tokens (this guide's recommendation). Microsoft Entra documents blocking new sign-ins and revoking refresh tokens, but says the application must revoke its own session; its access-token lifetime varies, and CAE critical-event enforcement can take up to 15 minutes for supported services. Cloudflare Access documents per-user session revocation, but an active IdP profile can start a new session; after user logout, previously issued tokens stop being accepted in 20-30 seconds. OAuth2 Proxy and Authelia document session-cookie expiry, but the cited passages do not establish immediate per-user revocation for those proxies. RFC 7009 specifies immediate token invalidation with possible propagation delay and conditional access-token revocation support. The fetched sources do not establish a provider-membership removal mechanism or this deployment's measured maximum time-to-revoke. Measure each system's maximum time-to-revoke where immediate revocation is unavailable, and work to shrink it (this guide's recommendation; no delay was measured here). [deployment-lifecycle.md](deployment-lifecycle.md) has the lifecycle checks; [mfa.md](mfa.md) covers revoking the second factor along with the account. 17. **Set a password policy and reject breached passwords.** Require a minimum length of at least 8 characters when a second factor is enforced, otherwise at least 15 (NIST SP 800-63B-4); allow long passphrases and every character, and drop periodic-rotation and composition rules. Screen new and changed passwords against a known-breach corpus (for example the Pwned Passwords range API), because a correct breached password defeats both hashing and per-IP rate limits. 18. **Secure account recovery.** Password and MFA reset is a sensitive authentication surface: issue cryptographically random, single-use, short-lived, account-bound reset tokens; deliver them only to a pre-verified channel; keep the reset response non-enumerating; reauthenticate for a recovery-address or MFA-factor change; and on completion invalidate outstanding sessions and unused reset tokens. Recovery must never silently drop the MFA requirement.