Skip to content

Fall back to still-valid cached keys when a JWKS refresh fails - #217

Open
djw8605 wants to merge 2 commits into
scitokens:masterfrom
djw8605:fix/stale-key-fallback
Open

Fall back to still-valid cached keys when a JWKS refresh fails#217
djw8605 wants to merge 2 commits into
scitokens:masterfrom
djw8605:fix/stale-key-fallback

Conversation

@djw8605

@djw8605 djw8605 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

When cached keys pass next_update, verification starts a refresh and sets m_ignore_error = true, intending to tolerate refresh failure — the cached keys remain valid until their much later expiry (default: refresh every 600 s, keys valid 4 days).

That flag is broken twice over:

  1. It's set on the old status object, which is immediately replaced by get_public_keys_from_web()'s freshly-constructed status — so the flag is lost.
  2. Nothing ever reads m_ignore_error anyway (grep confirms the only reference is the assignment).

The existing catch only covers a synchronous failure inside perform_start(). Once the refresh continues asynchronously (the normal case for network I/O), any failure — issuer briefly down, timeout, bad response — propagates out of verify() and fails the validation despite valid cached keys, defeating the stale-key tolerance the keycache is designed for.

Fix

  • Carry the flag plus the still-valid cached keys onto the refresh status (m_fallback_keys).
  • In get_public_key_pem_continue, save the fallback state in locals before calling into the fetch (the status object is destroyed during unwinding if it throws), and on failure fall back to the cached keys — recording failed_refreshes in the monitoring stats — instead of rethrowing.
  • The keycache entry is untouched, so the next validation past next_update retries the refresh.

Testing

  • New regression test SerializeTest.StaleKeyFallbackTest: stores valid keys for an unreachable issuer (https://127.0.0.1:1, connection refused — no external network) with a 1-second update interval, so verification triggers an asynchronously failing refresh. Verified the test fails without the fix and passes with it.
  • ctest unit, env_config, and monitoring suites pass.

🤖 Generated with Claude Code

djw8605 and others added 2 commits July 6, 2026 13:34
When cached keys pass next_update, verification kicks off a refresh
and sets m_ignore_error, intending to tolerate a refresh failure since
the cached keys remain valid until their (much later) expiry.  That
flag was broken twice over: it was set on the old status object which
was immediately replaced by get_public_keys_from_web()'s new status,
and nothing ever read it.  The catch block only covered a synchronous
failure in perform_start(); once the refresh continued asynchronously,
any failure (issuer briefly down, timeout, bad response) propagated
out of verify() and failed the validation despite valid cached keys --
defeating the stale-key tolerance the keycache is designed for
(refresh every 10 minutes, keys valid for 4 days).

Carry the flag and the still-valid cached keys onto the refresh
status, and on an asynchronous fetch failure fall back to those keys
(recording a failed refresh in the monitoring stats) instead of
rethrowing.  The keycache entry is untouched, so the next validation
past next_update retries the refresh.

The regression test stores keys for an unreachable issuer
(https://127.0.0.1:1) with a 1-second update interval: the refresh
fails asynchronously and validation must still succeed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@djw8605 djw8605 added the ai-gen label Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant