From 998b9056b8553f077c19d111f299610ba471fa53 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 30 May 2026 05:04:27 -0700 Subject: [PATCH] fix: decode large safari cookie pages --- .../browser-cookie-import-safari.test.ts | 62 +++++++++++++++++++ src/main/browser/browser-cookie-import.ts | 8 ++- 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/main/browser/browser-cookie-import-safari.test.ts diff --git a/src/main/browser/browser-cookie-import-safari.test.ts b/src/main/browser/browser-cookie-import-safari.test.ts new file mode 100644 index 00000000000..9e3aded5f37 --- /dev/null +++ b/src/main/browser/browser-cookie-import-safari.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest' +import { decodeSafariBinaryCookies } from './browser-cookie-import' + +function buildSafariCookieRecord(index: number): Buffer { + const domain = '.example.com' + const name = `cookie-${index}` + const path = '/' + const value = 'value' + const strings = [domain, name, path, value] + const stringBytes = strings.map((entry) => Buffer.byteLength(entry) + 1) + const size = 48 + stringBytes.reduce((sum, length) => sum + length, 0) + const record = Buffer.alloc(size) + record.writeUInt32LE(size, 0) + record.writeUInt32LE(0, 8) + let cursor = 48 + const offsets = strings.map((entry, stringIndex) => { + const offset = cursor + record.write(entry, offset, 'utf8') + cursor += stringBytes[stringIndex] + return offset + }) + record.writeUInt32LE(offsets[0], 16) + record.writeUInt32LE(offsets[1], 20) + record.writeUInt32LE(offsets[2], 24) + record.writeUInt32LE(offsets[3], 28) + record.writeDoubleLE(0, 40) + return record +} + +function buildSafariBinaryCookies(count: number): Buffer { + const records = Array.from({ length: count }, (_, index) => buildSafariCookieRecord(index)) + const totalRecordBytes = records.reduce((sum, record) => sum + record.length, 0) + const pageHeaderBytes = 8 + count * 4 + const page = Buffer.alloc(pageHeaderBytes + totalRecordBytes) + page.writeUInt32BE(0x00000100, 0) + page.writeUInt32LE(count, 4) + + let recordOffset = pageHeaderBytes + for (let index = 0; index < records.length; index += 1) { + const record = records[index] + page.writeUInt32LE(recordOffset, 8 + index * 4) + record.copy(page, recordOffset) + recordOffset += record.length + } + + const file = Buffer.alloc(12 + page.length) + file.write('cook', 0, 'utf8') + file.writeUInt32BE(1, 4) + file.writeUInt32BE(page.length, 8) + page.copy(file, 12) + return file +} + +describe('decodeSafariBinaryCookies', () => { + it('decodes Safari binary cookie pages with very large cookie counts', () => { + const cookies = decodeSafariBinaryCookies(buildSafariBinaryCookies(125_000)) + + expect(cookies).toHaveLength(125_000) + expect(cookies[0]?.name).toBe('cookie-0') + expect(cookies.at(-1)?.name).toBe('cookie-124999') + }) +}) diff --git a/src/main/browser/browser-cookie-import.ts b/src/main/browser/browser-cookie-import.ts index 6ab200c423e..52ca6905c47 100644 --- a/src/main/browser/browser-cookie-import.ts +++ b/src/main/browser/browser-cookie-import.ts @@ -1096,7 +1096,7 @@ function decryptAes256Gcm(payload: Buffer, key: Buffer): Buffer | null { // Safari binary cookie parser // --------------------------------------------------------------------------- -function decodeSafariBinaryCookies(buffer: Buffer): ValidatedCookie[] { +export function decodeSafariBinaryCookies(buffer: Buffer): ValidatedCookie[] { if (buffer.length < 8) { return [] } @@ -1119,7 +1119,11 @@ function decodeSafariBinaryCookies(buffer: Buffer): ValidatedCookie[] { for (const pageSize of pageSizes) { const page = buffer.subarray(cursor, cursor + pageSize) cursor += pageSize - cookies.push(...decodeSafariPage(page)) + // Why: a single Safari cookie page can contain enough entries to exceed + // V8's argument limit if appended via spread. + for (const cookie of decodeSafariPage(page)) { + cookies.push(cookie) + } } return cookies }