Repository navigation
fix: Expire the cached JWK Set used to validate signatures - #29
Draft
maikdijkstra wants to merge 1 commit into
Draft
maikdijkstra wants to merge 1 commit into
maikdijkstra wants to merge 1 commit into
Conversation
Keypair.cached_jwks cached the JWK Set used by jwt_decode without an expiry. It was only refreshed when a token had an unknown kid, and the next keypair is published ahead of time, so keypairs past their expires_at (or deleted after a compromise) kept validating signatures for as long as the cache store kept the entry. Expire the entry when the first keypair expires, at most after 5 minutes, under a new cache key so entries without an expiry written by earlier versions are ignored.
maikdijkstra
force-pushed
the
fix/expire-cached-jwks
branch
from
November 11, 2026 14:09
90c0ec2 to
c736eee
Compare
This branch has not been deployed
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.
Summary
Keypair.jwt_decode jwks: jwk_loader_cached cached_jwks(force: unknown kid) - Rails.cache.fetch('keypairs/Keypair/jwks') # never expires + Rails.cache.fetch('keypairs/Keypair/jwks/v2') # new key: old entries never expired + expires_in = min(5 minutes, first keypair expires_at - now)Without an expiry, the cached JWK Set was only refreshed when a token had an unknown
kid. Because the next keypair is published ahead of time, rotation rarely triggered that, so keypairs past theirexpires_at, or deleted after a compromise, kept validating signatures for as long as the cache store kept the entry.The first expiry comes from the keypairs that
keysetalready loads (nowvalid_keypairs), so this adds no extra query.Found during a JWT audit of drieam-lti, which will require this release instead of patching
Keypairitself.Evidence
.cached_jwksspecs fail, e.g.105 examples, 0 failures→ raisesJWT::DecodeError. RuboCop is clean.Merge Danger
Door: two-way
Code-only. Reverting falls back to the old cache key.
Blast Radius: signature validation
keysetalready does.