Files
gstack/browse/test/fixtures/native-cookie-file-owners.ts
T
Garry Tan a84b0b5b6d v1.90.0.0 feat: make browser cookie imports explicit and safe (#2964)
* fix(browse): prepare reliable cookie import wave for validation

* ci: sequence quality and behavior for validation branch

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* test(browse): isolate Windows descendant console lifetime

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* test: validate cookie guards and fit nested launch fixtures

* ci: configure the bundled Chromium sandbox helper

* fix(browse): classify Playwright authentication timeouts

* test: retain bounded Windows lifecycle diagnostics

* test(cso): reuse bounded NTFS precision candidates

* test(review): handle explicit preservation choices safely

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

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

* test: admit only the approved unscored cookie workflow refusal

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

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

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

* fix: start seeded PTY observations at owned readiness

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

* test: preserve qualified Git index bits without authorizing mutations
2026-09-25 12:06:45 -04:00

133 lines
7.0 KiB
TypeScript

import { lstatSync, realpathSync } from 'node:fs';
import path from 'node:path';
import { dlopen, FFIType, ptr } from 'bun:ffi';
let stage = 'platform';
class FileOwnerProbeError extends Error {
errorCode?: string;
constructor(public stage: string, error: unknown) {
super('owner_query_failed');
const code = (error as NodeJS.ErrnoException | undefined)?.code;
if (['EPERM', 'EACCES', 'EBUSY', 'ENOENT', 'EINVAL', 'ENOTDIR', 'ENAMETOOLONG', 'ETIMEDOUT'].includes(code || '')) this.errorCode = code;
}
}
function inspect() {
if (process.platform !== 'win32' || !['x64', 'arm64'].includes(process.arch)) return { available: false, reason: 'not_windows' };
stage = 'input_decode';
const input = JSON.parse(Buffer.from(process.argv[2], 'base64').toString('utf8'));
if (typeof input.root !== 'string' || typeof input.file !== 'string' || !Number.isSafeInteger(input.testPid)) return { available: false, reason: 'invalid_input' };
stage = 'resolve_root';
const root = realpathSync(input.root);
stage = 'resolve_file';
const file = realpathSync(input.file);
const relative = path.relative(root, file);
stage = 'file_stat';
const initial = lstatSync(input.file, { bigint: true });
if (relative.startsWith('..') || path.isAbsolute(relative) || !initial.isFile() || initial.isSymbolicLink()
|| relative !== path.relative(root, path.resolve(input.file))) return { available: false, reason: 'outside_owned_fixture' };
if (input.expectedIdentity !== undefined && (input.expectedIdentity?.dev !== initial.dev.toString()
|| input.expectedIdentity?.ino !== initial.ino.toString())) return { available: false, reason: 'failed_object_identity_changed' };
const unchanged = () => {
stage = 'file_recheck';
const current = lstatSync(input.file, { bigint: true });
return current.isFile() && !current.isSymbolicLink() && current.dev === initial.dev && current.ino === initial.ino
&& realpathSync(input.file) === file;
};
stage = 'load_restart_manager';
const restart = dlopen(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'rstrtmgr.dll'), {
RmStartSession: { args: [FFIType.ptr, FFIType.u32, FFIType.ptr], returns: FFIType.u32 },
RmRegisterResources: { args: [FFIType.u32, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.u32, FFIType.ptr], returns: FFIType.u32 },
RmGetList: { args: [FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.u32 },
RmEndSession: { args: [FFIType.u32], returns: FFIType.u32 },
});
stage = 'load_kernel';
const kernel = dlopen('kernel32.dll', {
OpenProcess: { args: [FFIType.u32, FFIType.i32, FFIType.u32], returns: FFIType.u64 },
GetProcessTimes: { args: [FFIType.u64, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
QueryFullProcessImageNameW: { args: [FFIType.u64, FFIType.u32, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
CloseHandle: { args: [FFIType.u64], returns: FFIType.i32 },
});
let session: number | undefined;
try {
const sessionBuffer = Buffer.alloc(4);
const key = Buffer.alloc(66);
stage = 'session_start';
let status = restart.symbols.RmStartSession(ptr(sessionBuffer), 0, ptr(key));
if (status !== 0) return { available: false, reason: 'session_start', status };
session = sessionBuffer.readUInt32LE(0);
const wideFile = Buffer.from(file + '\0', 'utf16le');
const names = Buffer.alloc(8);
names.writeBigUInt64LE(BigInt(ptr(wideFile)));
stage = 'register_file';
status = restart.symbols.RmRegisterResources(session, 1, ptr(names), 0, null, 0, null);
if (status !== 0) return { available: false, reason: 'register_file', status };
const needed = Buffer.alloc(4);
const count = Buffer.alloc(4);
const rebootReasons = Buffer.alloc(4);
stage = 'owner_count';
status = restart.symbols.RmGetList(session, ptr(needed), ptr(count), null, ptr(rebootReasons));
if (status === 0 && needed.readUInt32LE(0) === 0) return unchanged()
? { available: true, owners: [], rebootReasons: rebootReasons.readUInt32LE(0) }
: { available: false, reason: 'failed_object_identity_changed' };
const entries = needed.readUInt32LE(0);
if (status !== 234 || entries < 1 || entries > 64) return { available: false, reason: 'owner_count', status, entries };
const information = Buffer.alloc(entries * 668);
count.writeUInt32LE(entries);
stage = 'owner_list';
status = restart.symbols.RmGetList(session, ptr(needed), ptr(count), ptr(information), ptr(rebootReasons));
const returned = count.readUInt32LE(0);
if (status !== 0 || returned > entries) return { available: false, reason: 'owner_list', status };
const owners = [];
stage = 'owner_identity';
for (let index = 0; index < returned; index++) {
const offset = index * 668;
const pid = information.readUInt32LE(offset);
const recordedStart = information.readBigUInt64LE(offset + 4);
let image = 'unavailable';
let creationMatched = false;
const handle = kernel.symbols.OpenProcess(0x1000, 0, pid);
if (handle) {
try {
const times = Buffer.alloc(32);
const timeAddress = ptr(times);
if (kernel.symbols.GetProcessTimes(handle, timeAddress, timeAddress + 8, timeAddress + 16, timeAddress + 24)) {
creationMatched = times.readBigUInt64LE(0) === recordedStart;
}
if (creationMatched) {
const imageBuffer = Buffer.alloc(65536);
const imageLength = Buffer.alloc(4);
imageLength.writeUInt32LE(32768);
if (kernel.symbols.QueryFullProcessImageNameW(handle, 0, ptr(imageBuffer), ptr(imageLength))) {
const chars = imageLength.readUInt32LE(0);
const name = chars <= 32768 ? path.basename(imageBuffer.subarray(0, chars * 2).toString('utf16le')).toLowerCase() : '';
image = ['bun.exe', 'node.exe', 'msedge.exe', 'msmpeng.exe', 'mssense.exe', 'dllhost.exe', 'explorer.exe', 'powershell.exe', 'pwsh.exe', 'svchost.exe', 'conhost.exe'].includes(name) ? name : 'other';
}
}
} finally {
kernel.symbols.CloseHandle(handle);
}
}
owners.push({ pid, image, creationMatched, isTestHost: creationMatched && pid === input.testPid, applicationType: information.readUInt32LE(offset + 652) });
}
return unchanged() ? { available: true, owners, rebootReasons: rebootReasons.readUInt32LE(0) }
: { available: false, reason: 'failed_object_identity_changed' };
} catch (error) {
throw new FileOwnerProbeError(stage, error);
} finally {
stage = 'session_end';
if (session !== undefined) restart.symbols.RmEndSession(session);
stage = 'library_close';
kernel.close(); restart.close();
}
}
let result: object;
try { result = inspect(); }
catch (error) {
const failure = error instanceof FileOwnerProbeError ? error : new FileOwnerProbeError(stage, error);
result = { available: false, reason: 'owner_query_failed', stage: failure.stage, errorCode: failure.errorCode };
}
process.stdout.write(JSON.stringify(result) + '\n', () => process.exit(0));