diff --git a/src/main/browser/anti-detection.test.ts b/src/main/browser/anti-detection.test.ts index 1dfdd31b654..ddb904cb436 100644 --- a/src/main/browser/anti-detection.test.ts +++ b/src/main/browser/anti-detection.test.ts @@ -101,7 +101,7 @@ describe('ANTI_DETECTION_SCRIPT', () => { }) it.each(['geolocation', 'idle-detection', 'midi', 'storage-access'])( - 'preserves the native denied state for %s', + 'passes non-intercepted permission queries through to the native state for %s', async (name) => { const context = createContext({ nativeNotificationPermission: 'denied', diff --git a/src/main/browser/browser-session-permission-policy.ts b/src/main/browser/browser-session-permission-policy.ts index 895161db9e3..7dacf067dc7 100644 --- a/src/main/browser/browser-session-permission-policy.ts +++ b/src/main/browser/browser-session-permission-policy.ts @@ -12,8 +12,22 @@ const AUTO_GRANTED_BROWSER_PERMISSIONS = new Set([ 'persistent-storage', // Chromium still requires user activation, so this only removes Orca's // otherwise unactionable denial for immersive browser apps. - 'pointerLock' + 'pointerLock', + // Orca allows unpartitioned third-party cookies, so cross-site frames already send them; denying + // this only rejected the API and burned the caller's user gesture. Not cookies-only: the same + // permission also hands requestStorageAccess({localStorage: true}) a handle onto unpartitioned + // localStorage/IndexedDB, though ambient globals stay partitioned. Chrome grants the same + // permission under the same cookie policy, so this is parity. Electron has no auto-grant, so + // the embedder must answer, and check must agree with request or compliant sites fall back to + // the gesture path. Revisit if Orca ever gains a cookie or storage-partitioning control: the + // handle is gated separately and would survive, but Electron never writes the STORAGE_ACCESS + // content setting, so cookie access would stay blocked while the promise still resolved — and + // sites that reload on success would loop. + 'storage-access' ]) +// 'top-level-storage-access' is deliberately absent: Chromium gates requestStorageAccessFor() on +// Related Website Sets rather than cookie policy, and Orca has no such source, so the rationale +// above does not transfer to it. export function isAutoGrantedBrowserSessionPermission(permission: string): boolean { return AUTO_GRANTED_BROWSER_PERMISSIONS.has(permission) diff --git a/src/main/browser/browser-session-registry.persistence.test.ts b/src/main/browser/browser-session-registry.persistence.test.ts index f27a861a45c..193379239fc 100644 --- a/src/main/browser/browser-session-registry.persistence.test.ts +++ b/src/main/browser/browser-session-registry.persistence.test.ts @@ -547,6 +547,26 @@ describe('BrowserSessionRegistry persistence', () => { expect(checkHandler(null, 'persistent-storage', '')).toBe(true) expect(checkHandler(null, 'geolocation', '')).toBe(false) expect(checkHandler(null, 'media', '', { mediaType: 'video' })).toBe(true) + + // Why: this session allows unpartitioned third-party cookies, so a cross-site frame already has + // the access requestStorageAccess() would grant. Denying it protected nothing and only broke + // sites taking the API's failure path. Red before the grant landed. + requestHandler(guestWc, 'storage-access', permissionCallback) + expect(permissionCallback).toHaveBeenLastCalledWith(true) + expect(checkHandler(null, 'storage-access', '')).toBe(true) + + // Why: requestStorageAccessFor() is a different platform decision — Chromium consults Related + // Website Sets and has no third-party-cookie auto-grant, and Orca has no such data source. This + // pins the deliberate denial so a future blanket widening of the allow-set fails loudly. + requestHandler(guestWc, 'top-level-storage-access', permissionCallback) + expect(permissionCallback).toHaveBeenLastCalledWith(false) + expect(checkHandler(null, 'top-level-storage-access', '')).toBe(false) + + // Why: the reported symptom was a user-visible denial notice, so pin the notified list here — + // storage-access must no longer raise one, while the deliberate top-level denial still does. + expect( + browserManagerNotifyPermissionDeniedMock.mock.calls.map(([args]) => args.permission) + ).toEqual(['geolocation', 'top-level-storage-access']) expect(defaultSession.setDisplayMediaRequestHandler).toHaveBeenCalled() const displayMediaHandler = defaultSession.setDisplayMediaRequestHandler.mock.calls[0][0] const displayMediaCallback = vi.fn() diff --git a/src/main/browser/browser-session-registry.test.ts b/src/main/browser/browser-session-registry.test.ts index 9f32e4f379d..d91307d732f 100644 --- a/src/main/browser/browser-session-registry.test.ts +++ b/src/main/browser/browser-session-registry.test.ts @@ -249,6 +249,23 @@ describe('BrowserSessionRegistry', () => { expect(checkHandler(null, 'pointerLock', '', {})).toBe(true) }) + it('auto-grants storage-access for isolated partitions', () => { + // Why: mirrors the pointerLock precedent directly above — the default-partition suite does not + // reach this install path. + browserSessionRegistry.createProfile('isolated', 'Storage Access Test') + const mockSession = sessionFromPartitionMock.mock.results[0]?.value + const requestHandler = mockSession.setPermissionRequestHandler.mock.calls[0][0] + const checkHandler = mockSession.setPermissionCheckHandler.mock.calls[0][0] + const callback = vi.fn() + const guestWc = { id: 7, getURL: vi.fn(() => 'https://example.com/') } + + requestHandler(guestWc, 'storage-access', callback, {}) + + expect(callback).toHaveBeenCalledWith(true) + expect(checkHandler(null, 'storage-access', '', {})).toBe(true) + expect(checkHandler(null, 'top-level-storage-access', '', {})).toBe(false) + }) + it('routes media permission requests through macOS TCC for isolated partitions', async () => { // Why: verify the parallel fix to the default partition — isolated/imported // profiles must also defer media permission checks to macOS instead of