Files
orca/src/main/browser/browser-cookie-import.comet.test.ts
T
Brennan Benson c03c01ad15 fix(browser): stop impersonating the source browser's UA on cookie import (STA-3514) (#12849)
* fix(browser): stop impersonating the source browser's UA on cookie import (STA-3514)

Delete the import-time UA synthesis entirely instead of patching its version
detection. The session now always keeps the engine-derived UA the registry
sets at startup (clean or native), for imported and non-imported profiles
alike.

Why deletion is the fix:
- The synthesis read CFBundleShortVersionString, which on forks is a
  marketing version — Arc imports presented Chrome/1.x and sites rejected
  the browser as ancient (STA-3514).
- Its stated purpose (keep Google-bound cookies valid) is handled by
  isGoogleSourceBoundCookie excluding those cookies since #736, and #12884
  measured that Google rejects re-transplants regardless of UA identity.
- #12608 measured the synthesized Chrome-shaped UA reaching
  /v3/signin/rejected while the engine UA reached account lookup.

Persisted userAgent/userAgentByPartition meta is no longer read; legacy keys
drop off on the next meta write. The #12811 gate existed only to catch bad
synthesized values, so it leaves with the synthesis.

* test(browser): catch source UA impersonation regression
2026-08-09 17:05:00 -07:00

