import { afterAll, describe, expect, spyOn, test } from 'bun:test'; import { createRequire } from 'node:module'; import { spawnSync } from 'node:child_process'; import { appendFileSync, chmodSync, existsSync, linkSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; import { Database } from 'bun:sqlite'; import { allowedFixturePage, assertDiaSocketPath, assertOwnedDiaProfile, browserCleanupError, browserGroupFacts, browserOperationTimedOut, browserPreflightError, browserRootFacts, browserStartupCategory, browserStartupFacts, browserStderrFacts, browserStderrReasons, captureUserKeychains, classifyNativeWaitSample, createBrowserStderrCapture, createOwnedDiaProfile, DIA_DOWNLOAD, fixtureKeychainRestoreCommands, hasSandboxDisablingArgument, inspectMachOArchitectures, joinOwnedBrowserClose, macosCompatibility, nativeDiaLaunchOptions, observeBrowserLaunches, observePendingBrowserLaunch, sampleOwnedDiaWait, stopOwnedBrowserGroup, observeDiaKeychainEnvironments, observeFixtureKeychain, parseDefaultKeychain, parseKeychainPaths, playwrightModuleLoadFacts, prepareKeychainHome, qualifyDia, readFreshAccountConfiguration, removeOwnedDiaProfile, validateQualificationHost, writePrivateReceipt, } from '../../.github/scripts/qualify-dia-macos'; import { ARCHIVE_CHECK, FRESH_WORK_PREFIX, PRIVATE_RECEIPT_READ, classifyParentDomain, classifyUserDomain, freshLaunchDefinition, freshQualificationPassed, inspectParentDomain, inspectUidProcesses, inspectUserDomain, ownedUserDomainTarget, ownsFreshAccount, ownsLaunchService, parseDirectoryIds, parseDirectoryRecord, passiveUserDomainState, runFreshAccountQualification, uidProcessFacts } from '../../.github/scripts/run-dia-native-qualification'; const require = createRequire(import.meta.url); const root = mkdtempSync(path.join(tmpdir(), 'dia-qualification-test-')); const script = path.resolve(import.meta.dir, '../../.github/scripts/qualify-dia-macos.ts'); const nativeEnvironment = { CI: 'true', GITHUB_ACTIONS: 'true', RUNNER_ENVIRONMENT: 'github-hosted', RUNNER_OS: 'macOS', RUNNER_ARCH: 'ARM64', GSTACK_DIA_NATIVE_QUALIFY: '1', RUNNER_TEMP: root, GITHUB_RUN_ID: 'fixture-run', GITHUB_RUN_ATTEMPT: '1', }; afterAll(() => rmSync(root, { recursive: true, force: true })); describe('Dia macOS CI qualification safety', () => { test('admission accepts only the explicitly enabled disposable ARM64 Mac combination', () => { expect(() => validateQualificationHost(nativeEnvironment, 'darwin', 'arm64')).not.toThrow(); for (const [platform, architecture] of [['linux', 'arm64'], ['win32', 'arm64'], ['darwin', 'x64']]) { expect(() => validateQualificationHost(nativeEnvironment, platform as any, architecture as any)).toThrow('disposable_arm64_macos_ci_required'); } expect(readdirSync(root)).toEqual([]); }); for (const name of Object.keys(nativeEnvironment)) { test(`requires admission field ${name} before any native work`, () => { const env: NodeJS.ProcessEnv = { ...nativeEnvironment }; delete env[name]; expect(() => validateQualificationHost(env, 'darwin', 'arm64')).toThrow('disposable_arm64_macos_ci_required'); expect(readdirSync(root)).toEqual([]); }); } test('rejects a self-hosted machine even when the OS and opt-in match', () => { expect(() => validateQualificationHost({ ...nativeEnvironment, RUNNER_ENVIRONMENT: 'self-hosted' }, 'darwin', 'arm64')) .toThrow('disposable_arm64_macos_ci_required'); }); test('the actual launcher refuses an unapproved environment without staging files', () => { const nativeTemp = mkdtempSync(path.join(root, 'runner-temp-')); const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', '--no-macros', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, script], { cwd: root, env: { PATH: path.dirname(process.execPath), HOME: root, RUNNER_TEMP: nativeTemp, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) }, encoding: 'utf8', timeout: 10_000, }); expect(result.status).toBe(2); expect(result.stderr).toBe(''); expect(JSON.parse(result.stdout)).toEqual({ status: 'incomplete', reason: 'qualification_preflight_failed', counts: { pass: 0, fail: 0, skip: 0 } }); expect(readdirSync(nativeTemp)).toEqual([]); }); test('a fresh fixture-HOME child uses unmodified production Dia profile and domain discovery', () => { const home = realpathSync(mkdtempSync(path.join(root, 'fixture-home-'))); const ownership = createOwnedDiaProfile(home); const profile = path.join(ownership.profile, 'Default'); mkdirSync(profile, { recursive: true }); const database = new Database(path.join(profile, 'Cookies')); database.run('CREATE TABLE cookies (host_key TEXT, has_expires INTEGER, expires_utc INTEGER)'); database.query('INSERT INTO cookies VALUES (?, ?, ?)').run('.fixture.test', 0, 0); database.close(); const production = pathToFileURL(path.resolve(import.meta.dir, '../src/cookie-import-browser.ts')).href; const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', '--no-macros', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, '-e', ` import { homedir } from 'node:os'; const { listProfiles, listDomains } = await import(${JSON.stringify(production)}); console.log(JSON.stringify({ homeAtStartup: homedir() === process.env.HOME, profiles: listProfiles('Dia').map(profile => profile.name), domains: listDomains('Dia', 'Default').domains })); `], { cwd: root, env: { PATH: path.dirname(process.execPath), HOME: home, USERPROFILE: home, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) }, encoding: 'utf8', timeout: 10_000, }); expect(result.status).toBe(0); expect(result.stderr).toBe(''); expect(JSON.parse(result.stdout)).toEqual({ homeAtStartup: true, profiles: ['Default'], domains: [{ domain: '.fixture.test', count: 1 }] }); expect(() => assertOwnedDiaProfile(ownership)).not.toThrow(); removeOwnedDiaProfile(ownership, true); expect(existsSync(home)).toBe(true); }); for (const precreate of [false, true]) { test(`late-installed real Playwright loads only with its dependency directory present at startup (${precreate})`, () => { const fixture = realpathSync(mkdtempSync(path.join(root, 'late-playwright-'))); const home = path.join(fixture, 'home'); const scripts = path.join(fixture, '.github/scripts'); const modules = path.join(fixture, 'node_modules'); mkdirSync(home); mkdirSync(scripts, { recursive: true }); if (precreate) mkdirSync(modules, { mode: 0o700 }); writeFileSync(path.join(fixture, 'package.json'), JSON.stringify({ type: 'module', dependencies: { playwright: '1.62.1' } })); const sourceModules = path.resolve(import.meta.dir, '../../node_modules'); const worker = path.join(scripts, 'worker.ts'); writeFileSync(worker, ` import { cpSync, mkdirSync } from 'node:fs'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); mkdirSync(${JSON.stringify(modules)}, { recursive: true }); for (const name of ['playwright', 'playwright-core']) cpSync(${JSON.stringify(sourceModules)} + '/' + name, ${JSON.stringify(modules)} + '/' + name, { recursive: true }); const version = require(${JSON.stringify(path.join(modules, 'playwright/package.json'))}).version; try { const loaded = await import('playwright'); console.log(JSON.stringify({ version, loaded: !!loaded.chromium })); } catch (error) { console.log(JSON.stringify({ version, type: error.name, code: error.code })); process.exitCode = 2; } `); const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', '--no-macros', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, worker], { cwd: fixture, env: { HOME: home, PATH: path.dirname(process.execPath) }, encoding: 'utf8', timeout: 30_000, }); expect(result.stderr).toBe(''); expect(result.status).toBe(precreate ? 0 : 2); expect(JSON.parse(result.stdout)).toEqual(precreate ? { version: '1.62.1', loaded: true } : { version: '1.62.1', type: 'ResolveMessage', code: 'ERR_MODULE_NOT_FOUND' }); }); } test('the download is the published HTTPS Dia release endpoint', () => { expect(DIA_DOWNLOAD).toBe('https://releases.diabrowser.com/release/Dia-latest.dmg'); }); describe('bounded Mach-O architecture inspection', () => { const thin = (cpu = 0x0100000c, subtype = 0, little = true, wide = true) => { const buffer = Buffer.alloc((wide ? 32 : 28) + 8); const word = (value: number, at: number) => little ? buffer.writeUInt32LE(value, at) : buffer.writeUInt32BE(value, at); word(wide ? 0xfeedfacf : 0xfeedface, 0); word(cpu, 4); word(subtype, 8); word(2, 12); word(1, 16); word(8, 20); return buffer; }; const fat = (little = false, wide = false) => { const buffer = Buffer.alloc(512); const word = (value: number, at: number) => little ? buffer.writeUInt32LE(value, at) : buffer.writeUInt32BE(value, at); const wideWord = (value: bigint, at: number) => little ? buffer.writeBigUInt64LE(value, at) : buffer.writeBigUInt64BE(value, at); word(wide ? 0xcafebabf : 0xcafebabe, 0); word(2, 4); for (const [index, cpu, subtype] of [[0, 0x01000007, 3], [1, 0x0100000c, 0]]) { const at = 8 + index * (wide ? 32 : 20); const image = thin(cpu, subtype); word(cpu, at); word(subtype, at + 4); if (wide) { wideWord(BigInt(128 * (index + 1)), at + 8); wideWord(BigInt(image.length), at + 16); } else { word(128 * (index + 1), at + 8); word(image.length, at + 12); } word(7, at + (wide ? 24 : 16)); image.copy(buffer, 128 * (index + 1)); } return buffer; }; const store = (buffer: Buffer) => { const directory = realpathSync(mkdtempSync(path.join(root, 'macho-'))); const file = path.join(directory, 'executable'); writeFileSync(file, buffer, { mode: 0o600 }); return file; }; for (const little of [true, false]) { test(`thin headers honor byte order (${little}) and distinguish arm64 subtypes`, () => { expect(inspectMachOArchitectures(store(thin(0x0100000c, 0, little))).architectures).toEqual(['arm64']); expect(inspectMachOArchitectures(store(thin(0x0100000c, 0x80000002, little))).architectures).toEqual(['arm64e']); expect(inspectMachOArchitectures(store(thin(0x01000007, 3, little))).architectures).toEqual(['x86_64']); expect(inspectMachOArchitectures(store(thin(7, 3, little, false))).architectures).toEqual(['i386']); }); for (const wide of [true, false]) { test(`fat headers verify their actual slices (${little}, ${wide})`, () => { const result = inspectMachOArchitectures(store(fat(little, wide))); expect(result).toMatchObject({ method: 'bounded_macho_headers', format: wide ? 'fat64' : 'fat32', slices: 2, architectures: ['x86_64', 'arm64'] }); expect(result.bytesRead).toBeLessThanOrEqual(4096); }); } } for (const invalid of ['magic', 'truncated', 'empty_fat', 'excessive_fat', 'truncated_table', 'table_overlap', 'slice_overlap', 'slice_out_of_bounds', 'slice_too_short', 'misaligned', 'alignment_overflow', 'cpu_mismatch', 'subtype_mismatch', 'nested_fat', 'duplicate_architecture', 'wide_offset_overflow', 'reserved', 'header_width', 'file_type', 'load_commands', 'unknown_cpu']) { test(`architecture inspection rejects ${invalid} rather than guessing arm64`, () => { let image = fat(); if (invalid === 'magic') image = Buffer.from('not a macho executable'); if (invalid === 'truncated') image = thin().subarray(0, 20); if (invalid === 'empty_fat') image.writeUInt32BE(0, 4); if (invalid === 'excessive_fat') image.writeUInt32BE(33, 4); if (invalid === 'truncated_table') image = image.subarray(0, 24); if (invalid === 'table_overlap') image.writeUInt32BE(0, 16); if (invalid === 'slice_overlap') image.writeUInt32BE(128, 36); if (invalid === 'slice_out_of_bounds') image.writeUInt32BE(1024, 36); if (invalid === 'slice_too_short') image.writeUInt32BE(28, 40); if (invalid === 'misaligned') image.writeUInt32BE(257, 36); if (invalid === 'alignment_overflow') image.writeUInt32BE(0xffffffff, 44); if (invalid === 'cpu_mismatch') image.writeUInt32LE(0x01000007, 260); if (invalid === 'subtype_mismatch') image.writeUInt32LE(2, 264); if (invalid === 'nested_fat') image.writeUInt32BE(0xcafebabe, 256); if (invalid === 'duplicate_architecture') { image.writeUInt32BE(0x01000007, 28); image.writeUInt32BE(3, 32); thin(0x01000007, 3).copy(image, 256); } if (invalid === 'wide_offset_overflow') { image = fat(false, true); image.writeBigUInt64BE(1n << 60n, 16); } if (invalid === 'reserved') { image = fat(false, true); image.writeUInt32BE(1, 36); } if (invalid === 'header_width') image = thin(0x0100000c, 0, true, false); if (invalid === 'file_type') { image = thin(); image.writeUInt32LE(6, 12); } if (invalid === 'load_commands') { image = thin(); image.writeUInt32LE(1000, 20); } if (invalid === 'unknown_cpu') image = thin(0x010000ff, 0); expect(() => inspectMachOArchitectures(store(image))).toThrow(); }); } test('architecture inspection rejects links, non-files, and expired budgets', () => { const file = store(thin()); symlinkSync(file, file + '.link'); expect(() => inspectMachOArchitectures(file + '.link')).toThrow('unsafe_macho_file'); expect(() => inspectMachOArchitectures(path.dirname(file))).toThrow('unsafe_macho_file'); for (const budget of [0, NaN, Infinity]) expect(() => inspectMachOArchitectures(file, budget)).toThrow('macho_read_budget_exhausted'); expect(qualifyDia.toString()).not.toContain('/usr/bin/lipo'); }); for (const change of ['append', 'replace']) { test(`architecture inspection detects a file that changes by ${change}`, () => { const image = thin(); const file = store(image); let ticks = 0; const clock = spyOn(performance, 'now').mockImplementation(() => { if (++ticks === 3) { if (change === 'append') appendFileSync(file, Buffer.from([0])); else { renameSync(file, file + '.previous'); writeFileSync(file, image); } } return 100; }); try { expect(() => inspectMachOArchitectures(file)).toThrow('macho_changed_during_inspection'); } finally { clock.mockRestore(); } }); } }); test('the exact nested macOS account and qualifier socket layout stays below the unchanged limit', () => { const sourceProfile = path.posix.join(FRESH_WORK_PREFIX + 'XXXXXX', 'tmp', 'dia-XXXXXX', 'h', 'Library/Application Support/Dia/User Data'); expect(Buffer.byteLength(sourceProfile + '/SingletonSocket')).toBe(97); expect(() => assertDiaSocketPath(sourceProfile)).not.toThrow(); const oldProfile = '/private/tmp/dia-native-XXXXXX/tmp/dia-XXXXXX/h/Library/Application Support/Dia/User Data'; expect(Buffer.byteLength(oldProfile + '/SingletonSocket')).toBe(105); expect(() => assertDiaSocketPath(oldProfile)).toThrow('fixture_socket_path_too_long'); const atLimit = '/' + 'x'.repeat(100 - Buffer.byteLength('//SingletonSocket')); expect(Buffer.byteLength(atLimit + '/SingletonSocket')).toBe(100); expect(() => assertDiaSocketPath(atLimit)).toThrow('fixture_socket_path_too_long'); }); test('the registered-home source profile fits the socket bound without a shadow HOME', () => { const profile = path.posix.join(FRESH_WORK_PREFIX + 'XXXXXX', 'home', 'Library/Application Support/Dia/User Data'); expect(Buffer.byteLength(profile + '/SingletonSocket')).toBe(85); expect(() => assertDiaSocketPath(profile)).not.toThrow(); const implementation = qualifyDia.toString(); expect(implementation).toContain('readFreshAccountConfiguration'); expect(implementation).toContain('const home = account.home'); expect(implementation).not.toMatch(/fixtureEnvironment|systemEnvironment|isolation\.originalHome/); expect(implementation).toContain('removeOwnedDiaProfile'); }); test('profile ownership is exclusive and cleanup preserves the account home and sibling state', () => { const home = realpathSync(mkdtempSync(path.join(root, 'owned-profile-home-'))); prepareKeychainHome(home); const preserved = path.join(home, 'Library/Keychains/fixture-state'); writeFileSync(preserved, 'preserved fixture state'); const ownership = createOwnedDiaProfile(home); const sibling = path.join(path.dirname(ownership.profile), 'sibling-state'); writeFileSync(sibling, 'preserved sibling state'); expect(() => assertOwnedDiaProfile(ownership)).not.toThrow(); expect(() => createOwnedDiaProfile(home)).toThrow(); expect(() => removeOwnedDiaProfile(ownership, false)).toThrow('owned_browsers_not_stopped'); expect(existsSync(ownership.profile)).toBe(true); removeOwnedDiaProfile(ownership, true); expect(existsSync(ownership.profile)).toBe(false); expect(existsSync(home)).toBe(true); expect(readFileSync(preserved, 'utf8')).toBe('preserved fixture state'); expect(readFileSync(sibling, 'utf8')).toBe('preserved sibling state'); }); for (const change of ['nonce', 'inode', 'marker_symlink', 'profile_symlink', 'uid', 'home_escape', 'marker_permissions', 'marker_hardlink']) { test(`profile cleanup refuses changed ${change} ownership without deleting the directory`, () => { const home = realpathSync(mkdtempSync(path.join(root, 'profile-refusal-'))); const ownership = createOwnedDiaProfile(home); const profile = ownership.profile; const marker = path.join(profile, '.gstack-dia-owner'); if (change === 'nonce') writeFileSync(marker, (ownership.nonce[0] === '0' ? '1' : '0') + ownership.nonce.slice(1)); if (change === 'inode') { renameSync(profile, profile + '.original'); mkdirSync(profile, { mode: 0o700 }); writeFileSync(marker, ownership.nonce, { mode: 0o600 }); } if (change === 'marker_symlink') { renameSync(marker, marker + '.original'); symlinkSync(marker + '.original', marker); } if (change === 'profile_symlink') { renameSync(profile, profile + '.original'); symlinkSync(profile + '.original', profile, 'dir'); } if (change === 'uid') ownership.uid = process.getuid!() + 1; if (change === 'home_escape') ownership.profile = home; if (change === 'marker_permissions') chmodSync(marker, 0o644); if (change === 'marker_hardlink') linkSync(marker, path.join(home, 'marker-link')); expect(() => removeOwnedDiaProfile(ownership, true)).toThrow(); expect(existsSync(profile)).toBe(true); expect(existsSync(home)).toBe(true); }); } test('source profile creation refuses existing profiles and linked ancestors without writing through them', () => { const home = realpathSync(mkdtempSync(path.join(root, 'existing-profile-'))); const profile = path.join(home, 'Library/Application Support/Dia/User Data'); mkdirSync(profile, { recursive: true }); writeFileSync(path.join(profile, 'existing-state'), 'untouched'); expect(() => createOwnedDiaProfile(home)).toThrow(); expect(readdirSync(profile)).toEqual(['existing-state']); const linkedHome = realpathSync(mkdtempSync(path.join(root, 'linked-profile-home-'))); const elsewhere = realpathSync(mkdtempSync(path.join(root, 'linked-profile-target-'))); symlinkSync(elsewhere, path.join(linkedHome, 'Library'), 'dir'); expect(() => createOwnedDiaProfile(linkedHome)).toThrow('unsafe_profile_ancestor'); expect(readdirSync(elsewhere)).toEqual([]); }); test('the qualifier rejects an arbitrary caller-supplied home as fresh account authority', () => { const fake = path.join(root, 'account.json'); writeFileSync(fake, JSON.stringify({ home: root, uid: process.getuid?.() })); expect(() => readFreshAccountConfiguration(fake)).toThrow('unsafe_fresh_account_configuration'); expect(() => readFreshAccountConfiguration('account.json')).toThrow('unsafe_fresh_account_configuration'); }); test('headless source launch removes mock Keychain and first-run suppression defaults', () => { const env = { HOME: '/fixture/home', PATH: '/usr/bin:/bin' }; const options = nativeDiaLaunchOptions('/fixture/Dia.app/Contents/MacOS/Dia', env); expect(options.headless).toBe(true); expect(options.chromiumSandbox).toBe(true); expect(options.timeout).toBe(30_000); expect(options.ignoreDefaultArgs).toEqual(['--use-mock-keychain', '--password-store=basic', '--no-first-run']); expect(options.args).toEqual(['--disable-sync', '--no-default-browser-check', '--profile-directory=Default']); expect(options.env).toBe(env); expect(options.serviceWorkers).toBe('block'); expect(options.args.some(arg => /onboarding|skip-login|remote-debugging-port/.test(arg))).toBe(false); }); test('Mac native spawn policy refuses sandbox-disabling arguments regardless of caller options', () => { const descriptor = Object.getOwnPropertyDescriptor(process, 'platform')!; const profile = path.join(root, 'mac-sandbox-policy'); const childProcess = require('node:child_process'); const observer = observeBrowserLaunches(new Map([[process.execPath, profile]])); try { Object.defineProperty(process, 'platform', { ...descriptor, value: 'darwin' }); for (const disabling of [['--no-sandbox'], ['--no-sandbox=false'], ['--disable-sandbox'], ['--disable-gpu-sandbox'], ['--disable-setuid-sandbox'], ['--disable-seccomp-filter-sandbox'], ['--disable-namespace-sandbox'], ['--no-zygote-sandbox'], ['--single-process'], ['--in-process-gpu'], ['--disable-features=GpuSandboxV2'], ['--disable-features', 'RendererSandbox']]) { expect(hasSandboxDisablingArgument(disabling)).toBe(true); expect(() => childProcess.spawn(process.execPath, ['--remote-debugging-pipe', '--user-data-dir=' + profile, ...disabling], { detached: true, shell: false, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'], chromiumSandbox: false, allowSandboxDisable: true, env: { DISABLE_SANDBOX: '1' }, })).toThrow('browser_launch_policy_rejected'); expect(observer.attempts.at(-1)).toMatchObject({ sandboxRequired: true, sandboxDisablingFlag: true }); } expect(observer.children).toHaveLength(0); } finally { Object.defineProperty(process, 'platform', descriptor); observer.restore(); } expect(hasSandboxDisablingArgument(['--headless', '--disable-sync', '--disable-features=MediaRouter,Translate'])).toBe(false); }); test('startup admits only blank pages or the exact synthetic loopback origin', () => { const origin = 'http://127.0.0.1:8123'; expect(allowedFixturePage('about:blank', origin)).toBe(true); expect(allowedFixturePage(origin + '/seed', origin)).toBe(true); for (const url of ['https://www.diabrowser.com/login', 'dia://onboarding', 'chrome://welcome', 'http://localhost:8123/seed', 'http://127.0.0.1:8124/seed', 'javascript:alert(1)', 'about:config', 'invalid']) expect(allowedFixturePage(url, origin)).toBe(false); const authenticated = new URL(origin); authenticated.username = 'fixture-user'; authenticated.password = 'fixture'; expect(allowedFixturePage(authenticated.href, origin)).toBe(false); }); test('startup diagnostics classify pages without exposing URLs or broadening admission', () => { const pages = [ ['about:blank', 'blank'], ['about:blank#synthetic-private-value', 'other_about'], ['chrome://newtab/?token=synthetic-private-value', 'chromium_new_tab'], ['chrome://new-tab-page/', 'chromium_new_tab'], ['chrome://intro/', 'chromium_onboarding'], ['chrome://welcome/', 'chromium_onboarding'], ['chrome://settings/', 'chromium_internal'], ['dia://onboarding', 'dia_internal'], ['chrome-extension://fixture/path', 'extension'], ['https://fixture.invalid/login?token=synthetic-private-value', 'external_web'], ['http://127.0.0.1:8123' + '/fixture', 'loopback_web'], ['file:///synthetic-private-value', 'file'], ['data:text/html,synthetic-private-value', 'data'], ['invalid synthetic-private-value', 'invalid'], ]; const categories = pages.map(([url]) => browserStartupCategory(url)); expect(categories).toEqual(pages.map(([, category]) => category)); expect(JSON.stringify(categories)).not.toContain('synthetic-private-value'); expect(JSON.stringify(categories)).not.toContain('fixture.invalid'); for (const [url] of pages.slice(1, 10)) expect(allowedFixturePage(url, 'http://127.0.0.1:8123')).toBe(false); }); test('browser stderr classifies only known policy, pipe, Keychain, loader and bootstrap diagnostics', () => { for (const [line, reason] of [ ['DevTools remote debugging requires a non-default data directory. Specify this using --user-data-dir.', 'default_profile_policy'], ['Remote debugging pipe file descriptors are not open.', 'debugging_pipe_unavailable'], ['errSecInteractionNotAllowed: User interaction is not allowed.', 'keychain_interaction_disallowed'], ['errSecInteractionRequired', 'keychain_interaction_required'], ['errSecAuthFailed', 'keychain_access_failed'], ['dyld[123]: Library not loaded:', 'dynamic_library_error'], ['code signature invalid', 'code_signing_error'], ['bootstrap_check_in failed', 'graphics_or_bootstrap_error'], ['ProcessSingleton', 'browser_profile_unavailable'], ]) { const reasons = browserStderrReasons(line + ' synthetic-private-value /private/fixture/profile'); expect(reasons).toContain(reason); expect(JSON.stringify(reasons)).not.toContain('synthetic-private'); expect(JSON.stringify(reasons)).not.toContain('/private'); } for (const line of ['using a non-default data directory', 'remote-debugging-pipe enabled', 'Waiting for Keychain', 'WindowServer connection available', 'private unknown diagnostic']) expect(browserStderrReasons(line)).toEqual([]); }); test('stderr capture handles chunk boundaries and final unterminated lines without retaining text', () => { const capture = createBrowserStderrCapture(); const policy = 'DevTools remote debugging requires a non-default data directory.'; capture.consume(Buffer.from('synthetic-private-value\n' + policy.slice(0, 23))); expect(capture.snapshot().reasonCounts.default_profile_policy).toBe(0); capture.consume(Buffer.from(policy.slice(23))); const before = capture.snapshot(); expect(before.reasonCounts.default_profile_policy).toBe(1); capture.consume(Buffer.from('\n' + policy)); capture.end(); expect(capture.snapshot().reasonCounts.default_profile_policy).toBe(2); expect(before.reasonCounts.default_profile_policy).toBe(1); expect(capture.snapshot().ended).toBe(true); expect(JSON.stringify(capture.snapshot())).not.toContain('synthetic-private'); expect(JSON.stringify(capture.snapshot())).not.toContain('DevTools'); }); test('stderr inspection caps bytes and pending line size while exposing truncation honestly', () => { const capture = createBrowserStderrCapture(); capture.consume(Buffer.alloc(65_536, 120)); capture.consume(Buffer.from('\nDevTools remote debugging requires a non-default data directory.\n')); const facts = capture.snapshot(); expect(facts.bytesInspected).toBe(65_536); expect(facts.bytesSeen).toBeGreaterThan(65_536); expect(facts.truncated).toBe(true); expect(facts.discardedLongLines).toBe(1); expect(facts.reasonCounts.default_profile_policy).toBe(0); const lines = createBrowserStderrCapture(); lines.consume('x'.repeat(5000) + '\nRemote debugging pipe file descriptors are not open.\n'); expect(lines.snapshot().reasonCounts.debugging_pipe_unavailable).toBe(1); expect(lines.snapshot().truncated).toBe(true); }); test('causal stderr reasons outrank a generic timeout without changing its timeout identity', () => { const timeout = Object.assign(new Error('synthetic-private-timeout'), { name: 'TimeoutError' }); expect(browserOperationTimedOut(timeout)).toBe(true); expect(browserPreflightError(timeout)).toBe('operation_timeout'); expect(browserPreflightError(timeout, ['default_profile_policy'])).toBe('default_profile_policy'); expect(browserPreflightError(timeout, ['debugging_pipe_unavailable'])).toBe('debugging_pipe_unavailable'); expect(browserPreflightError(timeout, ['keychain_interaction_required'])).toBe('keychain_interaction_required'); const embedded = Object.assign(new Error('Timed out\nDevTools remote debugging requires a non-default data directory.'), { name: 'TimeoutError' }); expect(browserPreflightError(embedded)).toBe('default_profile_policy'); expect(browserOperationTimedOut(embedded)).toBe(true); }); test('stderr snapshots keep source, destination, and cleanup-time observations separate', () => { const source = createBrowserStderrCapture(); const destination = createBrowserStderrCapture(); source.consume('DevTools remote debugging requires a non-default data directory.\n'); const beforeCleanup = source.snapshot(); source.consume('bootstrap_check_in failed\n'); destination.consume('Remote debugging pipe file descriptors are not open.\n'); expect(beforeCleanup.reasonCounts.graphics_or_bootstrap_error).toBe(0); expect(source.snapshot().reasonCounts.graphics_or_bootstrap_error).toBe(1); expect(destination.snapshot().reasonCounts.default_profile_policy).toBe(0); expect(destination.snapshot().reasonCounts.debugging_pipe_unavailable).toBe(1); }); test('signed macOS requirements compare numeric versions and preserve unknown metadata', () => { expect(macosCompatibility({ LSMinimumSystemVersion: '14.0' }, '15.6.1')).toEqual({ hostVersion: '15.6.1', minimumSystemVersion: '14.0', minimumArm64Version: null, compatible: true }); expect(macosCompatibility({ LSMinimumSystemVersion: '26.0' }, '15.6.1').compatible).toBe(false); expect(macosCompatibility({ LSMinimumSystemVersion: '15.9' }, '15.10').compatible).toBe(true); expect(macosCompatibility({ LSMinimumSystemVersion: '15.6.1' }, '15.6').compatible).toBe(false); expect(macosCompatibility({ LSMinimumSystemVersion: '15.6' }, '15.6.0').compatible).toBe(true); expect(macosCompatibility({ LSMinimumSystemVersion: '14.0', LSMinimumSystemVersionByArchitecture: { arm64: '26.0' } }, '15.6.1').compatible).toBe(false); expect(macosCompatibility({ LSMinimumSystemVersion: '26.0', LSMinimumSystemVersionByArchitecture: { arm64: '14.0' } }, '15.6.1').compatible).toBe(false); expect(macosCompatibility({ LSMinimumSystemVersionByArchitecture: { arm64: '14.0' } }, '15.6.1').compatible).toBe(true); for (const plist of [{}, null, { LSMinimumSystemVersion: 'synthetic-private-value' }, { LSMinimumSystemVersion: 14 }, { LSMinimumSystemVersion: '14.0', LSMinimumSystemVersionByArchitecture: 'synthetic-private-value' }, { LSMinimumSystemVersion: '14.0', LSMinimumSystemVersionByArchitecture: [] }, { LSMinimumSystemVersion: '14.0', LSMinimumSystemVersionByArchitecture: { arm64: 'private-version' } }]) { const facts = macosCompatibility(plist, '15.6.1'); expect(facts.compatible).toBeNull(); expect(JSON.stringify(facts)).not.toContain('private'); } expect(macosCompatibility({ LSMinimumSystemVersion: '14.0' }, 'private-host-version').compatible).toBeNull(); }); test('bounded startup categories do not hide a disallowed page beyond the receipt limit', () => { const urls = [...Array(64).fill('about:blank'), 'https://private.invalid/?token=synthetic-private-value']; const facts = browserStartupFacts(urls, 'http://127.0.0.1:8123'); expect(facts).toMatchObject({ count: 65, truncated: true, allowed: false }); expect(facts.categories).toHaveLength(64); expect(JSON.stringify(facts)).not.toContain('private'); }); test('browser root facts retain exit and allowlisted signal evidence without process payloads', () => { const facts = browserRootFacts([ { pid: 300, process: { exitCode: null, signalCode: null, spawnargs: ['synthetic-private-value'] } }, { pid: 301, process: { exitCode: 0, signalCode: null } }, { pid: 302, process: { exitCode: null, signalCode: 'SIGABRT' } }, { pid: 303, process: { exitCode: null, signalCode: 'synthetic-private-signal' } }, ] as any); expect(facts).toEqual([{ pid: 300, exitCode: null, signal: null }, { pid: 301, exitCode: 0, signal: null }, { pid: 302, exitCode: null, signal: 'SIGABRT' }, { pid: 303, exitCode: null, signal: 'other' }]); expect(JSON.stringify(facts)).not.toContain('private'); }); test('browser cleanup distinguishes live groups from signal errors without retaining raw errors', () => { expect(browserCleanupError(new Error('owned_process_group_still_live')).reason).toBe('group_still_live'); expect(browserCleanupError(new Error('cleanup_budget_exhausted')).reason).toBe('cleanup_deadline'); expect(browserCleanupError(Object.assign(new Error('synthetic-private-value'), { code: 'EPERM', errno: 1 }))) .toEqual({ reason: 'signal_or_probe_failed', code: 'EPERM', errno: 1 }); const unknown = browserCleanupError({ message: 'synthetic-private-value', code: 'private-code', errno: 'private-errno' }); expect(unknown).toEqual({ reason: 'signal_or_probe_failed', code: 'unclassified', errno: null }); }); test('process-group diagnostics distinguish owned zombies, live members, and unrelated UID counts', () => { const facts = browserGroupFacts('20000 300 1 300 Z\n20000 301 1 300 S+\n501 302 1 300 S\n20000 400 1 400 S\n', 20000, 300); expect(facts).toEqual({ available: true, count: 2, foreignUidCount: 1, zombies: 1, live: 1, truncated: false, processes: [{ pid: 300, ppid: 1, state: 'Z' }, { pid: 301, ppid: 1, state: 'S' }] }); expect(browserGroupFacts(Array.from({ length: 70 }, (_, index) => `20000 ${index + 300} 1 300 S`).join('\n'), 20000, 300).processes).toHaveLength(64); expect(() => browserGroupFacts('private-invalid-row', 20000, 300)).toThrow('invalid_group_snapshot'); expect(() => browserGroupFacts('', 20000, 0)).toThrow('invalid_group_snapshot_target'); }); test('native samples retain fixed wait families only from call-graph frames', () => { const output = `Process: private-process [123] Path: /private/sensitive/Security/AppKit/CFNetwork Call graph: 100 Thread_11 DispatchQueue_1: com.apple.main-thread (serial) + 100 NSApplicationMain (in AppKit) private-source-path + 100 SecKeychainFindGenericPassword (in Security) private-item-name + 100 mach_msg_trap (in libsystem_kernel) 100 Thread_12 private-thread-name + 100 NSURLSessionTask (in CFNetwork) private-request-url Total number in stack (recursive counted multiple): 999 SecItemCopyMatching private-unrelated-summary Binary Images: Security AppKit CFNetwork /private/image/path `; const result = classifyNativeWaitSample(output); expect(result).toEqual({ available: true, callGraphSeen: true, mainThreadSeen: true, frames: 4, frameCounts: { security_keychain: 1, appkit_bootstrap: 1, network: 1, runloop: 1 }, mainThreadFrameCounts: { security_keychain: 1, appkit_bootstrap: 1, network: 0, runloop: 1 }, shape: { graphLines: 6, nonemptyGraphLines: 6, threadTokenLines: 2, numericPrefixLines: 2, imageAnnotatedLines: 4, nonAsciiGraphLines: 0, unrecognizedGraphLines: 0, graphEnd: 'totals', binaryImagesSeen: true } }); expect(JSON.stringify(result)).not.toContain('private'); expect(JSON.stringify(result)).not.toContain('SecKeychain'); expect(classifyNativeWaitSample('private missing call graph').available).toBe(false); expect(classifyNativeWaitSample('x'.repeat(1024 * 1024 + 1))).toEqual({ available: false, reason: 'sample_output_oversized' }); }); test('sample structure distinguishes an empty graph from unrecognized stack rows without exposing text', () => { const empty = classifyNativeWaitSample('Call graph:\n\nTotal number in stack:\nBinary Images:\nprivate-image'); expect(empty).toMatchObject({ available: false, frames: 0, callGraphSeen: true, shape: { graphLines: 1, nonemptyGraphLines: 0, unrecognizedGraphLines: 0, graphEnd: 'totals', binaryImagesSeen: true } }); const unfamiliar = classifyNativeWaitSample('Call graph:\n ◇ private-row (in Security)\nBinary Images:\nprivate-image'); expect(unfamiliar).toMatchObject({ available: false, frames: 0, shape: { nonemptyGraphLines: 1, unrecognizedGraphLines: 1, nonAsciiGraphLines: 1, imageAnnotatedLines: 1, graphEnd: 'binary_images' } }); expect(JSON.stringify(unfamiliar)).not.toContain('private'); expect(JSON.stringify(unfamiliar)).not.toContain('Security'); }); test('a pending launch is sampled once with a deadline before the native launch timeout', async () => { let resolve!: (value: string) => void; const pending = new Promise(done => { resolve = done; }); const started = performance.now(); const deadlines: number[] = []; const result = await observePendingBrowserLaunch(() => pending, deadline => { deadlines.push(deadline); resolve('ready'); }, started + 60_000, 5); expect(result).toBe('ready'); expect(deadlines).toHaveLength(1); expect(deadlines[0]).toBeLessThan(started + 30_000); expect(deadlines[0]).toBeGreaterThan(started + 24_000); }); test('settled launches cancel sampling and exhausted sampling windows do not extend the launch', async () => { let observations = 0; const observe = () => { observations++; }; expect(await observePendingBrowserLaunch(async () => 'ready', observe, performance.now() + 60_000, 5)).toBe('ready'); const original = new Error('original_launch_failure'); await expect(observePendingBrowserLaunch(async () => { throw original; }, observe, performance.now() + 60_000, 5)).rejects.toBe(original); expect(await observePendingBrowserLaunch(async () => { await Bun.sleep(10); return 'ready'; }, observe, performance.now() + 100, 0)).toBe('ready'); await Bun.sleep(10); expect(observations).toBe(0); }); test('native wait sampling uses only the observed live child and never writes a stack artifact', () => { const pid = 12345; const uid = process.getuid!(); const child: any = { pid, executable: '/owned/Dia', closeObserved: false, process: { pid, exitCode: null, signalCode: null } }; const calls: string[][] = []; const result = sampleOwnedDiaWait(child, uid, child.executable, performance.now() + 10_000, { HOME: root }, ((command: string, args: string[], options: any) => { calls.push([command, ...args]); expect(Number.isInteger(options.timeout)).toBe(true); expect(options.timeout).toBeGreaterThan(0); expect(options.timeout).toBeLessThanOrEqual(5_000); expect(options.killSignal).toBe('SIGKILL'); if (command === '/bin/ps') return { status: 0, stdout: `${uid} ${pid} ${process.pid} S Dia\n`, stderr: '' }; return { status: 0, stdout: 'private-header\nCall graph:\n 10 Thread_1 DispatchQueue_1: com.apple.main-thread\n + 10 CFRunLoopRun (in CoreFoundation) private-path\nBinary Images:\nprivate-image', stderr: '' }; }) as typeof spawnSync); expect(calls).toEqual([['/bin/ps', '-p', String(pid), '-o', 'uid=,pid=,ppid=,state=,ucomm='], ['/usr/bin/sample', String(pid), '1', '10', '-file', '/dev/stdout']]); expect(result).toMatchObject({ available: true, attempted: true, ownedLiveChildConfirmed: true, reason: 'sampled', waitFamilies: { mainThreadFrameCounts: { runloop: 1 } } }); expect(JSON.stringify(result)).not.toContain('private-'); expect(JSON.stringify(result)).not.toContain('CFRunLoopRun'); }); test('sampling refuses changed identity, zombies, and closed roots without attempting a native sample', () => { const uid = process.getuid!(); const child: any = { pid: 12345, executable: '/owned/Dia', closeObserved: false, process: { pid: 12345, exitCode: null, signalCode: null } }; for (const row of [`${uid + 1} 12345 ${process.pid} S Dia`, `${uid} 12345 ${process.pid + 1} S Dia`, `${uid} 12345 ${process.pid} Z Dia`, `${uid} 12345 ${process.pid} S other`, `${uid} 12346 ${process.pid} S Dia`]) { let calls = 0; const result = sampleOwnedDiaWait(child, uid, child.executable, performance.now() + 10_000, {}, ((command: string) => { calls++; expect(command).toBe('/bin/ps'); return { status: 0, stdout: row, stderr: '' }; }) as typeof spawnSync); expect(calls).toBe(1); expect(result.attempted).toBe(false); } for (const changed of [{ ...child, closeObserved: true }, { ...child, executable: '/other/Chromium' }, { ...child, process: { ...child.process, exitCode: 0 } }]) { const result = sampleOwnedDiaWait(changed, uid, '/owned/Dia', performance.now() + 10_000, {}, (() => { throw new Error('must_not_spawn'); }) as typeof spawnSync); expect(result).toMatchObject({ available: false, attempted: false }); } }); test('sampling reports OS permission refusal without escalation or raw failure text', () => { const uid = process.getuid!(); const child: any = { pid: 12345, executable: '/owned/Dia', closeObserved: false, process: { pid: 12345, exitCode: null, signalCode: null } }; const result = sampleOwnedDiaWait(child, uid, child.executable, performance.now() + 10_000, {}, ((command: string) => command === '/bin/ps' ? { status: 0, stdout: `${uid} 12345 ${process.pid} S Dia`, stderr: '' } : { status: 1, stdout: '', stderr: 'Failed to get task for pid: private-process-path. Operation not permitted.' }) as typeof spawnSync); expect(result).toMatchObject({ available: false, attempted: true, reason: 'sampling_permission_denied' }); expect(JSON.stringify(result)).not.toContain('private-process'); }); test('child close joining stays within its deadline and refuses an unconfirmed close', async () => { await expect(joinOwnedBrowserClose({ closed: Promise.resolve(), closeObserved: false } as any, performance.now() + 1000)) .rejects.toThrow('owned_child_close_unconfirmed'); await expect(joinOwnedBrowserClose({ closed: new Promise(() => {}), closeObserved: false } as any, performance.now() + 5)) .rejects.toThrow('operation_timeout'); await expect(joinOwnedBrowserClose({ closed: Promise.resolve(), closeObserved: true } as any, 0)) .rejects.toThrow('cleanup_budget_exhausted'); }); for (const phase of ['probe', 'signal']) { test(`an initial ${phase} error is retained while child reaping still precedes the final absence proof`, async () => { const facts: Record = { signalSent: false, absenceConfirmed: false }; const child: any = { pid: 12345, closeObserved: false }; child.closed = Promise.resolve().then(() => { child.closeObserved = true; }); const calls: Array<0 | 'SIGKILL'> = []; await stopOwnedBrowserGroup(child, performance.now() + 1000, facts, (_pid, signal) => { calls.push(signal); if (!child.closeObserved) { if (phase === 'signal' && signal === 0) return; throw Object.assign(new Error('private-probe-error'), { code: 'EPERM', errno: 1 }); } throw Object.assign(new Error('gone'), { code: 'ESRCH' }); }); expect(calls).toEqual(phase === 'probe' ? [0, 0] : [0, 'SIGKILL', 0]); expect(facts).toMatchObject({ stage: 'completed', signalSent: false, childCloseObserved: true, absenceConfirmed: true, initialSignalFailure: { stage: phase === 'probe' ? 'probe_before_signal' : 'signal', code: 'EPERM', errno: 1 } }); expect(JSON.stringify(facts)).not.toContain('private-probe-error'); }); } test('permission errors and zombies never replace the required child close and actual group absence', async () => { const facts = { signalSent: false, absenceConfirmed: false }; const waiting: any = { pid: 12345, closed: new Promise(() => {}), closeObserved: false }; const denied = () => { throw Object.assign(new Error('denied'), { code: 'EPERM' }); }; await expect(stopOwnedBrowserGroup(waiting, performance.now() + 10, facts, denied)).rejects.toThrow('operation_timeout'); expect(facts.absenceConfirmed).toBe(false); const closed: any = { pid: 12345, closed: Promise.resolve(), closeObserved: true }; await expect(stopOwnedBrowserGroup(closed, performance.now() + 1000, facts, denied)).rejects.toMatchObject({ code: 'EPERM' }); expect(facts.absenceConfirmed).toBe(false); await expect(stopOwnedBrowserGroup(closed, performance.now() + 10, facts, () => {})).rejects.toThrow('owned_process_group_still_live'); expect(facts.absenceConfirmed).toBe(false); }); for (const [error, category] of [ [new Error('browserType.launchPersistentContext: browser_launch_policy_rejected synthetic-private-value'), 'launch_policy_rejected'], [new Error('background_browser_ownership_failed'), 'ownership_unconfirmed'], [new Error('background_browser_startup_page_rejected'), 'startup_page_rejected'], [new Error('background_browser_render_failed'), 'render_mismatch'], [Object.assign(new Error('synthetic-private-value'), { name: 'TimeoutError' }), 'operation_timeout'], [new Error('native_operation_timed_out'), 'operation_timeout'], [new Error('operation_timeout'), 'operation_timeout'], [new Error('qualification_budget_exhausted'), 'operation_timeout'], [new Error('source_process_ownership_unconfirmed'), 'ownership_unconfirmed'], [new Error('destination_process_ownership_unconfirmed'), 'ownership_unconfirmed'], [new Error('onboarding_or_external_page'), 'startup_page_rejected'], [Object.assign(new Error('synthetic-private-value'), { code: 'ENOENT' }), 'executable_unavailable'], [Object.assign(new Error('synthetic-private-value'), { code: 'EACCES' }), 'permission_denied'], [{ name: 'ResolveMessage', code: 'ERR_MODULE_NOT_FOUND', message: 'synthetic-private-value' }, 'module_unavailable'], [Object.assign(new Error('synthetic-private-value'), { code: 'MODULE_NOT_FOUND' }), 'module_unavailable'], [Object.assign(new Error('synthetic-private-value'), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' }), 'module_export_unavailable'], [Object.assign(new Error('synthetic-private-value'), { code: 'ERR_REQUIRE_ESM' }), 'module_format_error'], [new RangeError('synthetic-private-value'), 'invalid_runtime_range'], [new TypeError('synthetic-private-value'), 'runtime_type_error'], [new Error('dyld[123]: Library not loaded: synthetic-private-value'), 'dynamic_library_error'], [new Error('code signature invalid: synthetic-private-value'), 'code_signing_error'], [new Error('ProcessSingleton synthetic-private-value'), 'browser_profile_unavailable'], [new Error('bootstrap_check_in failed synthetic-private-value'), 'graphics_or_bootstrap_error'], [new Error('Target page, context or browser has been closed synthetic-private-value'), 'target_closed'], [new Error('Protocol error: synthetic-private-value'), 'protocol_error'], [new Error('synthetic-private-value'), 'unclassified_browser_error'], [{ code: 'synthetic-private-value', message: 'synthetic-private-value' }, 'unclassified_browser_error'], ] as const) { test(`browser diagnostics return only the allowlisted ${category} category`, () => { expect(browserPreflightError(error)).toBe(category); expect(browserPreflightError(error)).not.toContain('synthetic-private-value'); }); } test('module-load facts retain only known error identifiers and known dependency filenames', () => { const snapshot = realpathSync(mkdtempSync(path.join(root, 'module-facts-'))); const packageDirectory = path.join(snapshot, 'node_modules/playwright'); mkdirSync(packageDirectory, { recursive: true }); writeFileSync(path.join(packageDirectory, 'package.json'), '{}', { mode: 0o600 }); const facts = playwrightModuleLoadFacts(snapshot, { name: 'ResolveMessage', code: 'ERR_MODULE_NOT_FOUND', message: "Cannot find package 'playwright' imported from /synthetic-private-value/worker.ts" }); expect(facts.errorType).toBe('ResolveMessage'); expect(facts.errorCode).toBe('ERR_MODULE_NOT_FOUND'); expect(facts.requestedModule).toBe('playwright'); expect(facts.files['playwright/package.json']).toEqual({ exists: true, readable: true, ownedByCurrentUid: true, insideSnapshot: true }); expect(facts.files['playwright-core/lib/coreBundle.js'].exists).toBe(false); expect(JSON.stringify(facts)).not.toContain('synthetic-private-value'); expect(JSON.stringify(facts)).not.toContain(snapshot); const unknown = playwrightModuleLoadFacts(snapshot, { name: 'synthetic-private-value', code: 'synthetic-private-value', message: "Cannot find package 'synthetic-private-value'" }); expect(unknown.errorType).toBe('unclassified'); expect(unknown.errorCode).toBe('unclassified'); expect(unknown.requestedModule).toBe('unclassified'); expect(JSON.stringify(unknown)).not.toContain('synthetic-private-value'); }); test('Keychain snapshots preserve exact quoted paths without shell parsing', () => { expect(parseKeychainPaths(' "/Users/runner/Library/Keychains/login.keychain-db"\n "/tmp/fixture keychain.keychain-db"\n')) .toEqual(['/Users/runner/Library/Keychains/login.keychain-db', '/tmp/fixture keychain.keychain-db']); for (const output of ['', 'not-json', '"relative-path"', '42', '"/valid/path"\ninvalid']) { expect(() => parseKeychainPaths(output)).toThrow(); } }); test('fresh users can have an empty user search list and no default Keychain', () => { const calls: string[][] = []; const snapshot = captureUserKeychains({ HOME: root }, [root], 1000, (args, timeout) => { calls.push(args); expect(timeout).toBeGreaterThan(0); expect(timeout).toBeLessThanOrEqual(1000); return args[0] === 'list-keychains' ? { status: 0, stdout: '', stderr: '' } : { status: 1, stdout: '', stderr: 'security: SecKeychainCopyDomainDefault user: A default keychain could not be found.\n' }; }); expect(snapshot).toEqual({ search: [], default: [] }); expect(calls).toEqual([['list-keychains', '-d', 'user'], ['default-keychain', '-d', 'user']]); }); test('computed Keychain timeouts reach real spawnSync as bounded integer milliseconds', () => { let reads = 0; const times = [0.125, 0.5, 5.75]; const clock = spyOn(performance, 'now').mockImplementation(() => times[Math.min(reads++, times.length - 1)]); const timeouts: number[] = []; const calls: string[][] = []; try { const snapshot = captureUserKeychains({ HOME: root }, [root], 10_000, (args, timeout) => { const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', '--no-macros', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, '-e', 'process.exit(0)'], { cwd: root, env: { HOME: root, PATH: path.dirname(process.execPath) }, encoding: 'utf8', timeout, }); expect(result.status).toBe(0); expect(result.error).toBeUndefined(); timeouts.push(timeout); calls.push(args); return result; }); expect(snapshot).toEqual({ search: [], default: [] }); expect(timeouts).toEqual([9999, 9994]); expect(calls).toEqual([['list-keychains', '-d', 'user'], ['default-keychain', '-d', 'user']]); } finally { clock.mockRestore(); } }); test('an expired or sub-millisecond Keychain budget never becomes an unbounded subprocess', () => { const clock = spyOn(performance, 'now').mockReturnValue(100); let commands = 0; try { for (const budget of [0.75, 0, -1, NaN, Infinity]) { expect(() => captureUserKeychains({ HOME: root }, [root], budget, () => { commands++; return { status: 0, stdout: '', stderr: '' }; })).toThrow('user_keychain_probe_timeout'); } expect(commands).toBe(0); } finally { clock.mockRestore(); } }); test('permission, securityd, and transport errors are never mistaken for no default Keychain', () => { for (const result of [ { status: 1, stdout: '', stderr: 'security: SecKeychainCopyDefault: User interaction is not allowed.' }, { status: 1, stdout: '', stderr: 'security: SecKeychainCopyDomainDefault system: A default keychain could not be found.' }, { status: 1, stdout: '', stderr: 'synthetic-private-error' }, { status: 0, stdout: '', stderr: 'synthetic-private-error' }, { status: 1, stdout: 'unexpected-data', stderr: 'security: SecKeychainCopyDefault: A default keychain could not be found.' }, { status: null, stdout: '', stderr: '', error: new Error('synthetic-private-error') }, ]) expect(() => parseDefaultKeychain(result)).toThrow('user_default_keychain_unavailable'); expect(parseDefaultKeychain({ status: 0, stdout: '', stderr: '' })).toEqual([]); expect(parseDefaultKeychain({ status: 1, stdout: '', stderr: 'security: SecKeychainCopyDefault: A default keychain could not be found.' })).toEqual([]); }); test('the snapshot boundary rejects System Keychain fallback and paths outside the owned home', () => { for (const file of ['/Library/Keychains/System.keychain', '/System/Library/Keychains/SystemRootCertificates.keychain', '/unowned/keychain']) { expect(() => captureUserKeychains({ HOME: root }, [root], 1000, args => ({ status: 0, stdout: args[0] === 'list-keychains' ? JSON.stringify(file) : '', stderr: '' }))) .toThrow('keychain_outside_owned_home_refused'); } }); test('a fresh Keychain home gains only owned standard directories, not a fabricated preference file', () => { const home = realpathSync(mkdtempSync(path.join(root, 'keychain-home-'))); expect(prepareKeychainHome(home)).toEqual({ before: { Library: false, 'Library/Preferences': false, 'Library/Keychains': false }, directoriesReady: true }); for (const directory of ['Library', 'Library/Preferences', 'Library/Keychains']) { expect(lstatSync(path.join(home, directory)).uid).toBe(process.getuid!()); expect(lstatSync(path.join(home, directory)).isDirectory()).toBe(true); } const preferences = path.join(home, 'Library/Preferences/com.apple.security.plist'); expect(existsSync(preferences)).toBe(false); expect(existsSync(path.join(home, 'Library/Safari'))).toBe(false); writeFileSync(preferences, 'opaque fixture preferences', { mode: 0o600 }); expect(prepareKeychainHome(home).before).toEqual({ Library: true, 'Library/Preferences': true, 'Library/Keychains': true }); expect(readFileSync(preferences, 'utf8')).toBe('opaque fixture preferences'); }); test('home preparation rejects a linked or unowned home without writing through it', () => { const home = realpathSync(mkdtempSync(path.join(root, 'linked-home-'))); const elsewhere = realpathSync(mkdtempSync(path.join(root, 'other-home-'))); symlinkSync(elsewhere, path.join(home, 'Library'), 'dir'); expect(() => prepareKeychainHome(home)).toThrow('keychain_home_unsafe'); expect(readdirSync(elsewhere)).toEqual([]); expect(() => prepareKeychainHome(elsewhere, process.getuid!() + 1)).toThrow('keychain_home_unsafe'); expect(readdirSync(elsewhere)).toEqual([]); }); for (const mismatch of ['search-empty', 'default-empty', 'search-path', 'default-path', 'search-error', 'default-error', 'read-mismatch', 'read-error']) { test(`Keychain diagnostics observe the explicit read independently of ${mismatch}`, () => { const directory = realpathSync(mkdtempSync(path.join(root, 'keychain-facts-'))); const keychain = path.join(directory, 'fixture.keychain-db'); const other = path.join(directory, 'other.keychain-db'); writeFileSync(keychain, 'opaque synthetic keychain', { mode: 0o600 }); writeFileSync(other, 'other synthetic keychain', { mode: 0o600 }); const expected = 'synthetic-private-equality-value'; const calls: string[][] = []; const facts = observeFixtureKeychain({ HOME: directory }, [directory], keychain, expected, 1000, (args, timeout) => { expect(Number.isInteger(timeout)).toBe(true); expect(timeout).toBeGreaterThan(0); calls.push(args); if (args[0] === 'list-keychains') return mismatch === 'search-error' ? { status: 1, stdout: '', stderr: 'synthetic-private-error' } : { status: 0, stdout: mismatch === 'search-empty' ? '' : JSON.stringify(mismatch === 'search-path' ? other : keychain), stderr: '' }; if (args[0] === 'default-keychain') return mismatch === 'default-error' ? { status: 1, stdout: '', stderr: 'synthetic-private-error' } : mismatch === 'default-empty' ? { status: 1, stdout: '', stderr: 'security: SecKeychainCopyDomainDefault user: A default keychain could not be found.' } : { status: 0, stdout: JSON.stringify(mismatch === 'default-path' ? other : keychain), stderr: '' }; if (mismatch === 'read-error') return { status: 1, stdout: '', stderr: 'synthetic-private-error' }; return { status: 0, stdout: mismatch === 'read-mismatch' ? 'other synthetic value' : expected + '\n', stderr: '' }; }); expect(calls).toEqual([['list-keychains', '-d', 'user'], ['default-keychain', '-d', 'user'], ['find-generic-password', '-s', 'Gstack Native Probe', '-w', keychain]]); expect(facts.searchCount).toBe(mismatch === 'search-error' ? null : mismatch === 'search-empty' ? 0 : 1); expect(facts.searchPathMatches).toBe(!mismatch.startsWith('search-')); expect(facts.defaultCount).toBe(mismatch === 'default-error' ? null : mismatch === 'default-empty' ? 0 : 1); expect(facts.defaultPathMatches).toBe(!mismatch.startsWith('default-')); expect(facts.explicitReadAttempted).toBe(true); expect(facts.explicitReadSucceeded).toBe(mismatch !== 'read-error'); expect(facts.explicitReadMatches).toBe(!mismatch.startsWith('read-')); expect(JSON.stringify(facts)).not.toContain(expected); expect(JSON.stringify(facts)).not.toContain('synthetic-private-error'); expect(JSON.stringify(facts)).not.toContain(keychain); }); } test('Dia Keychain diagnostics distinguish both HOME environments without configuring the shadow home', () => { const directory = realpathSync(mkdtempSync(path.join(root, 'dual-keychain-'))); const registered = path.join(directory, 'registered'); const shadow = path.join(directory, 'shadow'); mkdirSync(registered); mkdirSync(shadow); prepareKeychainHome(registered); writeFileSync(path.join(registered, 'Library/Preferences/com.apple.security.plist'), 'opaque private fixture preference'); const keychain = path.join(directory, 'fixture.keychain-db'); writeFileSync(keychain, 'opaque synthetic keychain', { mode: 0o600 }); const expected = 'synthetic-private-dual-home-value'; const environments = { keychainHome: { HOME: registered }, profileHome: { HOME: shadow } }; const calls: Array<{ home: string; args: string[] }> = []; const observed = observeDiaKeychainEnvironments(environments, [directory], keychain, expected, 20_000, (env, args, timeout) => { expect(Number.isInteger(timeout)).toBe(true); expect(timeout).toBeGreaterThan(0); expect(timeout).toBeLessThanOrEqual(10_000); calls.push({ home: env.HOME, args }); if (args[0] === 'list-keychains') return { status: 0, stdout: env.HOME === registered ? JSON.stringify(keychain) : '', stderr: '' }; if (args[0] === 'default-keychain') return env.HOME === registered ? { status: 0, stdout: JSON.stringify(keychain), stderr: '' } : { status: 1, stdout: '', stderr: 'security: SecKeychainCopyDomainDefault user: A default keychain could not be found.' }; expect(args.slice(0, 4)).toEqual(['find-generic-password', '-s', 'Dia Safe Storage', '-w']); return { status: 0, stdout: expected, stderr: '' }; }); expect(observed.firstFailure).toEqual({ environment: 'profileHome', check: 'search_path' }); expect(observed.environments.keychainHome).toMatchObject({ searchCount: 1, searchPathMatches: true, defaultCount: 1, defaultPathMatches: true, explicitReadMatches: true, lookupReadAttempted: true, lookupReadMatches: true, preferencesDirectoryExists: true, preferencesFileExists: true }); expect(observed.environments.profileHome).toMatchObject({ searchCount: 0, searchPathMatches: false, defaultCount: 0, defaultPathMatches: false, explicitReadMatches: true, lookupReadAttempted: false, preferencesDirectoryExists: false, preferencesFileExists: false }); expect(calls).toHaveLength(7); expect(calls.filter(call => call.home === shadow)).toHaveLength(3); expect(calls.some(call => call.args[0] === 'default-keychain' && call.args.includes('-s'))).toBe(false); expect(existsSync(path.join(shadow, 'Library'))).toBe(false); const serialized = JSON.stringify(observed); for (const privateValue of [expected, registered, shadow, keychain, 'opaque private']) expect(serialized).not.toContain(privateValue); }); for (const failure of ['search', 'default', 'explicit_read']) { test(`Dia diagnostic refuses unqualified lookup when the owned ${failure} proof fails`, () => { const directory = realpathSync(mkdtempSync(path.join(root, 'guarded-keychain-'))); const keychain = path.join(directory, 'fixture.keychain-db'); writeFileSync(keychain, 'opaque synthetic keychain', { mode: 0o600 }); let implicitCalls = 0; const result = observeFixtureKeychain({ HOME: directory }, [directory], keychain, 'expected-private-value', 1000, args => { if (args[0] === 'list-keychains') return { status: 0, stdout: failure === 'search' ? '' : JSON.stringify(keychain), stderr: '' }; if (args[0] === 'default-keychain') return { status: 0, stdout: failure === 'default' ? '' : JSON.stringify(keychain), stderr: '' }; if (args.length === 4) implicitCalls++; return { status: 0, stdout: failure === 'explicit_read' ? 'wrong-private-value' : 'expected-private-value', stderr: '' }; }, 'Dia Safe Storage'); expect(result.explicitReadAttempted).toBe(true); expect(result.lookupReadAttempted).toBe(false); expect(implicitCalls).toBe(0); }); } test('both Keychain environments remain observable while the first original validation failure is retained', () => { const directory = realpathSync(mkdtempSync(path.join(root, 'keychain-failures-'))); const keychain = path.join(directory, 'fixture.keychain-db'); writeFileSync(keychain, 'opaque synthetic keychain', { mode: 0o600 }); const environments = { keychainHome: { HOME: directory }, profileHome: { HOME: directory, MARKER: 'shadow' } }; for (const keychainSearchFails of [true, false]) { const homesObserved = new Set>(); const observed = observeDiaKeychainEnvironments(environments, [directory], keychain, 'expected-private-value', 20_000, (env, args) => { homesObserved.add(env); if (args[0] === 'list-keychains') return { status: 0, stdout: env === environments.profileHome || keychainSearchFails ? '' : JSON.stringify(keychain), stderr: '' }; if (args[0] === 'default-keychain') return { status: 1, stdout: '', stderr: 'private-default-error' }; return { status: 0, stdout: 'wrong-private-value', stderr: '' }; }); expect(homesObserved.size).toBe(2); expect(observed.firstFailure).toEqual({ environment: keychainSearchFails ? 'keychainHome' : 'profileHome', check: 'search_path' }); expect(observed.environments.keychainHome).toMatchObject({ defaultCount: null, explicitReadAttempted: true, explicitReadMatches: false }); expect(observed.environments.profileHome).toMatchObject({ defaultCount: null, explicitReadAttempted: true, explicitReadMatches: false }); expect(JSON.stringify(observed)).not.toContain('private-default-error'); expect(JSON.stringify(observed)).not.toContain('wrong-private-value'); } }); test('Dia environment validation preserves successful and explicit-read-only outcomes', () => { const directory = realpathSync(mkdtempSync(path.join(root, 'keychain-outcomes-'))); const keychain = path.join(directory, 'fixture.keychain-db'); writeFileSync(keychain, 'opaque synthetic keychain', { mode: 0o600 }); const environments = { keychainHome: { HOME: directory }, profileHome: { HOME: directory } }; for (const readMismatch of [false, true]) { const observed = observeDiaKeychainEnvironments(environments, [directory], keychain, 'expected-private-value', 20_000, (env, args) => { if (args[0] === 'list-keychains' || args[0] === 'default-keychain') return { status: 0, stdout: JSON.stringify(keychain), stderr: '' }; return { status: 0, stdout: readMismatch && env === environments.keychainHome ? 'wrong-private-value' : 'expected-private-value', stderr: '' }; }); if (readMismatch) expect(observed.firstFailure).toEqual({ environment: 'keychainHome', check: 'explicit_read' }); else expect(observed.firstFailure).toBeUndefined(); expect(observed.environments.profileHome).toMatchObject({ searchPathMatches: true, defaultPathMatches: true, explicitReadMatches: true, lookupReadAttempted: true, lookupReadMatches: true }); } }); test('an exhausted dual-environment diagnostic budget launches no Keychain commands', () => { const directory = realpathSync(mkdtempSync(path.join(root, 'keychain-budget-'))); const keychain = path.join(directory, 'fixture.keychain-db'); writeFileSync(keychain, 'opaque synthetic keychain', { mode: 0o600 }); let calls = 0; const observed = observeDiaKeychainEnvironments({ keychainHome: { HOME: directory }, profileHome: { HOME: directory } }, [directory], keychain, 'expected-private-value', 0, () => { calls++; throw new Error('must_not_spawn'); }); expect(calls).toBe(0); expect(observed.environments.keychainHome).toMatchObject({ searchCount: null, defaultCount: null, explicitReadAttempted: false, lookupReadAttempted: false }); expect(observed.environments.profileHome).toMatchObject({ searchCount: null, defaultCount: null, explicitReadAttempted: false, lookupReadAttempted: false }); expect(observed.firstFailure).toEqual({ environment: 'keychainHome', check: 'search_path' }); }); test('an originally absent default is restored by deleting only the created fixture, never by a null default setter', () => { expect(fixtureKeychainRestoreCommands({ search: [], default: [] }, '/owned/fixture.keychain-db', true)).toEqual([ ['delete-keychain', '/owned/fixture.keychain-db'], ['list-keychains', '-d', 'user', '-s'], ]); expect(fixtureKeychainRestoreCommands({ search: [], default: [] }, '/owned/fixture.keychain-db', false)).toEqual([ ['list-keychains', '-d', 'user', '-s'], ]); }); test('an existing default is restored before deleting the fixture, and preexisting fixture references are refused', () => { const before = { search: ['/owned/prior.keychain-db', '/owned/other.keychain-db'], default: ['/owned/prior.keychain-db'] }; expect(fixtureKeychainRestoreCommands(before, '/owned/fixture.keychain-db', true)).toEqual([ ['default-keychain', '-d', 'user', '-s', '/owned/prior.keychain-db'], ['delete-keychain', '/owned/fixture.keychain-db'], ['list-keychains', '-d', 'user', '-s', '/owned/prior.keychain-db', '/owned/other.keychain-db'], ]); expect(() => fixtureKeychainRestoreCommands(before, '/owned/prior.keychain-db', true)).toThrow('fixture_keychain_not_fresh'); }); test('fresh account cleanup requires the same GUID, UID, private group, and registered home', () => { const identity = { guid: 'A38AC39B-5960-4F0C-B02F-C32A4F625B33', uid: 23456, gid: 23456, home: '/private/tmp/fixture/home' }; const record = parseDirectoryRecord(`GeneratedUID: ${identity.guid}\nUniqueID: ${identity.uid}\nPrimaryGroupID: ${identity.gid}\nNFSHomeDirectory: ${identity.home}\n`); expect(ownsFreshAccount(record, identity)).toBe(true); for (const key of ['GeneratedUID', 'UniqueID', 'PrimaryGroupID', 'NFSHomeDirectory']) { expect(ownsFreshAccount({ ...record, [key]: 'different' }, identity)).toBe(false); } expect(() => parseDirectoryRecord('UniqueID: 23456\nUniqueID: 501')).toThrow('invalid_directory_record'); }); test('user-domain absence requires an explicit matching-domain response, not an arbitrary command failure', () => { for (const status of [64, 113]) { const absent = classifyUserDomain(23456, { status, stdout: '', stderr: 'Bad request.\nCould not find domain for user uid: 23456\n' }); expect(absent.state).toBe('absent'); expect(absent.uid).toBe(23456); } for (const result of [ { status: 1, stdout: '', stderr: 'sudo: a password is required' }, { status: 113, stdout: '', stderr: 'Could not find domain for user uid: 23457' }, { status: 113, stdout: '', stderr: 'Could not find domain for user gui: 23456' }, { status: 113, stdout: '', stderr: 'synthetic-private-error' }, { status: null, stdout: '', stderr: '', error: new Error('synthetic-private-error') }, ]) expect(classifyUserDomain(23456, result).state).toBe('unavailable'); expect(() => classifyUserDomain(0, { status: 0, stdout: '', stderr: '' })).toThrow('invalid_fresh_user_domain'); }); test('user-domain inspection records only safe state facts and notices an unexpected GUI domain', () => { const present = classifyUserDomain(23456, { status: 0, stdout: 'user/23456 = {\n type = user\n synthetic-private-value\n}', stderr: '' }); expect(present).toMatchObject({ uid: 23456, state: 'present', hasGuiDomain: false, exitCode: 0 }); expect(JSON.stringify(present)).not.toContain('synthetic-private-value'); expect(classifyUserDomain(23456, { status: 0, stdout: 'user/23456 = {\n subdomains = { gui/23456 }\n}', stderr: '' }).hasGuiDomain).toBe(true); expect(classifyUserDomain(23456, { status: 0, stdout: 'user/23456 = {\n session = Aqua\n}', stderr: '' }).hasGuiDomain).toBe(true); expect(classifyUserDomain(23456, { status: 0, stdout: 'user/501 = { }', stderr: '' }).state).toBe('unavailable'); }); test('domain structure retains counts and creator correlation, never arbitrary labels or environment values', () => { const stdout = `user/23456 = { \ttype = user \thandle = 23456 \tactive count = 3 \ton-demand count = 0 \tservice count = 2 \tactive service count = 1 \texternal activation count = 0 \tin-progress bootstraps = 0 \tpended requests = 0 \tcreator = launchctl.4567 \tcreator euid = 0 \tenvironment = { \t\tsynthetic-private-variable => synthetic-private-value \t\tservice count = 999 \t} \tservices = { \t\t234 0 synthetic-private-service \t\t0 0 synthetic-private-job \t} \tsubdomains = { \t\tsynthetic-private-child \t} \tunmanaged processes = { \t} \tendpoints = {} }`; const observation = classifyUserDomain(23456, { status: 0, stdout, stderr: '' }, 4567); expect(observation.structure).toMatchObject({ complete: true, type: 'user', handleMatchesUid: true, creator: 'launchctl', creatorIsProbe: true, counts: { 'service count': 2, 'active service count': 1, 'creator euid': 0, 'in-progress bootstraps': 0 }, sectionNonemptyLines: { services: 2, subdomains: 1, 'unmanaged processes': 0, endpoints: 0, jobs: null } }); expect(JSON.stringify(observation)).not.toContain('synthetic-private'); expect(JSON.stringify(observation)).not.toContain('4567'); expect(classifyUserDomain(23456, { status: 0, stdout, stderr: '' }, 4568).structure?.creatorIsProbe).toBe(false); expect(classifyUserDomain(23456, { status: 0, stdout, stderr: '' }).structure?.creatorIsProbe).toBeNull(); expect(classifyUserDomain(23456, { status: 0, stdout: stdout.replace('launchctl.4567', 'synthetic-private-creator.4567'), stderr: '' }, 4567).structure?.creator).toBe('other'); }); test('missing, malformed, duplicate, and incomplete domain facts never become empty-baseline evidence', () => { const minimal = 'user/23456 = {\n\ttype = user\n\thandle = 23456\n}'; const observation = classifyUserDomain(23456, { status: 0, stdout: minimal, stderr: '' }); expect(observation.structure?.counts['service count']).toBeNull(); expect(observation.structure?.sectionNonemptyLines.services).toBeNull(); expect(observation.structure?.creatorIsProbe).toBeNull(); for (const stdout of [minimal.slice(0, -1), minimal.replace('\thandle', '\ttype = user\n\thandle'), minimal.replace('\thandle = 23456', '\tservices = {\n\t\tsynthetic-private-service')]) { expect(classifyUserDomain(23456, { status: 0, stdout, stderr: '' }).structure?.complete).toBe(false); } for (const value of ['-1', 'NaN', '1.5', '9007199254740992', 'synthetic-private-value']) { const stdout = minimal.replace('\thandle', '\tservice count = ' + value + '\n\thandle'); expect(classifyUserDomain(23456, { status: 0, stdout, stderr: '' }).structure?.counts['service count']).toBeNull(); } }); test('known bracketed creator syntax is matched exactly while unsupported spellings remain unknown', () => { const fixture = (creator: string) => classifyUserDomain(23456, { status: 0, stdout: `user/23456 = {\n\ttype = user\n\tcreator = ${creator}\n}`, stderr: '' }, 4567); expect(fixture('launchctl[4567]').structure?.creatorIsProbe).toBe(true); expect(fixture('launchctl[4568]').structure?.creatorIsProbe).toBe(false); for (const creator of ['launchctl(4567)', 'launchctl[4567] extra', 'other[4567]', 'launchctl[0]', 'launchctl[4567)']) { expect(fixture(creator).structure?.creatorIsProbe).toBeNull(); expect(fixture(creator).structure?.creator).toBe('other'); } }); test('parent-domain observation extracts only candidate UID membership from complete subdomains', () => { for (const [header, target, gui] of [ ['system', 'user/23456', 'gui/23456'], ['com.apple.xpc.launchd.domain.system', 'com.apple.xpc.launchd.domain.user.23456', 'com.apple.xpc.launchd.user.domain.23456.100007.Aqua'], ]) { const stdout = `${header} = {\n\ttype = system\n\tsubdomains = {\n\t\t${target}\n\t\t${gui}\n\t\tpid/15\n\t}\n}`; expect(classifyParentDomain(23456, { status: 0, stdout, stderr: '' })).toMatchObject({ state: 'present', parseStage: 'parsed', subdomainCount: 3, matchingUserDomains: 1, matchingGuiDomains: 1, unrecognizedEntries: 0 }); } const stdout = `system = { \ttype = system \tenvironment = { \t\tsynthetic-private-name => user/23456 \t\tsubdomains = { \t\t\tuser/23456 \t\t} \t} \tsubdomains = { \t\tuser/234560 \t\tgui/501 \t\tpid/123 \t\tsession/100007 \t\tcom.apple.xpc.launchd.domain.pid.synthetic-private-process.23456 \t} \tservices = { \t\tsynthetic-private-service-user/23456 \t} }`; const observation = classifyParentDomain(23456, { status: 0, stdout, stderr: '' }); expect(observation).toMatchObject({ state: 'absent', parseStage: 'parsed', subdomainCount: 5, matchingUserDomains: 0 }); expect(JSON.stringify(observation)).not.toContain('synthetic-private'); expect(JSON.stringify(observation)).not.toContain('234560'); }); test('parent-domain absence refuses incomplete, missing, duplicate, nested-only, or unknown subdomain output', () => { const empty = 'system = {\n\ttype = system\n\tsubdomains = {}\n}'; expect(classifyParentDomain(23456, { status: 0, stdout: empty, stderr: '' }).state).toBe('absent'); for (const stdout of [empty.slice(0, -1), empty.replace('system = {', 'user/23456 = {'), empty.replace('\tsubdomains = {}\n', ''), empty.replace('\tsubdomains = {}', '\tsubdomains = {}\n\tsubdomains = {}'), empty.replace('type = system', 'type = user'), empty.replace('\tsubdomains = {}', '\tenvironment = {\n\t\tsubdomains = {}\n\t}'), empty.replace('subdomains = {}', 'subdomains = {\n\t\tunknown-private-domain\n\t}'), empty.replace('subdomains = {}', 'subdomains = {\n\t\tuser/23456\n\t\tunknown-private-domain\n\t}')]) { const observation = classifyParentDomain(23456, { status: 0, stdout, stderr: '' }); expect(observation.state).toBe('unavailable'); expect(JSON.stringify(observation)).not.toContain('private-domain'); } expect(classifyParentDomain(23456, { status: 1, stdout: empty, stderr: 'synthetic-private-error' }).state).toBe('unavailable'); expect(classifyParentDomain(23456, { status: 0, stdout: empty, stderr: '', error: new Error('synthetic-private-error') }).state).toBe('unavailable'); expect(classifyParentDomain(23456, { status: 0, stdout: empty + ' '.repeat(1024 * 1024), stderr: '' }).parseStage).toBe('oversized'); }); test('the registered parent observer queries only the existing system domain under its original bounds', () => { const env = { HOME: root, PATH: '/usr/bin:/bin' }; const observation = inspectParentDomain(23456, performance.now() + 10_000, env, ((command: string, args: string[], options: any) => { expect(command).toBe('/usr/bin/sudo'); expect(args).toEqual(['-n', '/bin/launchctl', 'print', 'system']); expect(options.env).toBe(env); expect(options.maxBuffer).toBe(1024 * 1024); expect(Number.isInteger(options.timeout)).toBe(true); expect(options.timeout).toBeGreaterThan(0); expect(options.timeout).toBeLessThanOrEqual(3_000); return { status: 0, stdout: 'system = {\n\ttype = system\n\tsubdomains = {}\n}', stderr: '' }; }) as typeof spawnSync); expect(observation.state).toBe('absent'); const never = (() => { throw new Error('must_not_spawn'); }) as typeof spawnSync; expect(() => inspectParentDomain(501, performance.now() + 10_000, {}, never)).toThrow('invalid_fresh_user_domain'); for (const deadline of [0, NaN, Infinity]) expect(() => inspectParentDomain(23456, deadline, {}, never)).toThrow('fresh_launcher_deadline'); }); test('the registered UID observer filters numeric global ps rows without resolving an unregistered account', () => { const env = { HOME: root, PATH: '/usr/bin:/bin' }; const observation = inspectUidProcesses(23456, performance.now() + 10_000, env, ((command: string, args: string[], options: any) => { expect(command).toBe('/bin/ps'); expect(args).toEqual(['-axo', 'uid=,pid=,ppid=,state=,ucomm=']); expect(options.env).toBe(env); expect(options.maxBuffer).toBe(128 * 1024); expect(Number.isInteger(options.timeout)).toBe(true); expect(options.timeout).toBeGreaterThan(0); expect(options.timeout).toBeLessThanOrEqual(2_000); return { status: 0, stdout: '501 300 1 S synthetic-private-name\n23456 400 1 S distnoted\n', stderr: '' }; }) as typeof spawnSync); expect(observation).toMatchObject({ available: true, count: 1, processes: [{ pid: 400, ppid: 1, state: 'S', basename: 'distnoted' }] }); expect(JSON.stringify(observation)).not.toContain('synthetic-private'); expect(inspectUidProcesses(23456, performance.now() + 10_000, {}, (() => ({ status: 0, stdout: '501 300 1 S synthetic-private-name\n', stderr: '' })) as typeof spawnSync)).toMatchObject({ available: true, count: 0, processes: [] }); for (const result of [ { status: 1, stdout: '', stderr: '' }, { status: 0, stdout: '', stderr: '' }, { status: 0, stdout: 'truncated-private-row', stderr: '' }, { status: null, stdout: null, stderr: null, error: new Error('synthetic-private-error') }, ]) expect(inspectUidProcesses(23456, performance.now() + 10_000, {}, (() => result) as typeof spawnSync)).toEqual({ available: false }); }); test('numeric UID process filtering runs through the real global process table', () => { const observation = inspectUidProcesses(23456, performance.now() + 10_000, { HOME: root, PATH: '/usr/bin:/bin' }); expect(observation.available).toBe(true); expect('count' in observation).toBe(true); }); test('the registered domain probe binds its creator check to the shell exec PID and bounds the query', () => { const env = { PATH: '/usr/bin:/bin', HOME: root }; let called = false; const observation = inspectUserDomain(23456, performance.now() + 10_000, env, ((command: string, args: string[], options: any) => { called = true; expect(command).toBe('/usr/bin/sudo'); expect(args).toEqual(['-n', '/bin/sh', '-c', 'printf "GSTACK_DIA_DOMAIN_PROBE_PID=%s\\n" "$$"; exec /bin/launchctl print "$1"', 'gstack-dia-domain-probe', 'user/23456']); expect(options.env).toBe(env); expect(options.timeout).toBeGreaterThan(0); expect(options.timeout).toBeLessThanOrEqual(3_000); expect(Number.isInteger(options.timeout)).toBe(true); expect(options.maxBuffer).toBe(1024 * 1024); return { status: 0, stdout: 'GSTACK_DIA_DOMAIN_PROBE_PID=4567\nuser/23456 = {\n\ttype = user\n\tcreator = launchctl.4567\n}', stderr: '' }; }) as typeof spawnSync); expect(called).toBe(true); expect(observation.state).toBe('present'); expect(observation.structure?.creatorIsProbe).toBe(true); expect(JSON.stringify(observation)).not.toContain('GSTACK_DIA_DOMAIN_PROBE_PID'); }); test('domain probe failures preserve unknown state instead of manufacturing absence or creator ownership', () => { for (const result of [ { status: 0, stdout: 'user/23456 = {\n\ttype = user\n}', stderr: '' }, { status: 0, stdout: 'GSTACK_DIA_DOMAIN_PROBE_PID=9007199254740992\nuser/23456 = {\n\ttype = user\n}', stderr: '' }, { status: 113, stdout: '', stderr: 'Could not find domain for user uid: 23456' }, { status: null, stdout: null, stderr: null, error: new Error('synthetic-private-error') }, ]) { const observation = inspectUserDomain(23456, performance.now() + 10_000, {}, (() => result) as typeof spawnSync); expect(observation.state).toBe('unavailable'); expect(observation.structure).toBeUndefined(); expect(JSON.stringify(observation)).not.toContain('synthetic-private'); } expect(inspectUserDomain(23456, performance.now() + 10_000, {}, (() => ({ status: 113, stdout: 'GSTACK_DIA_DOMAIN_PROBE_PID=4567\n', stderr: 'Could not find domain for user uid: 23456' })) as typeof spawnSync).state).toBe('absent'); const never = (() => { throw new Error('must_not_spawn'); }) as typeof spawnSync; expect(() => inspectUserDomain(0, performance.now() + 10_000, {}, never)).toThrow('invalid_fresh_user_domain'); for (const deadline of [0, NaN, Infinity]) expect(() => inspectUserDomain(23456, deadline, {}, never)).toThrow('fresh_launcher_deadline'); }); test('the domain probe PID protocol survives real shell exec without logging the PID or child output', () => { const fixture = path.join(root, 'domain-probe.cjs'); writeFileSync(fixture, 'process.stdout.write("user/23456 = {\\n\\ttype = user\\n\\tcreator = launchctl." + process.pid + "\\n}");'); const observation = inspectUserDomain(23456, performance.now() + 10_000, { PATH: '/usr/bin:/bin', HOME: root }, ((_command: string, args: string[], options: any) => spawnSync('/bin/sh', ['-c', args[3].replace('/bin/launchctl print "$1"', '"$1" "$2"'), args[4], process.execPath, fixture], { ...options, timeout: 3_000 })) as typeof spawnSync); expect(observation.state).toBe('present'); expect(observation.structure?.creatorIsProbe).toBe(true); expect(observation.structure?.complete).toBe(true); expect(JSON.stringify(observation)).not.toContain('GSTACK_DIA_DOMAIN_PROBE_PID'); }); test('user-domain teardown is bound to the new account and its pre-creation absence proof', () => { const account = { guid: 'A38AC39B-5960-4F0C-B02F-C32A4F625B33', uid: 23456, gid: 23456, home: '/private/tmp/dn-fixture/home' }; const record = { GeneratedUID: account.guid, UniqueID: '23456', PrimaryGroupID: '23456', NFSHomeDirectory: account.home }; const proof = classifyParentDomain(23456, { status: 0, stdout: 'system = {\n\ttype = system\n\tsubdomains = {}\n}', stderr: '' }); const current = classifyParentDomain(23456, { status: 0, stdout: 'system = {\n\ttype = system\n\tsubdomains = {\n\t\tuser/23456\n\t}\n}', stderr: '' }); expect(ownedUserDomainTarget(record, account, proof, current, 501)).toBe('user/23456'); expect(ownedUserDomainTarget(record, account, proof, proof, 501)).toBeNull(); for (const key of ['GeneratedUID', 'UniqueID', 'PrimaryGroupID', 'NFSHomeDirectory']) { expect(() => ownedUserDomainTarget({ ...record, [key]: 'changed' }, account, proof, current, 501)).toThrow('fresh_user_domain_ownership_unconfirmed'); } expect(() => ownedUserDomainTarget(record, account, undefined, current, 501)).toThrow('fresh_user_domain_ownership_unconfirmed'); expect(() => ownedUserDomainTarget(record, account, { ...proof, state: 'present' }, current, 501)).toThrow('fresh_user_domain_ownership_unconfirmed'); expect(() => ownedUserDomainTarget(record, account, { ...proof, uid: 23457 }, current, 501)).toThrow('fresh_user_domain_ownership_unconfirmed'); expect(() => ownedUserDomainTarget(record, account, proof, current, 23456)).toThrow('fresh_user_domain_ownership_unconfirmed'); expect(() => ownedUserDomainTarget(record, account, proof, current, NaN)).toThrow('fresh_user_domain_ownership_unconfirmed'); for (const changed of [{ ...current, matchingGuiDomains: 1 }, { ...current, uid: 23457 }, { ...current, state: 'unavailable' as const }, { ...current, duplicateEntries: 1 }, { ...current, matchingUserDomains: 2 }, { ...current, parseStage: 'missing_subdomains' }, { ...current, unrecognizedEntries: 1 }]) { expect(() => ownedUserDomainTarget(record, account, proof, changed, 501)).toThrow('fresh_user_domain_ownership_unconfirmed'); } }); test('duplicated native subdomain entries and equivalent aliases never authorize teardown or absence', () => { for (const entries of [ ['user/23456', 'user/23456'], ['user/23456', 'com.apple.xpc.launchd.domain.user.23456'], ['user/23456', 'gui/23456', 'gui/23456'], ['user/501', 'user/501'], ['user/23456', 'pid/22', 'com.apple.xpc.launchd.domain.pid.synthetic-private-process.22'], ]) { const observation = classifyParentDomain(23456, { status: 0, stdout: 'system = {\n\ttype = system\n\tsubdomains = {\n' + entries.map(entry => '\t\t' + entry).join('\n') + '\n\t}\n}', stderr: '' }); expect(observation.duplicateEntries).toBe(1); expect(observation.parseStage).toBe('duplicate_subdomain'); expect(observation.state).toBe('unavailable'); expect(passiveUserDomainState(observation, 23456)).toBe('unavailable'); } }); test('passive absence and presence require consistent complete facts with no GUI association', () => { const absent = classifyParentDomain(23456, { status: 0, stdout: 'system = {\n\ttype = system\n\tsubdomains = {}\n}', stderr: '' }); expect(passiveUserDomainState(absent, 23456)).toBe('absent'); for (const changed of [undefined, { ...absent, uid: 23457 }, { ...absent, matchingUserDomains: 1 }, { ...absent, matchingGuiDomains: 1 }, { ...absent, exitCode: 1 }, { ...absent, duplicateEntries: 1 }, { ...absent, unrecognizedEntries: 1 }, { ...absent, state: 'present' as const }]) { expect(passiveUserDomainState(changed, 23456)).toBe('unavailable'); } const gui = classifyParentDomain(23456, { status: 0, stdout: 'system = {\n\ttype = system\n\tsubdomains = {\n\t\tuser/23456\n\t\tgui/23456\n\t}\n}', stderr: '' }); expect(passiveUserDomainState(gui, 23456)).toBe('unavailable'); const oldTargetObservation = classifyUserDomain(23456, { status: 113, stdout: '', stderr: 'Could not find domain for user uid: 23456' }); expect(passiveUserDomainState(oldTargetObservation as any, 23456)).toBe('unavailable'); }); test('directory UID inventory retains occupied candidate IDs and refuses malformed or empty listings', () => { expect([...parseDirectoryIds('root 0\nfixture 20000\nnobody -2\nother fixture 20001 \n')]).toEqual([0, 20000, -2, 20001]); for (const value of ['', 'malformed', 'root 0\nfixture unknown\n', 'root 0\nfixture 9007199254740992\n']) { expect(() => parseDirectoryIds(value)).toThrow(); } }); test('normal qualification never invokes the materializing target-domain diagnostic', () => { const implementation = runFreshAccountQualification.toString(); expect(implementation).toContain('inspectParentDomain'); expect(implementation).toContain('passiveUserDomainState'); expect(implementation).not.toMatch(/inspectUserDomain|probeUserDomain|classifyUserDomain/); expect(implementation).not.toMatch(/['"]print['"],\s*['"](?:user|gui)\//); }); test('launchd receives a one-shot fresh-user security session without an Aqua or auto-login workaround', () => { const account: any = { label: 'ai.gstack.dia.fixture', account: 'gsdiafixture', bun: '/private/tmp/fixture/bin/bun', snapshot: '/private/tmp/fixture/repo', configFile: '/private/tmp/fixture/account.json', environment: { HOME: '/private/tmp/fixture/home', CI: 'true' } }; const definition = freshLaunchDefinition(account); expect(definition.UserName).toBe(account.account); expect(definition.GroupName).toBe(account.account); expect(definition.SessionCreate).toBe(true); expect(definition.RunAtLoad).toBe(true); expect(definition.KeepAlive).toBe(false); expect(definition.Umask).toBe(63); expect(definition.ProgramArguments[0]).toBe(account.bun); expect(definition.ProgramArguments).toContain('--fresh-worker'); expect(definition.StandardOutPath).toBe('/dev/null'); expect(definition.StandardErrorPath).toBe('/dev/null'); expect(JSON.stringify(definition)).not.toMatch(/Aqua|autoLogin|LoginWindow|GITHUB_TOKEN/); }); test('service cleanup binds the exact system label, executable, user, and private group', () => { const owner = { label: 'ai.gstack.dia.fixture', bun: '/private/tmp/fixture/bin/bun', account: 'gsdiafixture' }; const state = `system/${owner.label} = {\n program = ${owner.bun}\n username = ${owner.account}\n group = ${owner.account}\n}`; expect(ownsLaunchService(state, owner)).toBe(true); for (const replacement of [state.replace('system/', 'gui/501/'), state.replace(owner.label, 'unrelated'), state.replace(owner.bun, '/unrelated/bun'), state.replace('username = gsdiafixture', 'username = runner'), state.replace('group = gsdiafixture', 'group = staff')]) expect(ownsLaunchService(replacement, owner)).toBe(false); }); test('the fresh-account launcher refuses this non-authorized invocation without privileged work', () => { const launcher = path.resolve(import.meta.dir, '../../.github/scripts/run-dia-native-qualification.ts'); const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', '--no-macros', `--config=${process.platform === 'win32' ? 'NUL' : '/dev/null'}`, launcher], { cwd: root, env: { HOME: root, PATH: path.dirname(process.execPath) }, encoding: 'utf8', timeout: 10_000, }); expect(result.status).toBe(2); expect(result.stderr).toBe(''); expect(JSON.parse(result.stdout).reason).toBe('fresh_account_launcher_preflight_failed'); }); for (const shape of ['normal', 'safe-link', 'traversal', 'absolute', 'escape-link', 'symlink-parent', 'symlink-dotdot', 'hardlink', 'case-collision', 'unicode-collision', 'device']) { test(`archive preflight classifies ${shape} before extraction`, () => { const python = Bun.which('python3'); if (!python) throw new Error('Python 3 is required for archive boundary tests'); const archive = path.join(root, shape + '.tar'); const create = spawnSync(python, ['-I', '-c', ` import io, sys, tarfile shape, file = sys.argv[1:] with tarfile.open(file, 'w') as out: def entry(name, kind=tarfile.REGTYPE, link=''): item = tarfile.TarInfo(name); item.type = kind; item.linkname = link if kind == tarfile.REGTYPE: item.size = 1; out.addfile(item, io.BytesIO(b'x')) else: out.addfile(item) if shape == 'normal': entry('src/file.ts') elif shape == 'safe-link': entry('target'); entry('link', tarfile.SYMTYPE, 'target') elif shape == 'traversal': entry('../outside') elif shape == 'absolute': entry('/outside') elif shape == 'escape-link': entry('link', tarfile.SYMTYPE, '../outside') elif shape == 'symlink-parent': entry('link', tarfile.SYMTYPE, 'target'); entry('link/child') elif shape == 'symlink-dotdot': entry('b', tarfile.SYMTYPE, '.'); entry('a/link', tarfile.SYMTYPE, '../b/..') elif shape == 'hardlink': entry('target'); entry('link', tarfile.LNKTYPE, 'target') elif shape == 'case-collision': entry('File'); entry('file') elif shape == 'unicode-collision': entry('Caf' + chr(233)); entry('Cafe' + chr(769)) elif shape == 'device': entry('device', tarfile.CHRTYPE) `, shape, archive], { encoding: 'utf8', timeout: 10_000 }); expect(create.status).toBe(0); const checked = spawnSync(python, ['-I', '-c', ARCHIVE_CHECK, archive], { encoding: 'utf8', timeout: 10_000 }); expect(checked.status).toBe(['normal', 'safe-link'].includes(shape) ? 0 : 2); expect(JSON.parse(checked.stdout).valid).toBe(['normal', 'safe-link'].includes(shape)); expect(checked.stderr).toBe(''); }); } test('receipt collection rejects symlinks and an unrelated owner without printing content', () => { const python = Bun.which('python3'); if (!python) throw new Error('Python 3 is required for receipt boundary tests'); const directory = realpathSync(mkdtempSync(path.join(root, 'receipts-'))); const file = path.join(directory, 'receipt.json'); writeFileSync(file, JSON.stringify({ status: 'incomplete', reason: 'synthetic_fixture' }), { mode: 0o600 }); chmodSync(file, 0o600); const uid = process.getuid!(); const read = (selected: string, owner: number) => spawnSync(python, ['-I', '-c', PRIVATE_RECEIPT_READ, selected, String(owner), directory], { encoding: 'utf8', timeout: 10_000, }); const valid = read(file, uid); expect(valid.status).toBe(0); expect(JSON.parse(valid.stdout).status).toBe('incomplete'); const wrong = read(file, uid + 1); expect(wrong.status).toBe(2); expect(wrong.stdout).toBe(''); const link = path.join(directory, 'linked.json'); symlinkSync(file, link); const linked = read(link, uid); expect(linked.status).toBe(2); expect(linked.stdout).toBe(''); }); test('receipt collection rejects linked ancestors and incomplete JSON without disclosing content', () => { const python = Bun.which('python3'); if (!python) throw new Error('Python 3 is required for receipt boundary tests'); const directory = realpathSync(mkdtempSync(path.join(root, 'receipt-ancestors-'))); const nested = path.join(directory, 'nested'); mkdirSync(nested, { mode: 0o700 }); const file = path.join(nested, 'receipt.json'); writePrivateReceipt(file, { status: 'incomplete' }); const linked = path.join(directory, 'linked'); symlinkSync(nested, linked, 'dir'); const read = (selected: string) => spawnSync(python, ['-I', '-c', PRIVATE_RECEIPT_READ, selected, String(process.getuid!()), directory], { encoding: 'utf8', timeout: 3_000, }); expect(read(file).status).toBe(0); const rejected = read(path.join(linked, 'receipt.json')); expect(rejected.status).toBe(2); expect(rejected.stdout).toBe(''); writeFileSync(file, '{"synthetic-private-value":'); const partial = read(file); expect(partial.status).toBe(2); expect(partial.stdout).toBe(''); }); test('receipt publication is private, atomic, and refuses unintended overwrites or symlink targets', () => { const directory = realpathSync(mkdtempSync(path.join(root, 'atomic-receipt-'))); const file = path.join(directory, 'receipt.json'); writePrivateReceipt(file, { status: 'incomplete', phase: 'first' }); expect(lstatSync(file).mode & 0o777).toBe(0o600); expect(() => writePrivateReceipt(file, { status: 'passed' })).toThrow(); expect(JSON.parse(readFileSync(file, 'utf8')).phase).toBe('first'); writePrivateReceipt(file, { status: 'incomplete', phase: 'second' }, true); expect(JSON.parse(readFileSync(file, 'utf8')).phase).toBe('second'); const link = path.join(directory, 'linked.json'); symlinkSync(file, link); expect(() => writePrivateReceipt(link, { status: 'passed' }, true)).toThrow('unsafe_receipt_replacement'); expect(JSON.parse(readFileSync(file, 'utf8')).phase).toBe('second'); expect(readdirSync(directory).some(name => name.startsWith('.dia-receipt-'))).toBe(false); }); test('an atomic owner-checked diagnostic receipt is readable while its producer is still alive', async () => { const python = Bun.which('python3'); if (!python) throw new Error('Python 3 is required for receipt boundary tests'); const directory = realpathSync(mkdtempSync(path.join(root, 'live-receipt-'))); const file = path.join(directory, 'receipt.json'); const module = pathToFileURL(path.resolve(import.meta.dir, '../../.github/scripts/qualify-dia-macos.ts')).href; const child = Bun.spawn([process.execPath, '--no-env-file', '--no-install', '--no-macros', '--config=/dev/null', '-e', ` const { writePrivateReceipt } = await import(${JSON.stringify(module)}); writePrivateReceipt(${JSON.stringify(file)}, { status: 'incomplete', reason: 'worker_diagnostic', cleanup: { complete: false } }); console.log('ready'); setInterval(() => {}, 1000); `], { cwd: directory, env: { HOME: directory, PATH: path.dirname(process.execPath) }, stdout: 'pipe', stderr: 'pipe' }); try { const reader = child.stdout.getReader(); expect(new TextDecoder().decode((await reader.read()).value)).toContain('ready'); reader.releaseLock(); expect(child.exitCode).toBeNull(); const result = spawnSync(python, ['-I', '-c', PRIVATE_RECEIPT_READ, file, String(process.getuid!()), directory], { encoding: 'utf8', timeout: 3_000, }); expect(result.status).toBe(0); expect(JSON.parse(result.stdout)).toEqual({ status: 'incomplete', reason: 'worker_diagnostic', cleanup: { complete: false } }); expect(child.exitCode).toBeNull(); } finally { child.kill(); await child.exited; } }); test('UID process diagnostics preserve zombie/service/browser distinctions without argv or arbitrary names', () => { const facts = uidProcessFacts('20000 301 1 Ss cfprefsd\n20000 302 1 Z bun\n20000 303 301 R Google Chrome for Testing\n20000 304 1 S synthetic-private-value\n501 999 1 S security\n', 20000); expect(facts).toEqual({ available: true, count: 4, zombies: 1, live: 3, truncated: false, processes: [ { pid: 301, ppid: 1, state: 'S', basename: 'cfprefsd' }, { pid: 302, ppid: 1, state: 'Z', basename: 'bun' }, { pid: 303, ppid: 301, state: 'R', basename: 'Google Chrome for Testing' }, { pid: 304, ppid: 1, state: 'S', basename: 'other' }, ] }); expect(JSON.stringify(facts)).not.toContain('synthetic-private-value'); expect(JSON.stringify(facts)).not.toContain('999'); expect(uidProcessFacts('20000 401 1 S cfprefsd\n', 20000).processes[0].pid).toBe(401); expect(uidProcessFacts('20000 402 1 Z+ bun\n', 20000)).toMatchObject({ count: 1, zombies: 1, live: 0 }); expect(() => uidProcessFacts('malformed synthetic-private-value', 20000)).toThrow('invalid_uid_process_snapshot'); expect(uidProcessFacts(Array.from({ length: 80 }, (_, index) => `20000 ${1000 + index} 1 S bun`).join('\n'), 20000)) .toMatchObject({ count: 80, truncated: true }); expect(uidProcessFacts(Array.from({ length: 80 }, (_, index) => `20000 ${1000 + index} 1 S bun`).join('\n'), 20000).processes).toHaveLength(64); }); test('captured passing inner receipts cannot qualify a run with incomplete cleanup', () => { const cleanup = { serviceStopped: true, userDomainStopped: true, userProcessesStopped: true, accountRemoved: true, groupRemoved: true, stagingRemoved: true }; expect(freshQualificationPassed(0, 'passed', 'passed', cleanup)).toBe(true); for (const key of Object.keys(cleanup)) expect(freshQualificationPassed(0, 'passed', 'passed', { ...cleanup, [key]: false })).toBe(false); expect(freshQualificationPassed(0, 'passed', 'passed', {})).toBe(false); expect(freshQualificationPassed(2, 'passed', 'passed', cleanup)).toBe(false); expect(freshQualificationPassed(0, 'incomplete', 'passed', cleanup)).toBe(false); }); test('the registered spawn observer records the actual owned child and closes launch admission', async () => { const profile = path.join(root, 'profile'); const childProcess = require('node:child_process'); const original = childProcess.spawn; const observer = observeBrowserLaunches(new Map([[process.execPath, profile]])); const args = ['--no-env-file', '--no-install', '-e', 'process.exit(0)', '--', '--remote-debugging-pipe', '--user-data-dir=' + profile]; const options = { cwd: root, detached: true, shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'], env: { PATH: path.dirname(process.execPath), HOME: root, ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}) } }; try { const child = childProcess.spawn(process.execPath, args, options); const code = await new Promise(resolve => child.once('close', resolve)); expect(code).toBe(0); expect(observer.children).toHaveLength(1); expect(observer.children[0].process).toBe(child); expect(observer.children[0].pid).toBe(child.pid); observer.stop(); expect(() => childProcess.spawn(process.execPath, args, options)).toThrow('browser_launch_policy_rejected'); expect(observer.children).toHaveLength(1); } finally { observer.restore(); } expect(childProcess.spawn).toBe(original); }); test('the owned spawn stderr observer preserves the consumer stream and removes only its own listeners', async () => { const childProcess = require('node:child_process'); const original = childProcess.spawn; const profile = path.join(root, 'stderr-observer-profile'); const observer = observeBrowserLaunches(new Map([[process.execPath, profile]])); const text = 'DevTools remote debugging requires a non-default data directory.\nsynthetic-private-value\n'; try { const child = childProcess.spawn(process.execPath, ['--no-env-file', '--no-install', '-e', `process.stderr.write(${JSON.stringify(text)})`, '--', '--remote-debugging-pipe', '--user-data-dir=' + profile], { detached: true, shell: false, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'], env: { HOME: root, PATH: path.dirname(process.execPath) }, }); const chunks: Buffer[] = []; const consumer = (chunk: Buffer) => { chunks.push(chunk); }; child.stderr.on('data', consumer); const code = await new Promise(resolve => child.once('close', resolve)); expect(code).toBe(0); expect(Buffer.concat(chunks).toString()).toBe(text); const facts = browserStderrFacts(observer.children); expect(facts).toHaveLength(1); expect(facts[0]).toMatchObject({ available: true, bytesSeen: Buffer.byteLength(text), ended: true, reasonCounts: { default_profile_policy: 1 } }); expect(JSON.stringify(facts)).not.toContain('synthetic-private'); observer.restore(); expect(child.stderr.listeners('data')).toContain(consumer); expect(childProcess.spawn).toBe(original); } finally { observer.restore(); } }); test('an owned killed child is joined before proving its process group absent', async () => { const childProcess = require('node:child_process'); const profile = path.join(root, 'reaping-profile'); const observer = observeBrowserLaunches(new Map([[process.execPath, profile]])); let timer: ReturnType | undefined; try { const child = childProcess.spawn(process.execPath, ['--no-env-file', '--no-install', '-e', 'process.stdout.write("ready\\n"); setInterval(() => {}, 1000)', '--', '--remote-debugging-pipe', '--user-data-dir=' + profile], { detached: true, shell: false, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'], env: { HOME: root, PATH: path.dirname(process.execPath) }, }); await Promise.race([new Promise(resolve => child.stdout.once('data', resolve)), new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('fixture_startup_timeout')), 3000); })]); clearTimeout(timer); const owned = observer.children[0]; expect(owned.closeObserved).toBe(false); const until = performance.now() + 5_000; const facts: Record = { signalSent: false, absenceConfirmed: false }; await stopOwnedBrowserGroup(owned, until, facts); expect(owned.closeObserved).toBe(true); expect(facts).toMatchObject({ signalSent: true, childCloseObserved: true, absenceConfirmed: true }); let absent = false; while (performance.now() < until) { try { process.kill(-owned.pid, 0); } catch (error: any) { if (error.code === 'ESRCH') { absent = true; break; } throw error; } await Bun.sleep(10); } expect(absent).toBe(true); expect(browserRootFacts(observer.children)[0]).toMatchObject({ closeObserved: true, signal: 'SIGKILL' }); } finally { clearTimeout(timer); for (const owned of observer.children) { if (owned.closeObserved) continue; try { process.kill(-owned.pid, 'SIGKILL'); } catch {} await joinOwnedBrowserClose(owned, performance.now() + 5_000); } observer.restore(); } }); test('the pinned Playwright launch is captured with the observer installed after importing Playwright', async () => { const { chromium } = await import('playwright'); expect(require('playwright/package.json').version).toBe('1.62.1'); const executable = realpathSync(chromium.executablePath()); const profile = path.join(root, 'playwright-profile'); const observer = observeBrowserLaunches(new Map([[executable, profile]])); let context: Awaited> | undefined; try { const nativeOptions = nativeDiaLaunchOptions(executable, { HOME: root, PATH: path.dirname(process.execPath) + ':/usr/bin:/bin:/usr/sbin:/sbin', ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}), }); expect(nativeOptions.chromiumSandbox).toBe(true); context = await chromium.launchPersistentContext(profile, nativeOptions); expect(observer.children).toHaveLength(1); expect(observer.children[0].executable).toBe(executable); expect(observer.children[0].pid).toBeGreaterThan(1); expect(browserRootFacts(observer.children)).toEqual([{ pid: observer.children[0].pid, exitCode: null, closeObserved: false, signal: null }]); expect(browserStartupFacts(context.pages().map(page => page.url()), 'http://127.0.0.1:8123').allowed).toBe(true); expect(browserStderrFacts(observer.children)[0].available).toBe(true); expect(browserStderrFacts(observer.children)[0].bytesInspected).toBeLessThanOrEqual(65_536); expect(observer.attempts).toHaveLength(1); expect(observer.attempts[0]).toEqual({ admissionOpen: true, argumentsArray: true, pipeFlag: true, profileArgumentCount: 1, expectedProfile: true, detached: true, shellDisabled: true, stdioCount: 5, extraPipeDescriptors: true, headlessFlag: true, blankStartupArgument: true, tcpDebuggingFlag: false, mockKeychainFlag: false, passwordStoreFlag: false, firstRunSuppressed: false, sandboxRequired: process.platform === 'darwin', sandboxDisablingFlag: false }); expect(JSON.stringify(observer.attempts)).not.toContain(executable); expect(JSON.stringify(observer.attempts)).not.toContain(profile); const page = context.pages()[0] ?? await context.newPage(); await page.setContent('
isolated browser smoke
'); expect(await page.locator('#fixture').innerText()).toBe('isolated browser smoke'); await context.close(); context = undefined; expect(observer.children[0].process.exitCode !== null || observer.children[0].process.signalCode !== null).toBe(true); } finally { observer.stop(); await context?.close(); observer.restore(); } }, 40_000); for (const forbidden of ['--remote-debugging-port=9222', '--use-mock-keychain', '--password-store=basic', '--no-first-run']) { test(`the actual spawn callback rejects ${forbidden} before process creation`, () => { const profile = path.join(root, 'profile'); const childProcess = require('node:child_process'); const observer = observeBrowserLaunches(new Map([[process.execPath, profile]])); try { expect(() => childProcess.spawn(process.execPath, ['--remote-debugging-pipe', '--user-data-dir=' + profile, forbidden], { detached: true, shell: false, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] })).toThrow('browser_launch_policy_rejected'); expect(observer.children).toHaveLength(0); } finally { observer.restore(); } }); } test('the spawn callback rejects an unowned profile, shell expansion, and a shared process group', () => { const profile = path.join(root, 'profile'); const childProcess = require('node:child_process'); const observer = observeBrowserLaunches(new Map([[process.execPath, profile]])); try { for (const [selected, detached, shell] of [[path.join(root, 'other'), true, false], [profile, false, false], [profile, true, true]]) { expect(() => childProcess.spawn(process.execPath, ['--remote-debugging-pipe', '--user-data-dir=' + selected], { detached, shell, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] })) .toThrow('browser_launch_policy_rejected'); } expect(() => childProcess.spawn(process.execPath, ['--remote-debugging-pipe', '--user-data-dir=' + profile, '--user-data-dir=' + path.join(root, 'other')], { detached: true, shell: false, stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] })).toThrow('browser_launch_policy_rejected'); expect(() => childProcess.spawn(process.execPath, ['--remote-debugging-pipe', '--user-data-dir=' + profile], { detached: true, shell: false, stdio: 'inherit' })).toThrow('browser_launch_policy_rejected'); expect(observer.children).toHaveLength(0); } finally { observer.restore(); } }); });