Skip to content

Commit 6d11883

Browse files
SDSLeonclaude
andauthored
fix(browser): keep app product token in UA so Google sign-in works in-app (#235)
Google sign-in rejected the in-app browser with "this browser or app may not be secure" at the identifier step. Root cause: buildChromeLikeUserAgent stripped both the Electron token AND the app's own product token (Lightcode/x.y.z), leaving a bare Chrome user agent. Google rejects a bare-Chrome UA coming from an embedded browser as spoofed/unsupported, while it accepts a Chromium UA that honestly identifies the host app (verified against the live flow; this is also why Codex / T3 Code embedded browsers, which only drop Electron, are accepted). Strip only the Electron product token and keep the app token. Renamed buildChromeLikeUserAgent -> buildBrowserUserAgent for accuracy. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent dd09068 commit 6d11883

3 files changed

Lines changed: 43 additions & 31 deletions

File tree

src/main/browser/userAgent.test.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
11
import { describe, expect, it } from "vitest";
2-
import { buildChromeLikeUserAgent } from "./userAgent";
2+
import { buildBrowserUserAgent } from "./userAgent";
33

4-
describe("buildChromeLikeUserAgent", () => {
5-
it("removes Electron while preserving the current Chromium user agent", () => {
4+
describe("buildBrowserUserAgent", () => {
5+
it("removes the Electron product token", () => {
66
expect(
7-
buildChromeLikeUserAgent(
8-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Electron/41.7.0 Safari/537.36",
7+
buildBrowserUserAgent(
8+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Electron/41.7.0 Safari/537.36",
99
),
1010
).toBe(
11-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
11+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
1212
);
1313
});
1414

15-
it("removes an app product token before Chrome", () => {
15+
it("keeps the app product token before Chrome (Google rejects a bare Chrome UA from an embedded browser)", () => {
1616
expect(
17-
buildChromeLikeUserAgent(
18-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Lightcode/1.2.1 Chrome/141.0.0.0 Electron/41.7.0 Safari/537.36",
17+
buildBrowserUserAgent(
18+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Lightcode/1.3.2 Chrome/146.0.0.0 Electron/41.7.0 Safari/537.36",
1919
),
2020
).toBe(
21-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
21+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Lightcode/1.3.2 Chrome/146.0.0.0 Safari/537.36",
2222
);
2323
});
2424

25-
it("matches the equivalent real Chrome user agent", () => {
26-
const realChromeLinuxUserAgent =
27-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36";
28-
25+
it("preserves a channel-suffixed app token such as Lightcode Nightly", () => {
2926
expect(
30-
buildChromeLikeUserAgent(
31-
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Lightcode/1.2.1 Chrome/141.0.0.0 Electron/41.7.0 Safari/537.36",
27+
buildBrowserUserAgent(
28+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Lightcode Nightly/1.3.2 Chrome/146.0.0.0 Electron/41.7.0 Safari/537.36",
3229
),
33-
).toBe(realChromeLinuxUserAgent);
30+
).toBe(
31+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Lightcode Nightly/1.3.2 Chrome/146.0.0.0 Safari/537.36",
32+
);
3433
});
3534
});

src/main/browser/userAgent.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1+
// Electron's default user agent looks like
2+
// ...(KHTML, like Gecko) Lightcode/1.3.2 Chrome/146.0.0.0 Electron/41.7.0 Safari/537.36
3+
// Google sign-in (and other "is this a secure browser?" gates) reject any user
4+
// agent that contains the `Electron/<version>` product token, showing
5+
// "Couldn't sign you in — this browser or app may not be secure".
6+
//
7+
// The fix is to drop ONLY the `Electron/...` token. It is tempting to also strip
8+
// the host app's own product token (`Lightcode/1.3.2`) to make the UA look like
9+
// stock Chrome, but that backfires: Google rejects a *bare* `Chrome/...` UA
10+
// coming from an embedded browser as a spoofed/unsupported browser, while it
11+
// accepts a Chromium UA that honestly identifies the host app. Keeping the app
12+
// token is precisely what lets Google sign-in complete inside the in-app browser
13+
// (verified against the live flow; this is also why Codex / T3 Code embedded
14+
// browsers, which keep their app token and only drop Electron, are accepted).
115
const ELECTRON_PRODUCT_RE = /\sElectron\/[^\s]+/g;
2-
const APP_PRODUCT_BEFORE_CHROME_RE =
3-
/(\(KHTML, like Gecko\)\s+)(?:(?!Chrome\/)\S+\/\S+\s+)+(Chrome\/)/;
416

5-
export function buildChromeLikeUserAgent(defaultUserAgent: string): string {
6-
const withoutElectron = defaultUserAgent.replace(ELECTRON_PRODUCT_RE, "");
7-
const withoutAppProduct = withoutElectron.replace(APP_PRODUCT_BEFORE_CHROME_RE, "$1$2");
8-
return withoutAppProduct.replace(/\s{2,}/g, " ").trim();
17+
export function buildBrowserUserAgent(defaultUserAgent: string): string {
18+
return defaultUserAgent
19+
.replace(ELECTRON_PRODUCT_RE, "")
20+
.replace(/\s{2,}/g, " ")
21+
.trim();
922
}

src/main/main.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
installPickerProtocolHandler,
1919
registerPickerProtocolScheme,
2020
} from "./browser";
21-
import { buildChromeLikeUserAgent } from "./browser/userAgent";
21+
import { buildBrowserUserAgent } from "./browser/userAgent";
2222
import { SupervisorClient } from "./supervisor/SupervisorClient";
2323
import { createAutoUpdaterController } from "./updates/autoUpdater";
2424
import { createMainWindow } from "./window/createMainWindow";
@@ -49,8 +49,8 @@ if (process.platform === "win32") {
4949
app.commandLine.appendSwitch("force-color-profile", "srgb");
5050
}
5151

52-
const chromeLikeUserAgent = buildChromeLikeUserAgent(app.userAgentFallback);
53-
app.userAgentFallback = chromeLikeUserAgent;
52+
const browserUserAgent = buildBrowserUserAgent(app.userAgentFallback);
53+
app.userAgentFallback = browserUserAgent;
5454

5555
if (baseDirOverride) {
5656
app.setPath("userData", join(baseDirOverride, "userData"));
@@ -190,7 +190,7 @@ function createBrowserExtractWindow(): BrowserWindow {
190190
posthogKey,
191191
sentryEnabled,
192192
windowChromeHeight: WINDOW_CHROME_HEIGHT,
193-
browserUserAgent: chromeLikeUserAgent,
193+
browserUserAgent,
194194
appearance: windowChrome.appearance,
195195
sidebarTranslucency: windowChrome.sidebarTranslucency,
196196
openDevTools: false,
@@ -291,7 +291,7 @@ if (!hasSingleInstanceLock) {
291291

292292
installLocalFileProtocolHandler();
293293
installPickerProtocolHandler();
294-
electronSession.fromPartition("persist:lightcode-browser").setUserAgent(chromeLikeUserAgent);
294+
electronSession.fromPartition("persist:lightcode-browser").setUserAgent(browserUserAgent);
295295

296296
lightcodePaths = prepareLightcodeDataRoot(
297297
baseDirOverride ??
@@ -366,7 +366,7 @@ if (!hasSingleInstanceLock) {
366366
},
367367
);
368368

369-
browserPanelManager = new BrowserPanelManager(lightcodePaths, chromeLikeUserAgent, {
369+
browserPanelManager = new BrowserPanelManager(lightcodePaths, browserUserAgent, {
370370
isExtracted: () => browserExtractWindow !== null && !browserExtractWindow.isDestroyed(),
371371
focusExtractedWindow: focusBrowserExtractWindow,
372372
});
@@ -411,7 +411,7 @@ if (!hasSingleInstanceLock) {
411411
posthogKey,
412412
sentryEnabled,
413413
windowChromeHeight: WINDOW_CHROME_HEIGHT,
414-
browserUserAgent: chromeLikeUserAgent,
414+
browserUserAgent,
415415
appearance: windowChrome.appearance,
416416
sidebarTranslucency: windowChrome.sidebarTranslucency,
417417
...(process.env.VITE_DEV_SERVER_URL ? { devServerUrl: process.env.VITE_DEV_SERVER_URL } : {}),
@@ -502,7 +502,7 @@ if (!hasSingleInstanceLock) {
502502
posthogKey,
503503
sentryEnabled,
504504
windowChromeHeight: WINDOW_CHROME_HEIGHT,
505-
browserUserAgent: chromeLikeUserAgent,
505+
browserUserAgent,
506506
appearance: reopenChrome.appearance,
507507
sidebarTranslucency: reopenChrome.sidebarTranslucency,
508508
...(process.env.VITE_DEV_SERVER_URL

0 commit comments

Comments
 (0)