mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-28 15:41:57 +02:00
* 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
181 lines
13 KiB
TypeScript
181 lines
13 KiB
TypeScript
import { afterAll, describe, expect, test } from 'bun:test';
|
|
import { createHash } from 'node:crypto';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { compareDiaLaunchReceipts, normalizedLaunchHashes } from '../../.github/scripts/dia-launch-driver.mjs';
|
|
import { runDiaLaunchComparison, safeDiaComparisonResponse } from '../../.github/scripts/qualify-dia-macos';
|
|
|
|
const root = realpathSync(mkdtempSync(path.join(tmpdir(), 'dc-')));
|
|
const driverFile = path.resolve(import.meta.dir, '../../.github/scripts/dia-launch-driver.mjs');
|
|
afterAll(() => rmSync(root, { recursive: true, force: true }));
|
|
|
|
describe('diagnostic-only Dia runtime comparison', () => {
|
|
test('normalization replaces only declared owned path boundaries', () => {
|
|
const first = normalizedLaunchHashes(['--user-data-dir=/owned/a/home/profile', '--headless'], { HOME: '/owned/a/home', PATH: '/usr/bin:/bin' }, { home: '/owned/a/home' });
|
|
const second = normalizedLaunchHashes(['--user-data-dir=/owned/b/home/profile', '--headless'], { HOME: '/owned/b/home', PATH: '/usr/bin:/bin' }, { home: '/owned/b/home' });
|
|
expect(first).toEqual(second);
|
|
expect(normalizedLaunchHashes(['--user-data-dir=/owned/a/home-other'], {}, { home: '/owned/a/home' }).argvSha256)
|
|
.not.toBe(normalizedLaunchHashes(['--user-data-dir=/owned/b/home-other'], {}, { home: '/owned/b/home' }).argvSha256);
|
|
expect(normalizedLaunchHashes(['--label=https://private.invalid/owned/a/home'], {}, { home: '/owned/a/home' }).argvSha256)
|
|
.not.toBe(normalizedLaunchHashes(['--label=https://private.invalid/owned/b/home'], {}, { home: '/owned/b/home' }).argvSha256);
|
|
expect(JSON.stringify(first)).not.toContain('/owned');
|
|
});
|
|
|
|
test('IPC rejects raw payloads at any nesting depth', () => {
|
|
for (const value of [{ args: ['private-argv'] }, { cleanup: { raw: 'private-value' } }, { error: 'private-error-value' },
|
|
{ startupPages: { categories: ['https://private.invalid'] } }, { driver: { release: '/private/path' } }]) {
|
|
expect(safeDiaComparisonResponse(value)).toBe(false);
|
|
}
|
|
expect(safeDiaComparisonResponse({ protocol: 1, purpose: 'source', error: 'operation_timeout', cleanup: { confirmed: false } })).toBe(true);
|
|
});
|
|
|
|
test('the actual driver entry refuses an unapproved host without exposing its request', () => {
|
|
for (const executable of [process.execPath, Bun.which('node')!]) {
|
|
const result = spawnSync(executable, [driverFile, '/private-untrusted/account.json'], {
|
|
env: { PATH: '/usr/bin:/bin', HOME: root }, input: '{"private":"request-value"}', encoding: 'utf8', timeout: 10_000,
|
|
});
|
|
expect(result.status).toBe(2);
|
|
expect(result.stderr).toBe('');
|
|
expect(JSON.parse(result.stdout)).toEqual({ protocol: 1, ready: false, error: 'driver_admission_failed', cleanup: { confirmed: false } });
|
|
}
|
|
});
|
|
|
|
test('the real bounded stdin callback works under both runtimes and refuses raw request payloads', () => {
|
|
const entry = path.join(root, 'stdin-probe.mjs');
|
|
writeFileSync(entry, `import { readComparisonRequest } from ${JSON.stringify(pathToFileURL(driverFile).href)};
|
|
try { const request = readComparisonRequest(); process.stdout.write(JSON.stringify({purpose:request.purpose})); }
|
|
catch { process.stdout.write(JSON.stringify({rejected:true})); }`);
|
|
for (const executable of [process.execPath, Bun.which('node')!]) {
|
|
for (const input of ['{"purpose":"control"}', '{"purpose":"control","env":{"PRIVATE":"value"}}', 'x'.repeat(16 * 1024 + 1)]) {
|
|
const result = spawnSync(executable, [entry], { env: { HOME: root, PATH: '/usr/bin:/bin' }, input, encoding: 'utf8', timeout: 5000 });
|
|
expect(result.status).toBe(0);
|
|
expect(result.stderr).toBe('');
|
|
expect(JSON.parse(result.stdout)).toEqual(input === '{"purpose":"control"}' ? { purpose: 'control' } : { rejected: true });
|
|
}
|
|
}
|
|
});
|
|
test('both pinned runtimes execute the same protected worker with matched normalized inputs', async () => {
|
|
const node = Bun.which('node');
|
|
if (!node) throw new Error('Node 24.18 is required for the comparison regression');
|
|
const entry = path.join(root, 'probe.mjs');
|
|
const executablePath = realpathSync((await import('playwright')).chromium.executablePath());
|
|
writeFileSync(entry, `import { runProtectedLaunch } from ${JSON.stringify(pathToFileURL(driverFile).href)};
|
|
const home = process.env.HOME;
|
|
const result = await runProtectedLaunch(${JSON.stringify(executablePath)}, home + '/profile',
|
|
{ HOME: home, TMPDIR: home + '/tmp', PATH: '/usr/bin:/bin:/usr/sbin:/sbin', LANG: 'en_US.UTF-8' }, 'control', { home });
|
|
process.stdout.write(JSON.stringify(result) + '\\n');`);
|
|
const results: any[] = [];
|
|
for (const [name, executable] of [['bun', process.execPath], ['node', node]]) {
|
|
const home = path.join(root, name);
|
|
mkdirSync(home);
|
|
mkdirSync(path.join(home, 'tmp'));
|
|
const result = spawnSync(executable, [entry], { env: { HOME: home, PATH: '/usr/bin:/bin:/usr/sbin:/sbin' },
|
|
encoding: 'utf8', timeout: 40_000, killSignal: 'SIGKILL', maxBuffer: 64 * 1024 });
|
|
expect(result.status).toBe(0);
|
|
expect(result.stderr).toBe('');
|
|
const observed = JSON.parse(result.stdout);
|
|
expect(observed.ready).toBe(true);
|
|
expect(observed.protocolResponded).toBe(true);
|
|
expect(observed.cleanup.confirmed).toBe(true);
|
|
expect(observed.cleanup.childClosed).toBe(true);
|
|
expect(observed.cleanup.groupAbsent).toBe(true);
|
|
expect(observed.cleanup.launchSettled).toBe(true);
|
|
expect(observed.rootCount).toBe(1);
|
|
expect(observed.launchAttempts[0].pipeFlag).toBe(true);
|
|
expect(observed.launchAttempts[0].sandboxDisablingFlag).toBe(false);
|
|
expect(observed.samplingEnabled).toBe(false);
|
|
expect(safeDiaComparisonResponse(observed)).toBe(true);
|
|
expect(JSON.stringify(observed)).not.toContain(home);
|
|
results.push(observed);
|
|
}
|
|
expect(results[0].argvSha256).toBe(results[1].argvSha256);
|
|
expect(results[0].environmentSha256).toBe(results[1].environmentSha256);
|
|
}, 90_000);
|
|
|
|
const arm = (runtime: 'bun' | 'node', ready: boolean) => {
|
|
const sha = 'a'.repeat(64);
|
|
const config = { mode: 'launch-only', runtime, qualificationCredit: false, executableSha256: runtime === 'bun' ? sha : 'b'.repeat(64), driverSha256: sha, helpersSha256: sha };
|
|
const driver = { runtime, version: runtime === 'bun' ? '1.4.0' : '24.18.0', architecture: 'arm64', os: 'darwin', release: '24.6.0', playwright: '1.62.1',
|
|
executableSha256: config.executableSha256, driverSha256: sha, helpersSha256: sha };
|
|
const result = (purpose: string, success: boolean) => ({ protocol: 1, purpose, ready: success, launchReturned: success, protocolResponded: success,
|
|
samplingEnabled: false, rootCount: 1, supervisor: { closed: true, exitCode: 0 }, cleanup: { confirmed: true, childClosed: true, groupAbsent: true, launchSettled: true,
|
|
groups: [{ absenceConfirmed: true, childCloseObserved: true }] }, startupPages: { allowed: true }, postProbePages: { allowed: true },
|
|
argvSha256: sha, environmentSha256: sha, driver, launchAttempts: [{ sandboxRequired: true, sandboxDisablingFlag: false, pipeFlag: true,
|
|
tcpDebuggingFlag: false, mockKeychainFlag: false, passwordStoreFlag: false, firstRunSuppressed: false, headlessFlag: true,
|
|
expectedProfile: true, detached: true, shellDisabled: true, stdioCount: 5, extraPipeDescriptors: true, profileArgumentCount: 1 }] });
|
|
return { launchComparison: config, counts: { pass: 0, fail: 0, skip: 0 }, launcher: { accountGuid: runtime + '-separate-account', sourceRevision: 'c'.repeat(40), archiveSha256: sha, destinationSha256: sha },
|
|
launcherCleanup: { serviceStopped: true, userDomainStopped: true, userProcessesStopped: true, accountRemoved: true, groupRemoved: true, stagingRemoved: true },
|
|
backgroundPreflight: { status: 'passed', comparisonControl: result('control', true) },
|
|
qualification: { launchComparison: { qualificationCredit: false, source: result('source', ready) }, counts: { pass: 0, fail: 0, skip: 0 },
|
|
keychainStage: 'completed', isolation: { registeredIdentity: true, sharedRegisteredHome: true },
|
|
cleanup: { ownedBrowsersStopped: true, sourceProfileRemoved: true, keychainRestored: true, mountDetached: true, fixtureRemoved: true },
|
|
platform: { os: 'darwin', architecture: 'arm64', bun: '1.4.0', playwright: '1.62.1', release: '24.6.0' },
|
|
artifact: { signatureVerified: true, gatekeeperNotarized: true, macosCompatibility: { compatible: true, hostVersion: '15.7.9' },
|
|
architectures: ['arm64'], sha256: sha, executableSha256: sha, version: '1.49.1', bundleId: 'company.thebrowser.dia', team: 'S6N382Y83G' } } };
|
|
};
|
|
|
|
test('paired receipts distinguish runtime readiness without awarding qualification credit', () => {
|
|
expect(compareDiaLaunchReceipts(arm('bun', false), arm('node', true))).toEqual({ comparable: true, qualificationCredit: false, outcome: 'node_only_ready' });
|
|
expect(compareDiaLaunchReceipts(arm('bun', false), arm('node', false)).outcome).toBe('neither_ready');
|
|
expect(compareDiaLaunchReceipts(arm('bun', true), arm('node', true)).outcome).toBe('both_ready');
|
|
});
|
|
|
|
test('incomplete or incompatible arms cannot support a runtime conclusion', () => {
|
|
const left = arm('bun', false);
|
|
const mutations = [
|
|
(right: any) => { right.backgroundPreflight.comparisonControl.ready = false; },
|
|
(right: any) => { right.launcherCleanup.userDomainStopped = false; },
|
|
(right: any) => { right.qualification.cleanup.sourceProfileRemoved = false; },
|
|
(right: any) => { right.qualification.artifact.executableSha256 = 'b'.repeat(64); },
|
|
(right: any) => { right.qualification.launchComparison.source.argvSha256 = 'b'.repeat(64); },
|
|
(right: any) => { right.qualification.launchComparison.source.launchAttempts[0].sandboxDisablingFlag = true; },
|
|
(right: any) => { right.qualification.launchComparison.source.driver.version = '24.19.0'; },
|
|
(right: any) => { right.qualification.launchComparison.source.cleanup.launchSettled = false; },
|
|
(right: any) => { right.counts.pass = 1; },
|
|
(right: any) => { right.launcher.accountGuid = left.launcher.accountGuid; },
|
|
(right: any) => { delete right.qualification.platform; },
|
|
];
|
|
for (const mutate of mutations) {
|
|
const right = arm('node', true);
|
|
mutate(right);
|
|
expect(compareDiaLaunchReceipts(left, right).comparable).toBe(false);
|
|
}
|
|
});
|
|
|
|
test('comparison supervisor binds code hashes and rejects extra private response fields', async () => {
|
|
const work = path.join(root, 'ipc');
|
|
mkdirSync(path.join(work, 'bin'), { recursive: true });
|
|
mkdirSync(path.join(work, 'repo/.github/scripts'), { recursive: true });
|
|
const executable = path.join(work, 'bin/node');
|
|
const driver = path.join(work, 'repo/.github/scripts/dia-launch-driver.mjs');
|
|
const helpers = path.join(work, 'repo/.github/scripts/qualify-dia-macos.ts');
|
|
for (const file of [executable, driver, helpers]) writeFileSync(file, 'synthetic code');
|
|
const hash = (file: string) => createHash('sha256').update(readFileSync(file)).digest('hex');
|
|
const account: any = { work, snapshot: path.join(work, 'repo'), configFile: path.join(work, 'account.json'), environment: { HOME: work },
|
|
launchComparison: { mode: 'launch-only', runtime: 'node', executable, executableSha256: hash(executable), driverSha256: hash(driver), helpersSha256: hash(helpers) } };
|
|
const response: any = { protocol: 1, purpose: 'control', ready: false, launchReturned: false, samplingEnabled: false, cleanup: { confirmed: false },
|
|
driver: { runtime: 'node', version: '24.18.0', architecture: 'arm64', os: 'darwin', playwright: '1.62.1',
|
|
executableSha256: hash(executable), driverSha256: hash(driver), helpersSha256: hash(helpers) } };
|
|
let calls = 0;
|
|
const execute = ((command: string, args: string[], options: any) => {
|
|
calls++;
|
|
expect(command).toBe(executable);
|
|
expect(args).toEqual([driver, account.configFile]);
|
|
expect(JSON.parse(options.input)).toEqual({ purpose: 'control' });
|
|
expect(options.timeout).toBeLessThanOrEqual(40_000);
|
|
expect(options.maxBuffer).toBe(64 * 1024);
|
|
return { status: 0, stdout: JSON.stringify(response), stderr: '' };
|
|
}) as typeof spawnSync;
|
|
expect((await runDiaLaunchComparison(account, 'control', undefined, execute)).supervisor.exitCode).toBe(0);
|
|
response.error = 'private-error-value';
|
|
expect((await runDiaLaunchComparison(account, 'control', undefined, execute)).error).toBe('driver_exchange_failed');
|
|
writeFileSync(executable, 'changed executable');
|
|
await expect(runDiaLaunchComparison(account, 'control', undefined, execute)).rejects.toThrow('comparison_driver_inputs_changed');
|
|
expect(calls).toBe(2);
|
|
await expect(runDiaLaunchComparison({ ...account, launchComparison: undefined }, 'control', undefined, execute)).rejects.toThrow('comparison_driver_authority_missing');
|
|
});
|
|
});
|