230 lines
7.9 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type * as fsModule from 'node:fs'
const { sessionFromPartitionMock, dialogShowOpenDialogMock } = vi.hoisted(() => ({
sessionFromPartitionMock: vi.fn(),
dialogShowOpenDialogMock: vi.fn()
}))
vi.mock('electron', () => ({
BrowserWindow: { fromWebContents: vi.fn() },
dialog: { showOpenDialog: dialogShowOpenDialogMock },
session: { fromPartition: sessionFromPartitionMock }
}))
import { BROWSER_FAMILY_LABELS } from '../../shared/constants'
function slashPath(pathValue: string): string {
return pathValue.replaceAll('\\', '/')
}
describe('detectInstalledBrowsers — Comet', () => {
const originalPlatform = process.platform
const originalHome = process.env.HOME
beforeEach(() => {
// Why: browser-cookie-import.ts uses destructured named imports from
// 'node:fs' which are bound at module-load time. Without vi.resetModules(),
// only the first test's vi.doMock takes effect — subsequent tests see the
// cached module with the first mock still applied. resetModules must run
// BEFORE each doMock so the next import() picks up the fresh mock factory.
vi.resetModules()
Object.defineProperty(process, 'platform', { value: 'darwin' })
process.env.HOME = '/Users/test'
})
afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform })
process.env.HOME = originalHome
vi.restoreAllMocks()
})
it('detects Comet when its data directory and Cookies DB exist', async () => {
vi.doMock('node:fs', async () => {
const actual = await vi.importActual<typeof fsModule>('node:fs')
return {
...actual,
existsSync: (p: string) => {
const normalizedPath = slashPath(p)
if (normalizedPath.includes('Comet/Default/Network/Cookies')) {
return true
}
if (normalizedPath.includes('Comet/Local State')) {
return true
}
return false
},
readFileSync: (p: string, enc?: string) => {
if (typeof p === 'string' && slashPath(p).includes('Comet/Local State')) {
return JSON.stringify({ profile: { info_cache: { Default: { name: 'Default' } } } })
}
return actual.readFileSync(p as never, enc as never)
}
}
})
const { detectInstalledBrowsers } = await import('./browser-cookie-import')
const detected = detectInstalledBrowsers()
const comet = detected.find((b) => b.family === 'comet')
expect(comet).toBeDefined()
expect(comet?.label).toBe('Comet')
expect(slashPath(comet?.cookiesPath ?? '')).toContain('Comet/Default/Network/Cookies')
expect(comet?.keychainService).toBe('Comet Safe Storage')
})
it('does not list Comet when its data directory is absent', async () => {
vi.doMock('node:fs', async () => {
const actual = await vi.importActual<typeof fsModule>('node:fs')
return {
...actual,
existsSync: () => false
}
})
const { detectInstalledBrowsers } = await import('./browser-cookie-import')
const detected = detectInstalledBrowsers()
expect(detected.find((b) => b.family === 'comet')).toBeUndefined()
})
it('enumerates all Comet profiles from Local State info_cache', async () => {
vi.doMock('node:fs', async () => {
const actual = await vi.importActual<typeof fsModule>('node:fs')
return {
...actual,
existsSync: (p: string) => {
const normalizedPath = slashPath(p)
if (normalizedPath.includes('Comet/Default/Network/Cookies')) {
return true
}
if (normalizedPath.includes('Comet/Local State')) {
return true
}
return false
},
readFileSync: (p: string, enc?: string) => {
if (typeof p === 'string' && slashPath(p).includes('Comet/Local State')) {
return JSON.stringify({
profile: {
info_cache: {
Default: { name: 'Personal' },
'Profile 1': { name: 'Work' },
'Profile 2': { name: 'Research' }
}
}
})
}
return actual.readFileSync(p as never, enc as never)
}
}
})
const { detectInstalledBrowsers } = await import('./browser-cookie-import')
const detected = detectInstalledBrowsers()
const comet = detected.find((b) => b.family === 'comet')
expect(comet).toBeDefined()
const directories = comet!.profiles.map((p) => p.directory).sort()
expect(directories).toEqual(['Default', 'Profile 1', 'Profile 2'])
const names = comet!.profiles.map((p) => p.name).sort()
expect(names).toEqual(['Personal', 'Research', 'Work'])
})
it('ignores Comet profile directories that escape the browser root', async () => {
vi.doMock('node:fs', async () => {
const actual = await vi.importActual<typeof fsModule>('node:fs')
return {
...actual,
existsSync: (p: string) => {
if (p.includes('Comet/Local State')) {
return true
}
if (p.includes('Application Support/Outside/Network/Cookies')) {
return true
}
return false
},
readFileSync: (p: string, enc?: string) => {
if (typeof p === 'string' && p.includes('Comet/Local State')) {
return JSON.stringify({
profile: {
info_cache: {
'../Outside': { name: 'Outside' }
}
}
})
}
return actual.readFileSync(p as never, enc as never)
}
}
})
const { detectInstalledBrowsers } = await import('./browser-cookie-import')
const detected = detectInstalledBrowsers()
expect(detected.find((b) => b.family === 'comet')).toBeUndefined()
})
it('rejects explicit Comet profile selections that escape the browser root', async () => {
vi.doMock('node:fs', async () => {
const actual = await vi.importActual<typeof fsModule>('node:fs')
return {
...actual,
existsSync: (p: string) => {
if (p.includes('Application Support/Outside/Network/Cookies')) {
return true
}
return false
}
}
})
const { selectBrowserProfile } = await import('./browser-cookie-import')
const selected = selectBrowserProfile(
{
family: 'comet',
label: 'Comet',
cookiesPath: '/Users/test/Library/Application Support/Comet/Default/Network/Cookies',
keychainService: 'Comet Safe Storage',
keychainAccount: 'Comet',
profiles: [{ name: 'Outside', directory: '../Outside' }],
selectedProfile: 'Default'
},
'../Outside'
)
expect(selected).toBeNull()
})
it('skips Comet when the data directory exists but no Cookies DB is present', async () => {
vi.doMock('node:fs', async () => {
const actual = await vi.importActual<typeof fsModule>('node:fs')
return {
...actual,
existsSync: (p: string) => {
if (p.includes('Comet/Local State')) {
return true
}
if (p.includes('Network/Cookies') || p.endsWith('/Cookies')) {
return false
}
return false
},
readFileSync: (p: string, enc?: string) => {
if (typeof p === 'string' && p.includes('Comet/Local State')) {
return JSON.stringify({ profile: { info_cache: { Default: { name: 'Default' } } } })
}
return actual.readFileSync(p as never, enc as never)
}
}
})
const { detectInstalledBrowsers } = await import('./browser-cookie-import')
const detected = detectInstalledBrowsers()
expect(detected.find((b) => b.family === 'comet')).toBeUndefined()
})
})
describe('BROWSER_FAMILY_LABELS — Comet', () => {
it('maps the comet family key to the user-facing label "Comet"', () => {
expect(BROWSER_FAMILY_LABELS.comet).toBe('Comet')
})
})