mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-28 15:41:57 +02:00
v1.90.0.0 feat: make browser cookie imports explicit and safe (#2964)
* fix(browse): prepare reliable cookie import wave for validation * ci: sequence quality and behavior for validation branch * fix(browse): isolate Windows qualification and preserve native diagnostics * test(browse): cover cookie workflow quality and isolate Windows user paths * test(browse): trace native member startup and initialize fresh folders * fix(browse): keep Windows member stdin alive through EOF * fix(browse): latch native timeouts and compare contained Edge startup * test(browse): verify native version metadata and actual Windows argv * test(browse): qualify Dia import on isolated macOS CI * fix(browse): require picker origin for session mutations * fix(browse): bound credential reads through stream completion * test(browse): inspect owned Windows process arguments natively * test(evals): preserve passing coverage during cookie repair reruns * test(browse): isolate Dia qualification in a fresh macOS account * test(browse): pass bounded integer timeouts to native Mac probes * test(browse): distinguish Windows profile initialization from containment * test(browse): await descendant pipe readiness before parent exit * test(browse): initialize and restore isolated macOS Keychain state * test(browse): initialize Windows fixture folders before qualification * test(ci): pin the same Node runtime across Windows checks * test(browse): distinguish native macOS browser preflight stages * test(browse): isolate Windows descendant console lifetime * test(browse): preserve native receipts and identify fixture lock holders * test(browse): prepare dependency resolution before native Mac worker startup * test(ci): include lock and close checks in native diagnostics * test(browse): preserve native owner probe stages and subprocess deadlines * fix(browse): classify Chromium profile-in-use exit precisely * test(browse): retain Mac qualification evidence through cleanup failures * test(browse): bound Mac fixture paths and retire its owned user domain * test(browse): accept vanished fixture entries without weakening cleanup * test(browse): identify probe-created macOS user domains safely * test(browse): observe Mac user domains without targeting them first * test(browse): use passive fresh-user ownership throughout Mac qualification * test(browse): distinguish profile and registered-home Keychain lookups * test(browse): qualify Dia under one registered account home * test(browse): identify Dia startup and owned process-group failures * test(browse): classify bounded Dia startup diagnostics without leaking output * fix(test): preserve native Mac sandboxing and reap owned browser children * fix(browse): preserve Chromium sandboxing for native profile imports * test(browse): inspect signed Mach-O architecture without launching Xcode tools * test(browse): sample pending Dia startup and reap on all cleanup paths * test(browse): compare protected Dia launches in fresh Bun and Node accounts * test(browse): inspect isolated Mac GUI readiness without browser access * v1.90.0.0 fix: bind cookie picker actions to their document * test: validate cookie guards and fit nested launch fixtures * ci: configure the bundled Chromium sandbox helper * fix(browse): classify Playwright authentication timeouts * test: retain bounded Windows lifecycle diagnostics * test(cso): reuse bounded NTFS precision candidates * test(review): handle explicit preservation choices safely * test(browse): remove owned fixture directories with explicit primitives * test(review): distinguish descriptive reuse from edit commitments * test: admit only the approved unscored cookie workflow refusal * test: keep the Office Hours judge mock export-complete * fix: keep dependency-free CI planners independent of the model SDK * test: observe the exact holder after a native fixture unlink failure * fix: start seeded PTY observations at owned readiness * test: acquire identity-bound Windows deletion admission before profile resets * test: preserve qualified Git index bits without authorizing mutations
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, mkdtempSync, writeFileSync } from 'node:fs';
|
||||
import { createRequire } from 'node:module';
|
||||
import { release, tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { nativeBrowserPaths } from '../src/cookie-import-native';
|
||||
import { nativeCookieEnvironment, probeNativeCookieMember } from '../src/cookie-import-native-worker';
|
||||
import { hashNativeFile, nativeCodeHashes, nativeCodeMatches, NATIVE_BROWSER_VERSION_COMMAND, NATIVE_QUALIFICATION_DATA } from '../src/cookie-import-native-integrity';
|
||||
import { createNativeCookieJob, NativeCookieJobError, nativeCookieDiagnostic, type NativeCookieDiagnostic, type NativeCookieJob } from '../src/cookie-import-native-job';
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
console.error('Native cookie qualification requires Windows; no cases ran and no qualification was issued.');
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.env.GITHUB_ACTIONS !== 'true' || process.env.CI !== 'true') {
|
||||
console.error('Native cookie qualification requires a disposable GitHub Actions runner and never uses an existing browser profile.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const output = mkdtempSync(path.join(process.argv[2] || tmpdir(), 'cookie-native-qualification-'));
|
||||
function incomplete(reason: string, diagnostic?: NativeCookieDiagnostic): never {
|
||||
const receipt = { status: 'incomplete', reason, ...(diagnostic ? { diagnostic } : {}), counts: { pass: 0, fail: 0, skip: 0 }, activation: 'No build was qualified; production extraction remains disabled.' };
|
||||
writeFileSync(path.join(output, 'qualification.json'), JSON.stringify(receipt, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
console.log(JSON.stringify({ ...receipt, artifactDirectory: output }));
|
||||
process.exit(2);
|
||||
}
|
||||
if (Bun.version !== '1.4.0') incomplete('bun_1_4_0_required');
|
||||
let emptyJob: NativeCookieJob | undefined;
|
||||
try {
|
||||
emptyJob = await createNativeCookieJob();
|
||||
if (emptyJob.activeProcesses() !== 0) throw new NativeCookieJobError('job_query');
|
||||
emptyJob.terminate();
|
||||
if (emptyJob.activeProcesses() !== 0) throw new NativeCookieJobError('job_query');
|
||||
emptyJob.close();
|
||||
const preflight = { status: 'passed', activeProcesses: 0 };
|
||||
writeFileSync(path.join(output, 'job-preflight.json'), JSON.stringify(preflight, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
console.log(JSON.stringify({ nativeJobPreflight: preflight }));
|
||||
} catch (error) {
|
||||
const diagnostic = nativeCookieDiagnostic(error, 'job_create');
|
||||
try { emptyJob?.close(); } catch {}
|
||||
writeFileSync(path.join(output, 'job-preflight.json'), JSON.stringify({ status: 'failed', diagnostic }, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
incomplete('native_job_preflight_failed', diagnostic);
|
||||
}
|
||||
const memberProbe = await probeNativeCookieMember();
|
||||
const memberPreflight = 'cookies' in memberProbe ? { status: 'passed', activeProcesses: 0 } : { status: 'failed', error: memberProbe.error, diagnostic: memberProbe.diagnostic };
|
||||
writeFileSync(path.join(output, 'member-preflight.json'), JSON.stringify(memberPreflight, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
console.log(JSON.stringify({ nativeMemberPreflight: memberPreflight }));
|
||||
if ('error' in memberProbe) incomplete('native_member_preflight_failed', memberProbe.diagnostic);
|
||||
const root = path.resolve(import.meta.dir, '../..');
|
||||
let sourceHashes: Record<string, string>;
|
||||
try {
|
||||
sourceHashes = await nativeCodeHashes(root, Date.now() + 25_000);
|
||||
} catch {
|
||||
incomplete('source_inputs_unavailable_or_timed_out');
|
||||
}
|
||||
const environment = nativeCookieEnvironment(process.env);
|
||||
const browser = nativeBrowserPaths('Edge', environment);
|
||||
const executable = browser.executables.find(existsSync);
|
||||
const node = Bun.which('node');
|
||||
if (!executable || !node) incomplete('node_or_edge_not_installed');
|
||||
if (existsSync(browser.userDataDir)) incomplete('existing_default_profile_refused');
|
||||
const executableSha256 = await hashNativeFile(executable, Date.now() + 25_000);
|
||||
const nodeProbe = spawnSync(node, ['-p', 'JSON.stringify({ version: process.version, architecture: process.arch })'], { env: environment, encoding: 'utf8', timeout: 10_000, windowsHide: true });
|
||||
if (nodeProbe.status !== 0) incomplete('node_runtime_preflight_failed');
|
||||
const nodeInfo = JSON.parse(nodeProbe.stdout);
|
||||
if (nodeInfo.architecture !== process.arch || !['x64', 'arm64'].includes(process.arch)) incomplete('runtime_architecture_mismatch');
|
||||
const require = createRequire(import.meta.url);
|
||||
const playwrightVersion = require('playwright/package.json').version;
|
||||
if (playwrightVersion !== '1.62.1') incomplete('playwright_1_62_1_required');
|
||||
const versionProbe = spawnSync(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'), [
|
||||
'-NoProfile', '-NonInteractive', '-Command', NATIVE_BROWSER_VERSION_COMMAND,
|
||||
], { env: { ...environment, GSTACK_QUALIFY_BROWSER_EXE: executable }, encoding: 'utf8', timeout: 10_000, windowsHide: true });
|
||||
const versionErrorCode = (versionProbe.error as NodeJS.ErrnoException | undefined)?.code;
|
||||
const versionMetadata = {
|
||||
exitCode: versionProbe.status,
|
||||
signal: versionProbe.signal,
|
||||
spawnError: versionProbe.error ? (['ENOENT', 'EACCES', 'EPERM', 'ETIMEDOUT'].includes(versionErrorCode || '') ? versionErrorCode : 'spawn_failed') : undefined,
|
||||
stdoutBytes: Buffer.byteLength(versionProbe.stdout || ''),
|
||||
stderrBytes: Buffer.byteLength(versionProbe.stderr || ''),
|
||||
versionValid: /^\d+(?:\.\d+){2,3}$/.test((versionProbe.stdout || '').trim()),
|
||||
};
|
||||
writeFileSync(path.join(output, 'browser-version-preflight.json'), JSON.stringify(versionMetadata, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
console.log(JSON.stringify({ nativeBrowserVersionPreflight: versionMetadata }));
|
||||
if (versionProbe.status !== 0 || !versionMetadata.versionValid) incomplete('browser_version_preflight_failed');
|
||||
const result = spawnSync(process.execPath, ['--no-env-file', '--no-install', '--no-macros', '--config=NUL', 'test', 'browse/test/cookie-import-native-job.test.ts', '--test-name-pattern', '^native Windows process qualification'], {
|
||||
cwd: root,
|
||||
env: { ...environment, CI: 'true', GITHUB_ACTIONS: 'true', GSTACK_COOKIE_NATIVE_DEFAULT_FIXTURE: '1', NO_COLOR: '1' },
|
||||
encoding: 'utf8',
|
||||
timeout: 300_000,
|
||||
maxBuffer: 16 * 1024 * 1024,
|
||||
windowsHide: true,
|
||||
});
|
||||
const log = `${result.stdout || ''}\n${result.stderr || ''}`;
|
||||
writeFileSync(path.join(output, 'qualification.log'), log, { mode: 0o600, flag: 'wx' });
|
||||
const count = (kind: string) => Number([...log.matchAll(new RegExp(`(?:^|\\n)\\s*(\\d+) ${kind}\\b`, 'g'))].at(-1)?.[1] ?? 0);
|
||||
const counts = { pass: count('pass'), fail: count('fail'), skip: count('skip') };
|
||||
let inputsUnchanged = false;
|
||||
try {
|
||||
const deadline = Date.now() + 25_000;
|
||||
inputsUnchanged = await hashNativeFile(executable, deadline) === executableSha256 && nativeCodeMatches(sourceHashes, await nativeCodeHashes(root, deadline));
|
||||
} catch {}
|
||||
const passed = result.status === 0 && !result.error && counts.pass === 7 && counts.fail === 0 && counts.skip === 0 && inputsUnchanged && !existsSync(browser.userDataDir);
|
||||
const receipt = {
|
||||
status: passed ? 'passed' : 'failed',
|
||||
scope: 'Edge default-profile v20 extraction, pipe transport, profile identity, and owned-process cleanup',
|
||||
qualifiedBuild: {
|
||||
browserName: 'Edge',
|
||||
architecture: nodeInfo.architecture,
|
||||
windowsRelease: release(),
|
||||
executableSha256,
|
||||
nodeVersion: nodeInfo.version,
|
||||
bunVersion: Bun.version,
|
||||
playwrightVersion,
|
||||
sourceHashes,
|
||||
},
|
||||
browserVersion: versionProbe.stdout.trim(),
|
||||
supervisorArchitecture: process.arch,
|
||||
jobPreflight: { status: 'passed', activeProcesses: 0 },
|
||||
memberPreflight,
|
||||
counts,
|
||||
inputsUnchanged,
|
||||
sourceHashes,
|
||||
activationData: NATIVE_QUALIFICATION_DATA,
|
||||
activation: 'Review this receipt and add only this exact qualifiedBuild to the separate activation data; the runner never enables extraction and activation does not modify the recorded code hashes.',
|
||||
};
|
||||
writeFileSync(path.join(output, 'qualification.json'), JSON.stringify(receipt, null, 2) + '\n', { mode: 0o600, flag: 'wx' });
|
||||
console.log(JSON.stringify({ status: receipt.status, counts, artifactDirectory: output }));
|
||||
process.exitCode = passed ? 0 : 1;
|
||||
Reference in New Issue
Block a user