Skip to content

Fix #1051: Replace lru_cache with TTL-based key caching - #1070

Open
ArvinAlizadehGitHub wants to merge 3 commits into
jpadilla:masterfrom
ArvinAlizadehGitHub:fix-lru-cache-security-vulnerability
Open

ArvinAlizadehGitHub wants to merge 3 commits into
jpadilla:masterfrom
ArvinAlizadehGitHub:fix-lru-cache-security-vulnerability

Conversation

@ArvinAlizadehGitHub

Copy link
Copy Markdown

Summary

This PR fixes issue #1051 where PyJWKClient with cache_keys=True serves potentially revoked keys indefinitely.

Problem

When cache_keys=True is enabled, PyJWKClient applies @lru_cache to the get_signing_key method. This caches keys permanently until LRU eviction or process restart. If an identity provider removes a key from their JWKS, applications continue accepting tokens signed with that key.

Solution

  • Replace lru_cache with TTL-aware caching
  • Keys now expire after the configured lifespan (default 300 seconds)
  • Maintains full backward compatibility with existing API
  • Uses same expiration logic as existing JWKSetCache

Changes

  • jwt/jwks_client.py: Replace lru_cache with TTL cache implementation
  • tests/test_jwks_client.py: Add test demonstrating the fix works

Testing

All existing tests pass. New test verifies that:

  • Old implementation serves cached keys indefinitely
  • New implementation properly rejects expired keys

Backward Compatibility

No breaking changes. All existing parameters work identically:

  • cache_keys=True still enables individual key caching
  • max_cached_keys still limits cache size
  • Performance characteristics maintained

Fixes #1051

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes issue #1051 by replacing the permanent lru_cache with a TTL-aware cache for individual keys, ensuring that revoked keys are no longer served indefinitely.

  • Replaces lru_cache with a TTL-based caching mechanism in jwt/jwks_client.py.
  • Adds tests in tests/test_jwks_client.py to verify key expiration and proper cache eviction.
  • Updates the CHANGELOG to document the change.

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/test_jwks_client.py Adds new tests verifying that expired keys are rejected and cache eviction works correctly.
jwt/jwks_client.py Refactors caching logic by adding TTL-based caching methods and removing lru_cache.
CHANGELOG.rst Updates changelog to capture the new TTL-aware key caching fix.
Comments suppressed due to low confidence (1)

CHANGELOG.rst:12

  • The issue reference in the changelog appears to be inconsistent with the PR title (#1051). Please update the issue reference to #1051 if that is the correct identifier.
- Fix indefinite key caching in PyJWKClient by replacing lru_cache with TTL-aware cache in `#1070 <https://github.com/jpadilla/pyjwt/pull/1070>`__

Comment thread tests/test_jwks_client.py Outdated
@ArvinAlizadehGitHub

ArvinAlizadehGitHub commented Jul 7, 2025 •

Copy link
Copy Markdown
Author

Hi @auvipy (or @jpadilla)
The pre-commit checks are failing on an unrelated mypy error in jwt/algorithms.py line 796 (redundant type cast). This error exists in the main branch and is unrelated to my changes.

My changes only touch jwt/jwks_client.py and the test file. The failing mypy check is for code I didn't modify.

Could you advise if I should:

  1. Fix the unrelated mypy error in algorithms.py, or
  2. Have this merged as-is since it's a pre-existing issue?

The actual functionality tests are all passing - it's just the linting that's catching this pre-existing issue.

Would appreciate any feedback on next steps to move this forward. Happy to make any adjustments needed!

@github-actions github-actions Bot added the stale Issues without activity for more than 60 days label Nov 6, 2025
@github-actions github-actions Bot closed this Nov 13, 2025
@auvipy auvipy reopened this Nov 13, 2025
@github-actions github-actions Bot removed the stale Issues without activity for more than 60 days label Nov 14, 2025

@auvipy auvipy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please fix the merge conflicts

@thomasuster

Copy link
Copy Markdown

This is PR addresses a very important problem, looking forward to it's review/merge/release.

@auvipy

auvipy commented Mar 29, 2026

Copy link
Copy Markdown
Collaborator

there are merge conflicts to fix

@thomasuster

Copy link
Copy Markdown

@ArvinAlizadehGitHub

@thomasuster

Copy link
Copy Markdown

@auvipy Let's close this so someone else might consider making a PR to fix the issue.

@ArvinAlizadehGitHub

ArvinAlizadehGitHub commented Jul 17, 2026 •

Copy link
Copy Markdown
Author

@thomasuster @auvipy sorry ill take a look and get to fixing the errors

@auvipy

auvipy commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

@thomasuster @auvipy sorry ill take a look and get to fixing the errors

sure please

@ArvinAlizadehGitHub
ArvinAlizadehGitHub force-pushed the fix-lru-cache-security-vulnerability branch from a00aaff to b55a383 Compare July 20, 2026 08:02
@ArvinAlizadehGitHub

Copy link
Copy Markdown
Author

@thomasuster @auvipy should be updated to resolve conflicts and staleness, pls have a look / run workflows

@ArvinAlizadehGitHub
ArvinAlizadehGitHub force-pushed the fix-lru-cache-security-vulnerability branch from 5808196 to 465fe2b Compare July 21, 2026 06:43
@ArvinAlizadehGitHub

Copy link
Copy Markdown
Author

@auvipy @thomasuster pls have a look and rerun the workflows thanks

@thomasuster

Copy link
Copy Markdown

@auvipy Are we good to go?

@ArvinAlizadehGitHub

Copy link
Copy Markdown
Author

Hi @auvipy , any update on this PR? Thanks!

@thomasuster

Copy link
Copy Markdown

@jpadilla Are we good to go on this one?

@auvipy
auvipy requested a balanced review from Copilot September 2, 2026 09:02
@auvipy

auvipy commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

lets do another round of review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cache invalidation, thread safety, capacity edge cases, and backward-compatible LRU behavior remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 5
  • Review effort level: Balanced

Comment thread jwt/jwks_client.py
Comment on lines +194 to +197
if not self._key_cache_enabled or kid not in self._key_cache:
return None

key, timestamp = self._key_cache[kid]
Comment thread jwt/jwks_client.py
Comment on lines +233 to +235
cached_key = self._get_cached_key(kid)
if cached_key is not None:
return cached_key
Comment thread jwt/jwks_client.py
str, tuple[PyJWK, float]
] = {} # kid -> (key, timestamp)
self._max_cached_keys = max_cached_keys
self._key_cache_ttl = lifespan # Use same TTL as JWKSetCache
Comment thread jwt/jwks_client.py Outdated
Comment thread jwt/jwks_client.py
Comment on lines +210 to +215
# Evict oldest if at capacity
if len(self._key_cache) >= self._max_cached_keys and kid not in self._key_cache:
# Simple eviction: remove oldest timestamp
oldest_kid = min(
self._key_cache.keys(), key=lambda k: self._key_cache[k][1]
)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PyJWKClient with cache_keys=True serves potentially revoked keys

5 participants