)]}'
{
  "commit": "22a089aada723ab9006471ba0fdf55f423036cd3",
  "tree": "16557b1713c7aa1402066a3e97ccb920fdcd4701",
  "parents": [
    "6f2379672aafeca8569c49be3a7f232af5350d1e"
  ],
  "author": {
    "name": "Ben Collins",
    "email": "bcollins@libjwt.io",
    "time": "Thu Jun 18 18:55:16 2026 -0400"
  },
  "committer": {
    "name": "Ben Collins",
    "email": "bcollins@libjwt.io",
    "time": "Thu Jun 18 18:55:16 2026 -0400"
  },
  "message": "jwks: harden the cached source against HTTP errors and failing refreshes\n\nAdversarial review of the cache found three real defects; fix all three:\n\n  - HTTP errors wiped the cache. cache_apply() special-cased only 304 and\n    replaced the keys for every other status. __curl_fetch() returns success\n    for any completed HTTP exchange (a 4xx/5xx, or an unfollowed 3xx), so a\n    transient origin/proxy error destroyed a good keyring and then marked the\n    empty set fresh. Now only a 2xx (with a body that parses to a usable JWKS)\n    replaces the keys; 304 keeps them; any other status keeps the previously\n    cached keys and sets an error -- matching the documented contract.\n\n  - Cooldown did not bound FAILING refreshes. last_fetch was stamped only on a\n    completed fetch (in cache_apply), so while the JWKS host was unreachable a\n    kid-miss refresh was never throttled -- an unbounded outbound-request\n    amplifier. last_fetch is now stamped before every attempt, so a failed\n    attempt still consumes the cooldown window.\n\n  - A server-controlled Cache-Control max-age could overflow time_t in the\n    expiry computation. The lifetime (server max-age or configured TTL) is now\n    clamped to JWKS_MAX_TTL (one week).\n\ntests/jwt_jwks_cache.c: new test asserting a 5xx during a forced refresh\nretains the cached keys and reports the error. 36/36 suites pass; valgrind-clean.\n\nCo-Authored-By: Claude Opus 4.8 (1M context) \u003cnoreply@anthropic.com\u003e\nSigned-off-by: Ben Collins \u003cbcollins@libjwt.io\u003e\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "7de74d00cc7bedce7b2ba6ae10ab18504816a1ac",
      "old_mode": 33188,
      "old_path": "libjwt/jwks-curl.c",
      "new_id": "c486bc6ecd3d5436d1330d602a4a8724ba439487",
      "new_mode": 33188,
      "new_path": "libjwt/jwks-curl.c"
    },
    {
      "type": "modify",
      "old_id": "edd705e8fb77ea8e0f3cfc06e1feb44f1a7e50d5",
      "old_mode": 33188,
      "old_path": "tests/jwt_jwks_cache.c",
      "new_id": "e7baa50f05891ae29502b17aca2dc831c3cfcc5c",
      "new_mode": 33188,
      "new_path": "tests/jwt_jwks_cache.c"
    }
  ]
}
