#22352: fix(auth): validate Copilot token cache belongs to requesting account
size: S
trusted-contributor
Cluster:
GitHub Copilot Enhancements
## Problem
`resolveCopilotApiToken()` uses a single shared cache file (`github-copilot.token.json`) for all Copilot auth profiles. When multiple GitHub accounts are configured, a cached API token from account A is returned for requests meant for account B. If account A is rate-limited, account B's requests fail too — even though account B has available quota (#22264).
The cache check only validates expiry (`isTokenUsable`), never whether the cached token actually belongs to the requesting account.
## Fix
Store a SHA-256 hash of the `ghu_` token alongside the cached API token. On cache read, compare the hash against the current request's `githubToken`. A mismatch triggers a fresh token exchange instead of returning the wrong account's token.
This is backward-compatible: existing cache files without `githubTokenHash` simply miss the cache on first read and get refreshed with the hash included.
## Changes
- `src/providers/github-copilot-token.ts`: Add `githubTokenHash` to `CachedCopilotToken`, validate on read, persist on write
- `src/providers/github-copilot-token.test.ts`: Add test for cross-account cache rejection, update existing cache test to include hash
Closes #22264
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Fixes a multi-account Copilot auth bug where a single shared cache file (`github-copilot.token.json`) could serve a stale token from account A to requests intended for account B, causing cross-account rate-limit bleed (#22264).
- Adds a SHA-256 hash of the `ghu_` GitHub token to the cached payload (`githubTokenHash` field in `CachedCopilotToken`)
- On cache read, validates the hash matches the requesting account's token before returning a cached result; a mismatch triggers a fresh token exchange
- Backward-compatible: existing cache files without `githubTokenHash` simply miss on first read and get refreshed with the hash included
- Adds three well-scoped test cases covering cache hit (hash match), cache miss (hash mismatch / cross-account), and backward compatibility (missing hash field)
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge — it's a targeted, backward-compatible bug fix with good test coverage.
- The change is minimal and well-scoped: a single additional field on the cache type, one hash comparison added to the cache check, and one hash write on cache save. The backward-compat path (missing hash causes cache miss) is correct by construction since `undefined !== string`. The SHA-256 hashing avoids storing raw tokens. All three callers in the codebase pass `githubToken` to the function already, so no caller changes are needed. Tests cover the key scenarios.
- No files require special attention
<sub>Last reviewed commit: d1d2a37</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#4364: fix(github-copilot): use gho_ tokens directly without exchange
by RebelSyntax · 2026-01-30
80.8%
#8805: [Bug Fix][AI-assisted] Refresh Copilot token before expiry and retr...
by Arthur742Ramos · 2026-02-04
76.4%
#13505: feat(copilot): add GitHub Enterprise Cloud (GHE.com) support for Co...
by kryptus47 · 2026-02-10
75.3%
#11782: fix: resolve 403 auth error for GithubCopilot imageModel (#10277)
by adamkoncz · 2026-02-08
74.4%
#19885: test(gateway,browser): isolate tests from ambient OPENCLAW_GATEWAY_...
by NewdlDewdl · 2026-02-18
72.7%
#8467: fix(github-copilot): add configurable IDE headers + fix null filtering
by ericchansen · 2026-02-04
71.0%
#2123: fix(auth): sync from Claude CLI keychain before OAuth refresh
by jorge123255 · 2026-01-26
71.0%
#12414: fix(slack): do not cache API failures in thread_ts resolver
by Yida-Dev · 2026-02-09
70.8%
#3909: fix(auth): refresh all OAuth profiles per provider
by Daviey · 2026-01-29
70.8%
#5027: fix(auth): use correct OAuth credentials for google-gemini-cli refresh
by shayan919293 · 2026-01-30
70.5%