v1.90.0.0 feat: make browser cookie imports explicit and safe (#2964)

* fix(browse): prepare reliable cookie import wave for validation

* ci: sequence quality and behavior for validation branch

* fix(browse): isolate Windows qualification and preserve native diagnostics

* test(browse): cover cookie workflow quality and isolate Windows user paths

* test(browse): trace native member startup and initialize fresh folders

* fix(browse): keep Windows member stdin alive through EOF

* fix(browse): latch native timeouts and compare contained Edge startup

* test(browse): verify native version metadata and actual Windows argv

* test(browse): qualify Dia import on isolated macOS CI

* fix(browse): require picker origin for session mutations

* fix(browse): bound credential reads through stream completion

* test(browse): inspect owned Windows process arguments natively

* test(evals): preserve passing coverage during cookie repair reruns

* test(browse): isolate Dia qualification in a fresh macOS account

* test(browse): pass bounded integer timeouts to native Mac probes

* test(browse): distinguish Windows profile initialization from containment

* test(browse): await descendant pipe readiness before parent exit

* test(browse): initialize and restore isolated macOS Keychain state

* test(browse): initialize Windows fixture folders before qualification

* test(ci): pin the same Node runtime across Windows checks

* test(browse): distinguish native macOS browser preflight stages

* test(browse): isolate Windows descendant console lifetime

* test(browse): preserve native receipts and identify fixture lock holders

* test(browse): prepare dependency resolution before native Mac worker startup

* test(ci): include lock and close checks in native diagnostics

* test(browse): preserve native owner probe stages and subprocess deadlines

* fix(browse): classify Chromium profile-in-use exit precisely

* test(browse): retain Mac qualification evidence through cleanup failures

* test(browse): bound Mac fixture paths and retire its owned user domain

* test(browse): accept vanished fixture entries without weakening cleanup

* test(browse): identify probe-created macOS user domains safely

* test(browse): observe Mac user domains without targeting them first

* test(browse): use passive fresh-user ownership throughout Mac qualification

* test(browse): distinguish profile and registered-home Keychain lookups

* test(browse): qualify Dia under one registered account home

* test(browse): identify Dia startup and owned process-group failures

* test(browse): classify bounded Dia startup diagnostics without leaking output

* fix(test): preserve native Mac sandboxing and reap owned browser children

* fix(browse): preserve Chromium sandboxing for native profile imports

* test(browse): inspect signed Mach-O architecture without launching Xcode tools

* test(browse): sample pending Dia startup and reap on all cleanup paths

* test(browse): compare protected Dia launches in fresh Bun and Node accounts

* test(browse): inspect isolated Mac GUI readiness without browser access

* v1.90.0.0 fix: bind cookie picker actions to their document

* test: validate cookie guards and fit nested launch fixtures

* ci: configure the bundled Chromium sandbox helper

* fix(browse): classify Playwright authentication timeouts

* test: retain bounded Windows lifecycle diagnostics

* test(cso): reuse bounded NTFS precision candidates

* test(review): handle explicit preservation choices safely

* test(browse): remove owned fixture directories with explicit primitives

* test(review): distinguish descriptive reuse from edit commitments

* test: admit only the approved unscored cookie workflow refusal

* test: keep the Office Hours judge mock export-complete

* fix: keep dependency-free CI planners independent of the model SDK

* test: observe the exact holder after a native fixture unlink failure

* fix: start seeded PTY observations at owned readiness

* test: acquire identity-bound Windows deletion admission before profile resets

* test: preserve qualified Git index bits without authorizing mutations
This commit is contained in:
Garry Tan
2026-09-25 12:06:45 -04:00
committed by GitHub
parent 730a1017d1
commit a84b0b5b6d
111 changed files with 14996 additions and 1057 deletions
+277
View File
@@ -0,0 +1,277 @@
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
import { Database } from 'bun:sqlite';
import { spawnSync } from 'node:child_process';
import { chmodSync, existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { openCookieDatabase } from '../src/cookie-database';
const root = realpathSync(mkdtempSync(path.join(tmpdir(), 'cookie-db-')));
const dbPath = path.join(root, 'Cookies');
const adapterPath = path.join(root, 'cookie-database.mjs');
const corruptPath = path.join(root, 'private-corrupt-fixture');
const missingPath = path.join(root, 'private-missing-fixture');
const node = Bun.which('node');
if (!node) throw new Error('Node.js is required for cookie database adapter tests');
beforeAll(() => {
const database = new Database(dbPath);
database.run('CREATE TABLE cookies (host_key TEXT, name TEXT, encrypted_value BLOB, expires_utc INTEGER, has_expires INTEGER)');
const insert = database.query('INSERT INTO cookies VALUES (?, ?, ?, ?, ?)');
insert.run('.fixture.test', 'large-expiry', new Uint8Array([0, 127, 255]), 13300000000000001n, 1);
insert.run('.fixture.test', 'session', new Uint8Array([]), 0, 0);
insert.run('.fixture.test', 'expired', new Uint8Array([]), 500, 1);
insert.run('.other.test', 'other-session', new Uint8Array([]), 0, 0);
database.close();
chmodSync(dbPath, 0o600);
writeFileSync(corruptPath, 'private-fixture-not-a-database', { mode: 0o600 });
const source = readFileSync(path.resolve(import.meta.dir, '../src/cookie-database.ts'), 'utf8');
writeFileSync(adapterPath, new Bun.Transpiler({ loader: 'ts', target: 'node' }).transformSync(source), { mode: 0o600 });
});
afterAll(() => rmSync(root, { recursive: true, force: true }));
function runNode(source: string, flags: string[] = []) {
const result = spawnSync(node!, ['--input-type=module', ...flags, '-e', source, pathToFileURL(adapterPath).href, dbPath, corruptPath, missingPath], {
encoding: 'utf8', timeout: 10_000, env: {
PATH: path.dirname(node!), HOME: root, USERPROFILE: root, TEMP: root, TMP: root,
NODE_NO_WARNINGS: '1', ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
},
});
expect(result.error).toBeUndefined();
expect(result.status).toBe(0);
expect(result.stderr).toBe('');
return JSON.parse(result.stdout);
}
describe('cookie database runtime adapter', () => {
test('reproduces the old Node null stub before cookie queries can run', () => {
const result = runNode(`
import { existsSync } from 'node:fs';
const Database = null;
let attemptedQuery = false;
try {
const database = new Database(process.argv[2], { readonly: true });
attemptedQuery = true;
database.query('SELECT host_key FROM cookies').all();
} catch (error) {
console.log(JSON.stringify({ fixtureExists: existsSync(process.argv[2]), error: error.name, attemptedQuery }));
}
`);
expect(result).toEqual({ fixtureExists: true, error: 'TypeError', attemptedQuery: false });
});
test('Bun returns JSON-safe domain counts and preserves precise expiry integers and blobs', () => {
const database = openCookieDatabase(dbPath);
try {
const domains = database.query('SELECT host_key AS domain, COUNT(*) AS count FROM cookies WHERE has_expires = 0 OR expires_utc > ? GROUP BY host_key ORDER BY count DESC').all(1000);
expect(domains).toEqual([{ domain: '.fixture.test', count: 2 }, { domain: '.other.test', count: 1 }]);
expect(JSON.parse(JSON.stringify(domains))).toEqual(domains);
const rows = database.query('SELECT name, encrypted_value, expires_utc, has_expires FROM cookies WHERE host_key IN (?, ?) AND expires_utc = ?').all('fixture.test', '.fixture.test', 13300000000000001n) as any[];
expect(rows).toHaveLength(1);
expect(rows[0].expires_utc).toBe(13300000000000001n);
expect(rows[0].has_expires).toBe(1);
expect(Array.from(rows[0].encrypted_value)).toEqual([0, 127, 255]);
} finally {
database.close();
}
});
test('Node uses built-in SQLite even with a Bun server polyfill and preserves the same values', () => {
const result = runNode(`
globalThis.Bun = { fixturePolyfill: true };
const { openCookieDatabase } = await import(process.argv[1]);
const database = openCookieDatabase(process.argv[2]);
try {
const domains = database.query('SELECT host_key AS domain, COUNT(*) AS count FROM cookies WHERE has_expires = 0 OR expires_utc > ? GROUP BY host_key ORDER BY count DESC').all(1000);
const rows = database.query('SELECT name, encrypted_value, expires_utc, has_expires FROM cookies WHERE host_key IN (?, ?) AND expires_utc = ?').all('fixture.test', '.fixture.test', 13300000000000001n);
console.log(JSON.stringify({ domains, rowCount: rows.length, expiry: String(rows[0].expires_utc), expiryType: typeof rows[0].expires_utc,
flag: rows[0].has_expires, blob: Array.from(rows[0].encrypted_value) }));
} finally { database.close(); }
`);
expect(result).toEqual({ domains: [{ domain: '.fixture.test', count: 2 }, { domain: '.other.test', count: 1 }],
rowCount: 1, expiry: '13300000000000001', expiryType: 'bigint', flag: 1, blob: [0, 127, 255] });
});
for (const runtime of ['Bun', 'Node']) {
test(`${runtime} converts only integers inside the safe Number range`, () => {
const sql = 'SELECT 9007199254740991 AS safe_positive, -9007199254740991 AS safe_negative, 9007199254740992 AS unsafe_positive, -9007199254740992 AS unsafe_negative, 9223372036854775807 AS maximum, 1.5 AS fractional, NULL AS empty';
let row: any;
if (runtime === 'Bun') {
const database = openCookieDatabase(dbPath);
try {
row = database.query(sql).all()[0];
row = Object.fromEntries(Object.entries(row).map(([key, value]) => [key, { type: typeof value, value: String(value) }]));
} finally { database.close(); }
} else {
row = runNode(`
const { openCookieDatabase } = await import(process.argv[1]);
const database = openCookieDatabase(process.argv[2]);
try {
const row = database.query(${JSON.stringify(sql)}).all()[0];
console.log(JSON.stringify(Object.fromEntries(Object.entries(row).map(([key, value]) => [key, { type: typeof value, value: String(value) }]))));
} finally { database.close(); }
`);
}
expect(row).toEqual({ safe_positive: { type: 'number', value: '9007199254740991' }, safe_negative: { type: 'number', value: '-9007199254740991' },
unsafe_positive: { type: 'bigint', value: '9007199254740992' }, unsafe_negative: { type: 'bigint', value: '-9007199254740992' },
maximum: { type: 'bigint', value: '9223372036854775807' }, fractional: { type: 'number', value: '1.5' }, empty: { type: 'object', value: 'null' } });
});
test(`${runtime} refuses database writes and leaves the source bytes unchanged`, () => {
const before = readFileSync(dbPath);
const writes = ["INSERT INTO cookies VALUES ('private-insert', 'attempt', NULL, 0, 0)",
"UPDATE cookies SET name = 'private-update'", 'DELETE FROM cookies', 'CREATE TABLE private_created_table (value TEXT)'];
let results: any[];
if (runtime === 'Bun') {
const database = openCookieDatabase(dbPath);
try {
results = writes.map(sql => {
try { database.query(sql).all(); return { wrote: true }; }
catch (error: any) { return { code: error.code, message: error.message }; }
});
} finally { database.close(); }
} else {
results = runNode(`
const { openCookieDatabase } = await import(process.argv[1]);
const database = openCookieDatabase(process.argv[2]);
try {
const results = ${JSON.stringify(writes)}.map(sql => {
try { database.query(sql).all(); return { wrote: true }; }
catch (error) { return { code: error.code, message: error.message }; }
});
console.log(JSON.stringify(results));
} finally { database.close(); }
`);
}
expect(results).toHaveLength(4);
for (const result of results) {
expect(result.code).toBe('SQLITE_READONLY');
expect(result.message).not.toContain('private-');
expect(result.message).not.toContain('private_created_table');
}
expect(readFileSync(dbPath)).toEqual(before);
});
test(`${runtime} keeps parameters bound instead of interpreting SQL-looking values`, () => {
const value = ".fixture.test'; DROP TABLE cookies; --";
let rows: unknown[];
if (runtime === 'Bun') {
const database = openCookieDatabase(dbPath);
try {
rows = database.query('SELECT name FROM cookies WHERE host_key = ?').all(value);
expect(database.query('SELECT COUNT(*) AS count FROM cookies').all()).toEqual([{ count: 4 }]);
} finally { database.close(); }
} else {
const result = runNode(`
const { openCookieDatabase } = await import(process.argv[1]);
const database = openCookieDatabase(process.argv[2]);
try {
const rows = database.query('SELECT name FROM cookies WHERE host_key = ?').all(${JSON.stringify(value)});
console.log(JSON.stringify({ rows, counts: database.query('SELECT COUNT(*) AS count FROM cookies').all() }));
} finally { database.close(); }
`);
rows = result.rows;
expect(result.counts).toEqual([{ count: 4 }]);
}
expect(rows).toEqual([]);
});
test(`${runtime} supports a mutable close method for copied-profile cleanup`, () => {
if (runtime === 'Bun') {
const database = openCookieDatabase(dbPath);
const close = database.close.bind(database);
let cleanup = false;
database.close = () => { close(); cleanup = true; };
database.close();
expect(cleanup).toBe(true);
expect(() => database.query('SELECT 1').all()).toThrow();
} else {
expect(runNode(`
const { openCookieDatabase } = await import(process.argv[1]);
const database = openCookieDatabase(process.argv[2]);
const close = database.close.bind(database);
let cleanup = false;
database.close = () => { close(); cleanup = true; };
database.close();
let closed = false;
try { database.query('SELECT 1').all(); } catch { closed = true; }
console.log(JSON.stringify({ cleanup, closed }));
`)).toEqual({ cleanup: true, closed: true });
}
});
test(`${runtime} sanitizes open, corrupt-database, query, and binding failures`, () => {
const exercise = (open: typeof openCookieDatabase, missing: string, corrupt: string, valid: string) => {
const results: { code: string; message: string }[] = [];
for (const file of [missing, corrupt]) {
let database: ReturnType<typeof openCookieDatabase> | undefined;
try { database = open(file); database.query('SELECT * FROM cookies').all(); }
catch (error: any) { results.push({ code: error.code, message: error.message }); }
finally { database?.close(); }
}
const database = open(valid);
try {
try { database.query('SELECT private_query_sentinel FROM cookies').all(); }
catch (error: any) { results.push({ code: error.code, message: error.message }); }
try { database.query('SELECT ? AS value').all(Symbol('private-binding-sentinel')); }
catch (error: any) { results.push({ code: error.code, message: error.message }); }
} finally { database.close(); }
return results;
};
const results = runtime === 'Bun' ? exercise(openCookieDatabase, missingPath, corruptPath, dbPath) : runNode(`
const { openCookieDatabase } = await import(process.argv[1]);
const exercise = ${exercise.toString()};
console.log(JSON.stringify(exercise(openCookieDatabase, process.argv[4], process.argv[3], process.argv[2])));
`);
expect(results.map((result: any) => result.code)).toEqual(['SQLITE_CANTOPEN', 'SQLITE_CORRUPT', 'SQLITE_ERROR', 'SQLITE_ERROR']);
expect(JSON.stringify(results)).not.toMatch(/private[-_]|Cookies|cookie-db-/);
expect(existsSync(missingPath)).toBe(false);
});
}
for (const failure of ['missing module', 'missing export']) {
test(`Node import remains available without SQLite and cookie use fails safely (${failure})`, () => {
const result = runNode(`
import Module from 'node:module';
const original = Module._load;
let loads = 0;
Module._load = function(name, ...args) {
if (name === 'node:sqlite') {
loads++;
if (${JSON.stringify(failure)} === 'missing export') return {};
throw new Error('private-module-error');
}
return original.call(this, name, ...args);
};
try {
const { openCookieDatabase } = await import(process.argv[1]);
const loadsAtImport = loads;
try { openCookieDatabase(process.argv[2]); }
catch (error) { console.log(JSON.stringify({ loadsAtImport, loads, code: error.code, message: error.message })); }
} finally { Module._load = original; }
`);
expect(result.loadsAtImport).toBe(0);
expect(result.loads).toBe(1);
expect(result.code).toBe('sqlite_unavailable');
expect(result.message).toContain('Node.js 22.13 or newer');
expect(result.message).toContain('Upgrade Node.js or enable SQLite');
expect(result.message).not.toContain('private-module-error');
expect(result.message).not.toContain(dbPath);
});
}
test('actual Node with SQLite disabled can import the module and gets version guidance only on cookie use', () => {
const result = runNode(`
const { openCookieDatabase } = await import(process.argv[1]);
try { openCookieDatabase(process.argv[2]); }
catch (error) { console.log(JSON.stringify({ imported: true, code: error.code, message: error.message })); }
`, ['--no-experimental-sqlite']);
expect(result.imported).toBe(true);
expect(result.code).toBe('sqlite_unavailable');
expect(result.message).toContain('Node.js 22.13 or newer');
expect(result.message).toContain('Upgrade Node.js or enable SQLite');
expect(result.message).not.toContain(dbPath);
});
});