-
Notifications
You must be signed in to change notification settings - Fork 3
XS✔ ◾ fix: don't require or send a GitHub PAT for the public stable release channel #983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -372,4 +372,24 @@ describe("ReleaseChannelIPCHandlers — PR releases require a healthy GitHub tok | |
| expect(verifyGitHubTokenMock).not.toHaveBeenCalled(); | ||
| expect(result).toEqual({ available: false, currentVersion: "1.2.3" }); | ||
| }); | ||
|
|
||
| it("does not attach a saved (possibly invalid/expired) token as an auth header when switching to the latest stable (public) channel (#600)", async () => { | ||
| // The stable channel is served from a public GitHub repo and must not require — or send — a | ||
| // GitHub token at all. Before the fix, configureAutoUpdater() unconditionally attached any | ||
| // saved token as a Bearer header before branching on channel type, so an invalid/expired token | ||
| // (which the user is never prompted to fix, since the stable channel doesn't gate on health) | ||
| // was still sent to the public GitHub releases provider. | ||
| getChannelMock.mockResolvedValue({ type: "latest" }); | ||
| getTokenMock.mockResolvedValue("saved-but-possibly-invalid-token"); | ||
|
|
||
| const { autoUpdater } = (await import("electron-updater")) as unknown as { | ||
| autoUpdater: { requestHeaders?: Record<string, string> }; | ||
| }; | ||
| autoUpdater.requestHeaders = {}; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] New regression test doesn't simulate the actual leftover-header regression scenario The new test resets The test as written only proves a token present in the token store isn't newly read/attached for the Suggested strengthening: seed flagged by: codex-rescue
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] New #600 regression test doesn't seed a leftover Authorization header, so it doesn't prove the strip The test sets Suggested fix: seed flagged independently by: code-review + codex-rescue |
||
|
|
||
| const handlers = new ReleaseChannelIPCHandlers(); | ||
| await handlers.configureAutoUpdater({ type: "latest" }); | ||
|
|
||
| expect(autoUpdater.requestHeaders?.Authorization).toBeUndefined(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] New #600 regression test doesn't actually seed a stale Both review lenses independently flagged this. The test sets Suggested fix: seed flagged by: code-review + codex-rescue |
||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -571,16 +571,17 @@ export class ReleaseChannelIPCHandlers { | |
| return; | ||
| } | ||
|
|
||
| const githubToken = await this.tokenStore.getToken(); | ||
| if (githubToken) { | ||
| autoUpdater.requestHeaders = { | ||
| ...autoUpdater.requestHeaders, | ||
| Authorization: `Bearer ${githubToken}`, | ||
| }; | ||
| } | ||
|
|
||
| // Configure autoUpdater based on channel | ||
| if (channel.type === "latest") { | ||
| // #600 — the stable channel is served from this repo's *public* GitHub releases, which never | ||
| // need authentication. Don't attach a saved token here at all: a saved-but-invalid/expired | ||
| // token (which this channel never gates on, unlike PR channels below) would otherwise be sent | ||
| // as a Bearer header on every request to the public provider, risking spurious 401s instead of | ||
| // the clean unauthenticated request GitHub expects for public repos. Explicitly clear any | ||
| // Authorization header a previous PR-channel configuration may have left behind. | ||
| const { Authorization: _unused, ...headersWithoutAuth } = autoUpdater.requestHeaders ?? {}; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Shared Two related gaps in how the fix manages the shared, module-level
Suggested fix: give request-header attachment a single, per-request source of truth (e.g. compute/attach the current header value at the point of each flagged by: code-review + codex-rescue (independently, from different angles)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Unused-var alias in destructuring-omit is inconsistent with the codebase's style
flagged by: code-review |
||
| autoUpdater.requestHeaders = headersWithoutAuth; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Stale
This is exactly the class of bug #600 is about (a stale/invalid token being sent when it shouldn't be). Today the UI always supplies a non-empty flagged by: code-review
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] No synchronization around
The flagged by: codex-rescue
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Header-clearing only handles the exact key "Authorization"; no defense against a differently-cased or additional leftover header The destructure flagged by: codex-rescue
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Destructure-to-omit pattern is a one-off compared to existing idioms in this file
flagged by: code-review |
||
|
|
||
| autoUpdater.channel = "latest"; | ||
| autoUpdater.allowPrerelease = false; | ||
| autoUpdater.allowDowngrade = false; | ||
|
|
@@ -618,6 +619,17 @@ export class ReleaseChannelIPCHandlers { | |
| return; | ||
| } | ||
|
|
||
| // Only attach the token once it's confirmed healthy above (#600) — PR release assets are | ||
| // downloaded via a "generic" feed pointed at this repo's own GitHub releases, which needs | ||
| // authentication headers to avoid anonymous rate limits during the download. | ||
| const githubToken = await this.tokenStore.getToken(); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [minor] Redundant token fetch in PR branch of configureAutoUpdater
flagged by: codex-rescue |
||
| if (githubToken) { | ||
| autoUpdater.requestHeaders = { | ||
| ...autoUpdater.requestHeaders, | ||
| Authorization: `Bearer ${githubToken}`, | ||
| }; | ||
| } | ||
|
|
||
| // For PR channels, we need to find the latest release tag first | ||
| const prNumber = this.extractPRNumberFromChannel(channel.channel); | ||
| if (prNumber) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[minor] New regression test doesn't verify clearing of a pre-existing stale Authorization header
The new test sets
autoUpdater.requestHeaders = {}(empty) before callingconfigureAutoUpdater({ type: "latest" }), then asserts noAuthorizationheader appears. This correctly pins the original bug (unconditional token attach before branching), but it would still pass even if the header-stripping destructure (const { Authorization: _unused, ...headersWithoutAuth } = autoUpdater.requestHeaders ?? {}) were deleted entirely — starting from an empty object there's nothing to strip. It doesn't exercise the scenario the added code comment explicitly claims to guard: "Explicitly clear any Authorization header a previous PR-channel configuration may have left behind."Suggested fix: add/extend a test that first sets
autoUpdater.requestHeaders = { Authorization: 'Bearer stale-pr-token', 'X-Some-Other-Header': 'keep-me' }(simulating state left over from a prior PR-channel configuration) before callingconfigureAutoUpdater({ type: 'latest' }), then assertAuthorizationis gone and the unrelated header survives.flagged by: code-review + codex-rescue (independent agreement)