| ... | @@ -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 | ); |
| 104 | | 104 | |
| | 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; |