From 8f6d9320184cc766fb33b3aca8227b0936338cb3 Mon Sep 17 00:00:00 2001 From: KaizenPrad Date: Thu, 10 Sep 2026 18:51:54 +0530 Subject: [PATCH 1/2] fix: include auth state in cache key to prevent stale data after adding PAT (#228) fetchWithCache() previously used only the URL as the IndexedDB cache key, ignoring whether a PAT was provided. This caused unauthenticated responses to be returned even after the user added a PAT, because the cached unauthenticated page=1 response was served instead of re-fetching with auth. The cache key now appends '::auth' when a PAT is present, ensuring authenticated and unauthenticated responses are cached separately. --- src/services/github.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/services/github.js b/src/services/github.js index a4180fa..0b50411 100644 --- a/src/services/github.js +++ b/src/services/github.js @@ -53,8 +53,12 @@ export async function cacheClear() { // Core fetchWithCache async function fetchWithCache(url, pat) { + // Include auth state in cache key so authenticated and unauthenticated + // responses are cached separately (fixes #228) + const cacheKey = pat ? `${url}::auth` : url + // L2 check - const cached = await cacheGet(url) + const cached = await cacheGet(cacheKey) if (cached) return cached const headers = { Accept: 'application/vnd.github.v3+json' } @@ -78,7 +82,7 @@ async function fetchWithCache(url, pat) { if (!res.ok) throw new Error(`HTTP_${res.status}`) const data = await res.json() - cacheSet(url, data) // write-back, non-blocking + cacheSet(cacheKey, data) // write-back, non-blocking return data } From 75766e549d36fb76257cdb955ba01b5c68e46459 Mon Sep 17 00:00:00 2001 From: KaizenPrad Date: Thu, 10 Sep 2026 19:20:44 +0530 Subject: [PATCH 2/2] fix: use PAT hash in cache key instead of generic ::auth suffix Address CodeRabbit review (CWE-524): the previous fix used a generic '::auth' suffix for all PATs, which meant different PATs shared cache entries. Now uses a non-secret hash of the PAT so each unique token gets its own cache, without storing the raw token in IndexedDB. --- src/services/github.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/services/github.js b/src/services/github.js index 0b50411..637799b 100644 --- a/src/services/github.js +++ b/src/services/github.js @@ -51,11 +51,21 @@ export async function cacheClear() { } catch { return false } } +// Hash a PAT to a short non-secret identifier for cache keying (fixes #228) +function hashPAT(pat) { + let h = 0 + for (let i = 0; i < pat.length; i++) { + h = ((h << 5) - h + pat.charCodeAt(i)) | 0 + } + return (h >>> 0).toString(36) +} + // Core fetchWithCache async function fetchWithCache(url, pat) { - // Include auth state in cache key so authenticated and unauthenticated - // responses are cached separately (fixes #228) - const cacheKey = pat ? `${url}::auth` : url + // Include a non-secret per-identity hash in the cache key so each + // unique PAT gets its own cache entries, without storing the raw + // token in IndexedDB (fixes #228, addresses CodeRabbit CWE-524) + const cacheKey = pat ? `${url}::${hashPAT(pat)}` : url // L2 check const cached = await cacheGet(cacheKey)