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
+131
View File
@@ -0,0 +1,131 @@
const fs = require('node:fs');
const cp = require('node:child_process');
const { createHash } = require('node:crypto');
const path = require('node:path');
module.exports = ({ observation, playwrightEntry, mode = 'normal-close', inspectCommandLine = false, observerExecutable, seedCookie = { name: 'synthetic', value: 'synthetic', domain: 'example.test', path: '/' } }) => {
if (inspectCommandLine && process.platform === 'win32' && typeof observerExecutable !== 'string') throw new Error('Native observer executable is required');
const originalSpawn = cp.spawn;
let inspected = Promise.resolve();
let folderEvidence;
const directoryState = (env, root) => ({
requestedProfile: fs.existsSync(root),
localEnvironment: fs.existsSync(env.LOCALAPPDATA || ''),
roamingEnvironment: fs.existsSync(env.APPDATA || ''),
localUnderProfile: fs.existsSync(path.join(env.USERPROFILE || '', 'AppData', 'Local')),
roamingUnderProfile: fs.existsSync(path.join(env.USERPROFILE || '', 'AppData', 'Roaming')),
});
const safeFolders = value => Object.fromEntries(['local', 'roaming'].map(name => [name,
Object.fromEntries(['verified', 'dontVerify'].map(kind => {
const item = value?.[name]?.[kind];
return [kind, {
hresult: Number.isInteger(item?.hresult) ? item.hresult : null,
pathHash: /^[a-f0-9]{64}$/.test(item?.pathHash) ? item.pathHash : null,
exists: typeof item?.exists === 'boolean' ? item.exists : null,
matchesEnvironment: typeof item?.matchesEnvironment === 'boolean' ? item.matchesEnvironment : null,
underUserProfile: typeof item?.underUserProfile === 'boolean' ? item.underUserProfile : null,
}];
})),
]));
const runProbe = (input, env) => new Promise(resolve => {
const probe = originalSpawn(observerExecutable, [
'--no-env-file', '--no-install', '--no-macros', '--config=NUL', path.join(__dirname, 'native-cookie-process-observer.ts'),
Buffer.from(JSON.stringify(input)).toString('base64'),
], { env, stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
let output = '';
let stderrBytes = 0;
let spawnFailed = false;
const timer = setTimeout(() => probe.kill(), 5_000);
probe.stdout.on('data', chunk => { output += chunk.toString('utf8'); if (output.length > 16384) probe.kill(); });
probe.stderr.on('data', chunk => { stderrBytes += chunk.length; });
probe.once('error', () => { spawnFailed = true; });
probe.once('close', code => {
clearTimeout(timer);
try { resolve({ measured: JSON.parse(output), code, stderrBytes }); }
catch { resolve({ measured: { available: false, reason: spawnFailed ? 'probe_spawn_failed' : 'probe_no_receipt' }, code, stderrBytes }); }
});
});
cp.spawn = function(command, args, options) {
if (args.some(arg => /^--(?:no-sandbox|disable-setuid-sandbox)(?:=|$)/.test(arg))) throw new Error('Native fixture refuses a sandbox-disabled browser');
const child = originalSpawn.call(this, command, args, options);
const evidence = {
command, args, pid: child.pid,
argsHash: createHash('sha256').update(JSON.stringify(args)).digest('hex'),
envHash: createHash('sha256').update(JSON.stringify(Object.entries(options.env || {}).sort(([a], [b]) => a.localeCompare(b)))).digest('hex'),
stderrBytes: 0,
reasons: [],
runtime: { node: process.version, bun: process.versions.bun || null, architecture: process.arch },
folderEvidence,
};
const publish = () => fs.writeFileSync(observation, JSON.stringify(evidence));
publish();
let tail = '';
child.stderr?.on('data', chunk => {
evidence.stderrBytes += chunk.length;
if (evidence.stderrBytes > 65536) return;
const text = tail + chunk.toString('utf8');
const patterns = {
job_assignment_failed: /AssignProcessToJobObject|failed to (?:assign|create).*job object/i,
sandbox_failed: /sandbox.*(?:failed|error)|SBOX_FATAL/i,
profile_locked: /ProcessSingleton|profile.*in use/i,
default_profile_policy: /remote debugging requires a non-default data directory/i,
permission_denied: /access is denied|ERROR_ACCESS_DENIED|permission denied/i,
crashpad_failed: /crashpad.*(?:failed|error)/i,
missing_dependency: /specified module could not be found|0xc0000135/i,
};
for (const [reason, pattern] of Object.entries(patterns)) {
if (pattern.test(text) && !evidence.reasons.includes(reason)) evidence.reasons.push(reason);
}
tail = text.slice(-512);
publish();
});
child.once('exit', (code, signal) => { evidence.exitCode = code; evidence.signal = signal; publish(); });
child.once('error', error => {
evidence.spawnError = ['ENOENT', 'EACCES', 'EPERM', 'EINVAL'].includes(error.code) ? error.code : 'spawn_failed';
publish();
});
if (inspectCommandLine && process.platform === 'win32' && Number.isInteger(child.pid)) {
inspected = runProbe({ pid: child.pid, owner: process.pid, image: command }, options.env).then(({ measured, code, stderrBytes }) => {
const expectedHashes = args.map(arg => createHash('sha256').update(arg).digest('hex'));
const dataDir = args.find(arg => arg.startsWith('--user-data-dir='))?.slice(16);
evidence.observedCommandLine = {
available: measured.available === true,
parentMatched: measured.parentMatched === true,
imageMatched: measured.imageMatched === true,
reason: ['not_windows', 'invalid_input', 'process_open', 'process_identity', 'owned_process_unavailable', 'command_line', 'command_line_length', 'command_line_bounds', 'argument_parse', 'argument_bounds', 'job_query', 'observer_initialize', 'probe_spawn_failed', 'probe_no_receipt'].includes(measured.reason) ? measured.reason : undefined,
win32Error: Number.isInteger(measured.win32Error) ? measured.win32Error : undefined,
ntStatus: Number.isInteger(measured.ntStatus) ? measured.ntStatus : undefined,
commandLineHash: /^[a-f0-9]{64}$/.test(measured.commandLineHash) ? measured.commandLineHash : undefined,
argumentsMatchRequested: measured.available === true && JSON.stringify(measured.argumentHashes) === JSON.stringify(expectedHashes),
userDataDirCount: Number.isInteger(measured.userDataDirCount) && measured.userDataDirCount >= 0 && measured.userDataDirCount <= 128 ? measured.userDataDirCount : undefined,
userDataDirMatchesRequested: typeof dataDir === 'string' && measured.userDataDirHash === createHash('sha256').update(dataDir).digest('hex'),
pipePresent: measured.pipePresent === true,
browserInJob: typeof measured.browserInJob === 'boolean' ? measured.browserInJob : undefined,
observerJobLimitFlags: Number.isInteger(measured.observerJobLimitFlags) ? measured.observerJobLimitFlags : undefined,
observerJobQueryError: Number.isInteger(measured.observerJobQueryError) ? measured.observerJobQueryError : undefined,
exitCode: code, stderrBytes,
};
evidence.folderEvidence.afterLaunch = safeFolders(measured.knownFolders);
evidence.folderEvidence.directoriesAfterLaunch = directoryState(options.env, args.find(arg => arg.startsWith('--user-data-dir='))?.slice(16) || '');
publish();
});
}
return child;
};
const { chromium } = require(playwrightEntry);
return { chromium: { async launchPersistentContext(root, options) {
if (options.chromiumSandbox !== true) throw new Error('Native fixture requires the browser sandbox');
const started = Date.now();
if (inspectCommandLine && process.platform === 'win32') {
const directoriesBefore = directoryState(options.env, root);
const before = await runProbe({ mode: 'known-folders' }, options.env);
folderEvidence = { beforeLaunch: safeFolders(before.measured.knownFolders), directoriesBefore, directoriesAfterProbe: directoryState(options.env, root) };
}
const context = await chromium.launchPersistentContext(root, inspectCommandLine && process.platform === 'win32'
? { ...options, timeout: Math.max(1, options.timeout - (Date.now() - started)) } : options);
await inspected;
await context.addCookies([seedCookie]);
if (mode === 'stalled-close') context.close = () => { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0); };
return context;
} } };
};