authorgravatar for akiraueno@outlook.comakiraueno <akiraueno@outlook.com> 2026-09-13 04:07:33+09:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-12 12:07:33-07:00
logc542b781c6c4c766e66dcb764b2142d95600e890
tree58d8b7af3b841215a79f4985cde3a659d8784655
parentbbedad0278bbf753503184c00e0c09a0eab6679c
signature Signed by PGP key B5690EEEBB952194

fix(desktop): keep the native preview User-Agent so Turnstile passes (#7110)


2 files changed, 45 insertions(+), 6 deletions(-)

apps/desktop/src/preview/BrowserSession.test.ts+39-1
...@@ -13,7 +13,7 @@ const { fromPartition, sessions } = vi.hoisted(() => ({...@@ -13,7 +13,7 @@ const { fromPartition, sessions } = vi.hoisted(() => ({
13 {13 {
14 readonly clearCache: ReturnType<typeof vi.fn>;14 readonly clearCache: ReturnType<typeof vi.fn>;
15 readonly clearStorageData: ReturnType<typeof vi.fn>;15 readonly clearStorageData: ReturnType<typeof vi.fn>;
16 readonly getUserAgent: ReturnType<typeof vi.fn>;16 readonly getUserAgent: ReturnType<typeof vi.fn<() => string>>;
17 readonly setPermissionRequestHandler: ReturnType<typeof vi.fn>;17 readonly setPermissionRequestHandler: ReturnType<typeof vi.fn>;
18 readonly setPermissionCheckHandler: ReturnType<typeof vi.fn>;18 readonly setPermissionCheckHandler: ReturnType<typeof vi.fn>;
19 readonly setUserAgent: ReturnType<typeof vi.fn>;19 readonly setUserAgent: ReturnType<typeof vi.fn>;
...@@ -102,6 +102,44 @@ describe("BrowserSession", () => {...@@ -102,6 +102,44 @@ describe("BrowserSession", () => {
102 }).pipe(Effect.provide(layer)),102 }).pipe(Effect.provide(layer)),
103 );103 );
104104
105 // A rewritten session UA — any variant, even ones that keep the Electron
106 // token — makes Cloudflare Turnstile loop with error 600010 (#5002), so
107 // the guest must end up with Electron's native User-Agent. The mock applies
108 // setUserAgent calls, so this fails on any reintroduced rewrite while still
109 // permitting a harmless re-set of the unchanged native string.
110 it.effect("keeps the guest's effective User-Agent equal to Electron's native one", () =>
111 Effect.gen(function* () {
112 // Electron's real UA shape: app token, then Chrome, then Electron, then
113 // Safari — the token order and casing matter to any strip regex.
114 const nativeUserAgent =
115 "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) T3Code(Alpha)/0.0.33 Chrome/146.0.7680.216 Electron/41.5.0 Safari/537.36";
116 fromPartition.mockReset();
117 fromPartition.mockImplementation((partition: string) => {
118 let userAgent = nativeUserAgent;
119 const browserSession = {
120 clearCache: vi.fn(() => Promise.resolve()),
121 clearStorageData: vi.fn(() => Promise.resolve()),
122 getUserAgent: vi.fn(() => userAgent),
123 setPermissionRequestHandler: vi.fn(),
124 setPermissionCheckHandler: vi.fn(),
125 setUserAgent: vi.fn((next: string) => {
126 userAgent = next;
127 }),
128 };
129 sessions.set(partition, browserSession);
130 return browserSession;
131 });
132
133 const browserSessions = yield* BrowserSession.BrowserSession;
134 const partition = yield* browserSessions.getPartition("scope-a");
135 yield* browserSessions.getSession("scope-a");
136
137 const browserSession = sessions.get(partition);
138 assert.isDefined(browserSession);
139 assert.strictEqual(browserSession.getUserAgent(), nativeUserAgent);
140 }).pipe(Effect.provide(layer)),
141 );
142
105 it.effect("grants clipboard-sanitized-write through both the request and check handlers", () =>143 it.effect("grants clipboard-sanitized-write through both the request and check handlers", () =>
106 Effect.gen(function* () {144 Effect.gen(function* () {
107 const browserSessions = yield* BrowserSession.BrowserSession;145 const browserSessions = yield* BrowserSession.BrowserSession;
apps/desktop/src/preview/BrowserSession.ts+6-5
...@@ -197,11 +197,12 @@ export const make = Effect.gen(function* BrowserSessionMake() {...@@ -197,11 +197,12 @@ export const make = Effect.gen(function* BrowserSessionMake() {
197 return Effect.try({197 return Effect.try({
198 try: () => {198 try: () => {
199 const browserSession = session.fromPartition(partition);199 const browserSession = session.fromPartition(partition);
200 const userAgent = browserSession200 // The guest keeps Electron's native User-Agent. Rewriting it in any
201 .getUserAgent()201 // form — even variants that keep the Electron token — makes Cloudflare
202 .replace(/Electron\/[\d.]+ /, "")202 // Turnstile fail its integrity check with error 600010 and recreate
203 .replace(/\s*t3code\/[\d.]+/, "");203 // the challenge every few seconds, so logins behind it never complete
204 browserSession.setUserAgent(userAgent);204 // (#5002). Re-setting the unchanged native string is harmless, so it
205 // is the rewritten string itself that trips the check.
205 browserSession.setPermissionRequestHandler((_webContents, permission, callback) => {206 browserSession.setPermissionRequestHandler((_webContents, permission, callback) => {
206 callback(ALLOWED_PREVIEW_PERMISSIONS.has(permission));207 callback(ALLOWED_PREVIEW_PERMISSIONS.has(permission));
207 });208 });