fix(auth): kid-aware JWKS refresh + cap force-refresh cadence + active checks on legacy fallback #405
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/mokosh-server!405
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/MAPPS-337-jwks-kid-aware-redirect-cap-active-checks"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
MAPPS-337: three related Bunyip-RS hardening items from the 2026-06-30 audit.
JWKS amplification defense:
verify_at_jwtpreviously force-refreshed JWKS on everyInvalidSignature, conflating "wrong key, signature does not match the cached JWK" with "unknown kid, the OP may have rotated". A junk-token spray (sign random tokens with the issuer's expected header but random data) would trigger one JWKS fetch per attempt because every signature would fail. Introduce a newVerifyError::UnknownKidvariant returned only whenheader.kidis absent from the cached set; the retry-on-refresh path triggers ONLY on that. GenuineInvalidSignaturefailures fall closed without any extra network IO.Force-refresh cooldown: even legitimate
UnknownKidpaths should not fan out to the OP at arbitrary rates. Add a per-process cooldown (FORCE_REFRESH_COOLDOWN_SECS = 60) tracked throughlast_force_refresh. Sequential UnknownKid attempts inside the window short-circuit toOk(())so the caller's nexttry_validatestill returnsUnknownKid(Unauthorized) without a network call. The existingrefresh_lockcoalesces concurrent calls; the new cooldown defends sequentially.JWKS HTTP client refuses redirects: the JWKS / discovery client built
reqwest::Clientwith the default redirect policy (up to 10 follows). A misconfigured DNS or a 302 from the issuer would chase the chain into attacker-chosen territory..redirect(reqwest::redirect::Policy::none())so the legitimate OP must serve the doc directly or the fetch fails.Legacy fallback active checks: the legacy HS256 cookie path authenticated a user as long as the JWT decoded and the row existed;
users.status != Activeand a suspended tenant were ignored, so revocations only took effect at token TTL. Add a publicAuthService::ensure_user_and_tenant_active(mirroring the checkslogin()runs) and call it from the middleware before populatingAuthState. Revocations take effect on the very next request.#MAPPS-337