fix(github): evict a cached App JWT on rejection to stop fleet-wide token-minting poisoning - #2480
Conversation
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ Gittensory review result - manual review recommendedReview updated: 2026-07-02 07:14:58 UTC
⏸️ Suggested Action - Manual Review
Review summary Nits — 6 non-blocking
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2480 +/- ##
==========================================
+ Coverage 95.94% 95.95% +0.01%
==========================================
Files 226 226
Lines 25361 25436 +75
Branches 9229 9247 +18
==========================================
+ Hits 24333 24408 +75
Misses 417 417
Partials 611 611
🚀 New features to boost your workflow:
|
a7ca98f to
6ccc1f2
Compare
…oken-minting poisoning createAppJwt caches the App-level JWT for ~8 minutes with no eviction path on rejection, unlike installation tokens (evicted + retried once by withInstallationTokenRetry since #2406). A single GitHub-side rejection of the currently-cached App JWT — a transient validation hiccup, a clock-skew edge case, or a brief App suspend/reinstate — kept getting reused by every subsequent installation-token mint attempt across EVERY installation on the instance, since mintInstallationToken threw straight through on the first non-ok response with no retry at all. Add expireCachedAppJwt (mirroring expireCachedInstallationToken) and have mintInstallationToken evict + re-sign + retry once on a 401 from the access-tokens endpoint, bounded the same way withInstallationTokenRetry already bounds installation-token retries. Fixes #2453
…jected createAppJwt caches optimistically before GitHub validates the JWT. The first-401 retry path evicted and re-signed once, but if that retried JWT was ALSO rejected, the just-rejected JWT stayed cached and kept poisoning every installation-token mint fleet-wide for up to APP_JWT_REUSE_MS -- the exact failure mode the retry was meant to fix (flagged by the gate's own review). Evict again on a second 401 before throwing, so the next mint attempt signs fresh instead of replaying the poisoned JWT.
6ccc1f2 to
39c96c2
Compare
Summary
createAppJwt(src/github/app.ts) caches the App-level JWT for ~8 minutes (APP_JWT_REUSE_MS) with no eviction path on rejection, unlike installation tokens — those are evicted and retried once bywithInstallationTokenRetry(shipped in fix(github): evict a rejected installation token on PR-mutating calls #2406). A single GitHub-side rejection of the currently-cached App JWT (a transient validation hiccup, a clock-skew edge case, or a brief App suspend/reinstate, withenv.GITHUB_APP_PRIVATE_KEYunchanged) kept getting reused bymintInstallationTokenon every subsequent installation-token mint attempt, across every installation on the instance, because the function threw straight through on the first non-ok response from the access-tokens endpoint with no retry at all.expireCachedAppJwt(mirrorsexpireCachedInstallationToken's exact eviction pattern) and hasmintInstallationTokenevict + re-sign + retry once on a 401 from/app/installations/{id}/access_tokens, bounded the same waywithInstallationTokenRetryalready bounds installation-token retries — a persistently-bad key (or a genuinely revoked App) still fails cleanly after the one retry, not an infinite loop.requestInstallationTokenWithJwthelper so the retry can issue the identical request twice without duplicating the fetch/headers wiring.Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coverage— new code is 100% line+branch covered (verified againstcoverage/lcov.infofor the exact edited range insrc/github/app.ts; file totals 181/181 lines, the 4 remaining uncovered branches are pre-existing, outside this PR's lines). Global 96.55%/95.53%.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderate— 0 vulnerabilitiesFull suite: 6062/6062 passing. Also ran
npm run db:migrations:check(unaffected, green).Safety
Notes