fix(web): keep user-token lookups out of the shared result cache (#164) - #173
lukaso-bot wants to merge 1 commit into
Conversation
A lookup made with X-User-Github-Token / X-User-Gitlab-Token may be a private repo's answer. It was written to the same anonymous cache slot that public permalinks, badges and OG cards read, and could join an anonymous request's single-flight. makeWorkerCache now returns a non-shared cache for such requests (no reads, no writes), and the three single-flight sites skip coalescing for it. Closes #164 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Preview deployed
Federated GitLab lookups (freedesktop / GNOME) degrade to the "use the CLI" card — the Anubis relay is off in preview. GitHub lookups, permalinks, and OG render work once |
|
Why the This PR changes no dependencies (8 files under
#170 (Dependabot group bump) moves all three to the fixed versions, and its osv check is green. Merge order: merge #170 first, then click "Update branch" here (or say Preview check (
|
Closes #164.
A lookup made with
X-User-Github-Token/X-User-Gitlab-Tokencan be a private repo's answer. Onmainit was written to the same 5-part cache slot that the public permalink, badge and OG routes read without auth, and an anonymous request running at the same time could join its single-flight and get the result.Change
auth.ts:hasUserToken(req)(either header, blank ignored, same rule as the token resolvers).cache.ts:makeWorkerCachereturns a non-shared cache for such requests: no reads, no writes,shared: false. All six routes get their cache from here, so this covers every slot read and write.resolve.ts,routes/lookup.ts,routes/internal.ts: the threesingleFlightsites skip coalescing when!cache.shared.This is option 2 from the issue (do not cache PAT lookups). Cost: a private lookup recomputes every time. Option 1 (a per-PAT key namespace) can come later if that cost matters.
Evidence
New tests, each red on
origin/mainbefore the fix:cache.test.ts: a token request never callscaches.default.put/match(both headers). Control: a blank header still uses the store.resolve.test.ts: an anonymous call concurrent with a PAT call on one key runs its own load. Control: two anonymous calls still share one load.integration.test.ts:/api/lookupwith a PAT does not return a seeded shared slot, and an anonymous call still does./api/lookupand/internal/h/...: a PAT request and a concurrent anonymous one get different bodies.Mutants (each hunk reverted on its own, tests re-run):
makeWorkerCacheearly return removed: 5 tests redresolve.tsflight gate forced on: 1 redlookup.tsflight gate forced on: 1 redinternal.tsflight gate forced on: 1 redpnpm -r typecheckclean,pnpm testgreen (core 230, cli 45, web 285, web-og 44+8).Out of scope
cache-control: public, …. Cloudflare does not store those itself, but a shared proxy could. Nothing in the repo sends these headers on GET. Noted in the backlog.🤖 Generated with Claude Code