docs backend evidence community safety SEC07B_JWKS_EXPIRY - AlexandreZanata/Abastevo GitHub Wiki
SEC07-B — Per-issuer JWKS expiry and bounded refresh
Date: 2026-10-11. Status: LOCAL_DONE. Scope: JWKS cache/refresh/
redirect policy only (no session/Android change). Head: maintained
dev + this task.
Plan: remediation plan
(SEC07, AUD-09). Contract: frozen 1h TTL kept; expiry independent per
issuer; unknown kids trigger exactly one TTL-bypassing refetch
(rotation verifies, removal still refuses); outage fails closed as
unavailable; JWKS redirects never followed (stable endpoints).
Constraint: no external network in tests (local httptest TLS only);
plaintext keys never logged.
Validation: PASS
Changes
account/adapters/oidc/jwks.go:CachedKeys.atis now per issuer (map[string]time.Time);Fetchstamps only the fetched issuer; failed refetch keeps the old stamp and fails closed (retries next call, never serves expired keys). NewRefreshingKeySourceCachedKeys.Refresh(one unconditional fetch, restamps only that issuer).HTTPKeys.Fetchpins a copied client with a refusingCheckRedirect(caller's client never mutated, default-client redirect-following closed); https-only + 64KiB cap kept.
account/adapters/oidc/oidc.go:VerifyretriesselectKeyonce throughRefreshingKeySourceon unknown kid (refresh failure → unavailable; still unknown → unknown-issuer). Non-refreshing sources keep the old immediate refusal.- Tests (
sec07b_jwks_test.go, 4 new): alternating providers past TTL (apple fetch at +50m does not prolong google; google refetches at +70m while apple stays cached — 3 source calls); rotation via one refresh then verification; removal refusing after refresh past TTL; outage failing closed as unavailable; malicious local redirect refused with zero attacker requests.
Validation (actual)
go test ./internal/modules/account/...green (incl. 4 new + pre-existing TTL/outage/rotation/tamper suites).go build ./...,go vet,gofmt -l(clean),git diff --checkclean; secret scan passed (prior turn, no secret-adjacent change).- RED pre-GREEN: shared-
atcode served google stale at +70m (2 fetches, want 3 — one provider prolonged the other); unknown kids never retried (rotation needed a TTL wait); default client followed the 302 (attacker saw the request).
Limits / owed
- Removal propagates at most after the issuer TTL (cache stays authoritative while fresh — recorded, not silent).
- Stale-on-outage is not served (fail-closed by policy).
- Production JWKS URLs unchanged. No session/Android change.
Next microtask: SEC07-C Android refresh serialization.