blob: c4c9a08766a220e45c4d3bbed1eeb330687a0957 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { execFile, spawn } from 'node:child_process';
import { randomBytes } from 'node:crypto';
import { existsSync } from 'node:fs';
import { mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from 'node:fs/promises';
import { homedir, tmpdir } from 'node:os';
import { basename, dirname, join, resolve } from 'node:path';
import { promisify } from 'node:util';
import { fileURLToPath, pathToFileURL } from 'node:url';
const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const execFileAsync = promisify(execFile);
export const WINDOWS_SANDBOX_PHASE4_MATRIX = Object.freeze([
{
category: 'filesystem_aliases',
evidence: ['junction admission', 'multi-hard-link admission', 'outside-root read denial'],
},
{
category: 'network_channels',
evidence: ['TCP connect denial'],
},
{ category: 'ipc', evidence: ['host named-pipe denial', 'bounded inherited handle list'] },
{
category: 'descendants',
evidence: [
'descendant creation denied or AppContainer token retained',
'kill-on-close Job membership',
],
},
{
category: 'environment',
evidence: ['ambient host secret omitted', 'closed allowlisted environment'],
},
{
category: 'credentials',
evidence: ['outside credential-file denial', 'ambient secret omission'],
},
{ category: 'registry', evidence: ['host HKCU value denial'] },
{
category: 'lifecycle_failures',
evidence: [
'client cancellation',
'Runtime Host parent death',
'64-launch concurrency soak',
'quarantined identity non-reuse',
],
},
]);
export const WINDOWS_SANDBOX_DEFERRED_HARDENING = Object.freeze([
'Authenticode identity verification',
'direct Credential Manager and DPAPI probes',
'inbound listener enforcement',
'UDP channel enforcement',
'no-Win32k mitigation',
'dedicated window-station and clipboard isolation',
'power-loss automatic recovery',
]);
function assertCondition(condition, message) {
if (!condition) throw new Error(message);
}
/**
* Runs real filesystem-worker operations (write, read, glob, a fail-closed
* grep and a denied outside write) through FilesystemWorkerClient against the
* PACKAGED Windows app: the packaged broker executable enforces the
* AppContainer boundary, the packaged Electron executable is the worker
* runtime (ELECTRON_RUN_AS_NODE, exactly as production launches it) and the
* packaged `resources\workers\filesystem-worker.js` is the worker bundle.
* Only the driver (client + launch-spec code) comes from the repository
* build, because the packaged copy lives inside app.asar which plain node
* cannot import; every executed artifact is the shipped one.
*/
export async function verifyWindowsSandboxWorkerE2E(appDirectoryPath) {
const appDirectory = resolve(appDirectoryPath);
const appExecutable = join(appDirectory, 'Maka.exe');
const resourcesPath = join(appDirectory, 'resources');
const sandboxExecutable = join(resourcesPath, 'windows-sandbox', 'maka-windows-sandbox.exe');
const workerBundle = join(resourcesPath, 'workers', 'filesystem-worker.js');
for (const [path, label] of [
[appExecutable, 'packaged Electron executable'],
[sandboxExecutable, 'packaged sandbox broker'],
[workerBundle, 'packaged filesystem-worker bundle'],
]) {
assertCondition(existsSync(path), `Missing ${label}: ${path}`);
}
const runtimeDist = join(repoRoot, 'packages', 'runtime', 'dist');
const importDist = (relativePath) => import(pathToFileURL(join(runtimeDist, relativePath)).href);
const { FilesystemWorkerClient, FilesystemWorkerClientError } = await importDist(
'filesystem-worker/client.js',
);
const { createFilesystemWorkerLaunchSpecProvider } = await importDist(
'filesystem-worker/launch-spec.js',
);
const { SandboxManager } = await importDist('sandbox/sandbox-manager.js');
const { WindowsBrokerSandboxBackend, createWindowsBrokerManifestWriter } = await importDist(
'sandbox/windows-sandbox.js',
);
const workspace = await realpath(await mkdtemp(join(tmpdir(), 'maka-packaged-e2e-ws-')));
const outside = await realpath(await mkdtemp(join(homedir(), '.maka-packaged-e2e-outside-')));
try {
const getLaunchSpec = createFilesystemWorkerLaunchSpecProvider({
runtime: 'electron',
executable: appExecutable,
resourceLocation: { kind: 'desktop-packaged', resourcesPath },
});
const launchSpec = await getLaunchSpec();
assertCondition(launchSpec.ok, 'Windows filesystem-worker launch spec was unavailable.');
assertCondition(
launchSpec.ok && launchSpec.spec.program === (await realpath(appExecutable)),
'Windows launch spec did not select the packaged Electron executable.',
);
assertCondition(
launchSpec.ok && launchSpec.spec.args.includes(await realpath(workerBundle)),
'Windows launch spec did not select the packaged worker bundle.',
);
// The recursive runtime grant must stay on the product-owned application
// directory and never widen to the directory that contains it (for an
// installed app that would be every sibling under `...\Programs`).
const appRoot = await realpath(appDirectory);
assertCondition(
launchSpec.ok && launchSpec.spec.runtimeReadableRoots.includes(appRoot),
'Windows launch spec omitted the packaged application directory.',
);
assertCondition(
launchSpec.ok && !launchSpec.spec.runtimeReadableRoots.includes(dirname(appRoot)),
'Windows launch spec widened the runtime ACL root past the application directory.',
);
const client = new FilesystemWorkerClient({
sandboxManager: new SandboxManager([
new WindowsBrokerSandboxBackend({
clientPath: sandboxExecutable,
writeManifest: createWindowsBrokerManifestWriter(),
}),
]),
platform: 'win32',
getLaunchSpec,
});
const execute = (operation, expectedIdentity) =>
client.execute({ operation, cwd: workspace, mode: 'ask', expectedIdentity });
// Exact writes stay exact in the preview: the target is pre-seeded so the
// grant covers only this file object, never its parent directory.
const insidePath = join(workspace, 'inside.txt');
await writeFile(insidePath, 'seeded');
const insideMetadata = await stat(insidePath, { bigint: true });
await execute(
{ kind: 'write', path: insidePath, content: 'packaged-relay-ok' },
{ dev: String(insideMetadata.dev), ino: String(insideMetadata.ino) },
);
assertCondition(
(await readFile(insidePath, 'utf8')) === 'packaged-relay-ok',
'Sandboxed write did not land in the workspace.',
);
// A missing target would need recursive Modify on its parent — broader
// than the approved operation — so the preview fails it closed before
// any launch.
let parentEntryDenied = false;
try {
// The target is genuinely missing; 'missing' is the truthful T0 state
// so the client lets the request through and the sandbox's own
// fail-closed parent-entry check is what rejects it (#3487).
await execute(
{ kind: 'write', path: join(workspace, 'missing.txt'), content: 'x' },
'missing',
);
} catch (error) {
parentEntryDenied =
error instanceof FilesystemWorkerClientError &&
error.reason === 'invalid_request' &&
/parent-entry/.test(error.message);
}
assertCondition(parentEntryDenied, 'Missing-target write did not fail closed.');
assertCondition(
!existsSync(join(workspace, 'missing.txt')),
'Failed-closed write still produced a file.',
);
const read = await execute({ kind: 'read', path: insidePath });
assertCondition(
read.kind === 'read' && read.content.includes('packaged-relay-ok'),
'Sandboxed read did not return the written content.',
);
console.log('[verify-windows-sandbox] cancelling an active packaged AppContainer worker');
await verifyPackagedClientCancellation({
FilesystemWorkerClient,
FilesystemWorkerClientError,
SandboxManager,
WindowsBrokerSandboxBackend,
createWindowsBrokerManifestWriter,
client,
launchSpec: launchSpec.spec,
sandboxExecutable,
workspace,
targetPath: insidePath,
});
console.log('[verify-windows-sandbox] packaged client cancellation and recovery verified');
console.log('[verify-windows-sandbox] killing a Runtime Host parent during launch');
await verifyPackagedRuntimeHostParentDeath({
appDirectory,
appExecutable,
sandboxExecutable,
client,
workspace,
targetPath: insidePath,
});
console.log('[verify-windows-sandbox] Runtime Host parent-death cleanup verified');
console.log('[verify-windows-sandbox] running the 64-launch packaged concurrency soak');
await verifyPackagedConcurrencySoak({
FilesystemWorkerClient,
SandboxManager,
WindowsBrokerSandboxBackend,
createWindowsBrokerManifestWriter,
launchSpec: launchSpec.spec,
sandboxExecutable,
workspace,
targetPath: insidePath,
});
console.log('[verify-windows-sandbox] packaged concurrency soak verified');
console.log('[verify-windows-sandbox] running the packaged adversarial matrix');
await verifyPackagedAdversarialMatrix(sandboxExecutable);
console.log('[verify-windows-sandbox] packaged adversarial matrix verified');
const sourceDirectory = join(workspace, 'src');
await mkdir(sourceDirectory, { recursive: true });
await writeFile(join(sourceDirectory, 'health.ts'), 'export const healthSignal = true;\n');
const globResult = await execute({
kind: 'glob',
path: sourceDirectory,
pattern: '**/*.ts',
});
assertCondition(
globResult.kind === 'glob' && globResult.files.length === 1,
'Sandboxed glob did not find the expected file.',
);
// The sandbox preview does not expose Grep (no in-process substitute
// preserves the ripgrep contract); the worker must fail closed.
let grepUnavailable = false;
try {
await execute({
kind: 'grep',
path: sourceDirectory,
pattern: 'healthSignal',
maxCountPerFile: 50,
limit: 200,
timeoutMs: 10_000,
});
} catch (error) {
grepUnavailable =
error instanceof FilesystemWorkerClientError && error.reason === 'grep_unavailable';
}
assertCondition(grepUnavailable, 'Sandboxed grep did not fail closed as unavailable.');
let denied = false;
try {
// Same contract as above: a truthful T0 state so the client's own
// permission gate (not the identity validation) is what denies.
await execute(
{ kind: 'write', path: join(outside, 'blocked.txt'), content: 'blocked' },
'missing',
);
} catch (error) {
denied = error instanceof FilesystemWorkerClientError && error.reason === 'path_denied';
}
assertCondition(denied, 'Write outside the workspace was not denied.');
assertCondition(
!existsSync(join(outside, 'blocked.txt')),
'Denied write still produced a file.',
);
} finally {
for (const path of [workspace, outside]) {
await rm(path, { recursive: true, force: true, maxRetries: 10, retryDelay: 200 });
}
}
}
async function verifyPackagedRuntimeHostParentDeath({
appDirectory,
appExecutable,
sandboxExecutable,
client,
workspace,
targetPath,
}) {
const requestId = `runtime-host-parent-death-${process.pid}-${randomBytes(4).toString('hex')}`;
const launchRequestId = `${requestId}-launch`;
assertCondition(
(await listCancellationProcesses(sandboxExecutable)).length === 0,
'Runtime Host parent-death evidence started with an existing sandbox process.',
);
const child = spawn(
appExecutable,
[
fileURLToPath(import.meta.url),
'--runtime-host-mid-launch-child',
JSON.stringify({ appDirectory, workspace, targetPath, requestId }),
],
{
env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' },
stdio: ['ignore', 'pipe', 'pipe'],
windowsHide: true,
},
);
let stdout = '';
let stderr = '';
child.stdout.on('data', (chunk) => {
stdout += chunk.toString();
});
child.stderr.on('data', (chunk) => {
stderr += chunk.toString();
});
const exited = new Promise((resolvePromise) => {
child.once('exit', (code, signal) => resolvePromise({ code, signal }));
});
let primaryError;
try {
await waitForObservation({
description: 'Runtime Host-owned packaged AppContainer child',
probe: (remainingMs) => listCancellationProcesses(sandboxExecutable, remainingMs),
accept: (processes) => processes.length >= 2,
settledEarly: () => child.exitCode !== null,
});
assertCondition(child.kill(), 'Could not terminate the Runtime Host fixture process.');
await raceWithTimeout(
exited,
10_000,
`Runtime Host fixture did not exit. stdout=${stdout} stderr=${stderr}`,
);
await waitForObservation({
description: 'Runtime Host parent-death AppContainer tree exit',
probe: (remainingMs) => listCancellationProcesses(sandboxExecutable, remainingMs),
accept: (processes) => processes.length === 0,
});
await client.execute({
operation: { kind: 'read', path: targetPath },
cwd: workspace,
mode: 'ask',
});
assertCondition(
(await findRecoveryRecord(launchRequestId)) === undefined,
'Runtime Host parent death left an ACL recovery ledger after a successful recovery launch.',
);
} catch (error) {
primaryError = error;
} finally {
if (child.exitCode === null) child.kill();
await raceWithTimeout(exited, 10_000, 'Runtime Host fixture cleanup timed out.').catch(
() => undefined,
);
}
if (primaryError) throw primaryError;
}
async function runRuntimeHostMidLaunchChild({ appDirectory, workspace, targetPath, requestId }) {
const resourcesPath = join(appDirectory, 'resources');
const appExecutable = join(appDirectory, 'Maka.exe');
const sandboxExecutable = join(resourcesPath, 'windows-sandbox', 'maka-windows-sandbox.exe');
const runtimeDist = join(repoRoot, 'packages', 'runtime', 'dist');
const importDist = (relativePath) => import(pathToFileURL(join(runtimeDist, relativePath)).href);
const { FilesystemWorkerClient } = await importDist('filesystem-worker/client.js');
const { createFilesystemWorkerLaunchSpecProvider } = await importDist(
'filesystem-worker/launch-spec.js',
);
const { SandboxManager } = await importDist('sandbox/sandbox-manager.js');
const { WindowsBrokerSandboxBackend, createWindowsBrokerManifestWriter } = await importDist(
'sandbox/windows-sandbox.js',
);
const getPackagedLaunchSpec = createFilesystemWorkerLaunchSpecProvider({
runtime: 'electron',
executable: appExecutable,
resourceLocation: { kind: 'desktop-packaged', resourcesPath },
});
const packaged = await getPackagedLaunchSpec();
assertCondition(packaged.ok, 'Runtime Host fixture could not resolve the packaged launch spec.');
const client = new FilesystemWorkerClient({
sandboxManager: new SandboxManager([
new WindowsBrokerSandboxBackend({
clientPath: sandboxExecutable,
writeManifest: createWindowsBrokerManifestWriter(),
requestId: () => requestId,
}),
]),
platform: 'win32',
newId: () => `${requestId}-operation`,
timeoutMs: 60_000,
getLaunchSpec: async () => ({
ok: true,
spec: {
...packaged.spec,
program: sandboxExecutable,
args: ['--stdio-probe', '--sleep', '47'],
},
}),
});
process.stdout.write('runtime-host-mid-launch-ready\n');
await client.execute({
operation: { kind: 'read', path: targetPath },
cwd: workspace,
mode: 'ask',
});
throw new Error('Runtime Host mid-launch fixture unexpectedly completed.');
}
async function verifyPackagedConcurrencySoak({
FilesystemWorkerClient,
SandboxManager,
WindowsBrokerSandboxBackend,
createWindowsBrokerManifestWriter,
launchSpec,
sandboxExecutable,
workspace,
targetPath,
}) {
const prefix = `phase4-soak-${process.pid}-${randomBytes(4).toString('hex')}`;
let sequence = 0;
const soakClient = new FilesystemWorkerClient({
sandboxManager: new SandboxManager([
new WindowsBrokerSandboxBackend({
clientPath: sandboxExecutable,
writeManifest: createWindowsBrokerManifestWriter(),
requestId: () => `${prefix}-${sequence++}`,
}),
]),
platform: 'win32',
getLaunchSpec: async () => ({ ok: true, spec: launchSpec }),
});
const waves = 8;
const concurrency = 8;
for (let wave = 0; wave < waves; wave += 1) {
const results = await Promise.all(
Array.from({ length: concurrency }, () =>
soakClient.execute({
operation: { kind: 'read', path: targetPath },
cwd: workspace,
mode: 'ask',
}),
),
);
assertCondition(
results.every((result) => result.kind === 'read'),
`Packaged concurrency soak wave ${wave + 1} returned a non-read result.`,
);
}
assertCondition(sequence === waves * concurrency, 'Concurrency soak did not run 64 launches.');
await waitForObservation({
description: 'packaged concurrency soak process drain',
probe: (remainingMs) => listCancellationProcesses(sandboxExecutable, remainingMs),
accept: (processes) => processes.length === 0,
});
assertCondition(
(await findRecoveryRecordsByPrefix(prefix)).length === 0,
'Packaged concurrency soak left ACL recovery records behind.',
);
}
async function verifyPackagedAdversarialMatrix(sandboxExecutable) {
const script = join(repoRoot, 'experiments', 'windows-sandbox', 'adversarial-matrix-smoke.ps1');
const { stdout, stderr } = await execFileAsync(
'pwsh.exe',
['-NoProfile', '-NonInteractive', '-File', script, '-LauncherPath', sandboxExecutable],
{
cwd: repoRoot,
timeout: 180_000,
windowsHide: true,
maxBuffer: 8 * 1024 * 1024,
},
);
assertCondition(
stdout.includes('Phase 4 adversarial matrix verified:'),
`Packaged adversarial matrix returned no completion evidence. stdout=${stdout} stderr=${stderr}`,
);
}
async function verifyPackagedClientCancellation({
FilesystemWorkerClient,
FilesystemWorkerClientError,
SandboxManager,
WindowsBrokerSandboxBackend,
createWindowsBrokerManifestWriter,
client,
launchSpec,
sandboxExecutable,
workspace,
targetPath,
}) {
const requestId = `packaged-client-cancel-${process.pid}-${randomBytes(4).toString('hex')}`;
const launchRequestId = `${requestId}-launch`;
const sleepSeconds = 47;
assertCondition(
(await listCancellationProcesses(sandboxExecutable)).length === 0,
'Client-cancel evidence started with an existing sandbox process.',
);
const controller = new AbortController();
const cancelClient = new FilesystemWorkerClient({
sandboxManager: new SandboxManager([
new WindowsBrokerSandboxBackend({
clientPath: sandboxExecutable,
writeManifest: createWindowsBrokerManifestWriter(),
requestId: () => requestId,
}),
]),
platform: 'win32',
newId: () => `${requestId}-operation`,
timeoutMs: 60_000,
getLaunchSpec: async () => ({
ok: true,
spec: {
...launchSpec,
program: sandboxExecutable,
args: ['--stdio-probe', '--sleep', String(sleepSeconds)],
},
}),
});
let cancellationError;
let cancellationSettled = false;
const cancellation = cancelClient
.execute({
operation: { kind: 'read', path: targetPath },
cwd: workspace,
mode: 'ask',
abortSignal: controller.signal,
})
.then(
() => {
cancellationSettled = true;
},
(error) => {
cancellationError = error;
cancellationSettled = true;
},
);
let primaryError;
try {
await waitForObservation({
description: 'packaged client-cancel AppContainer child',
probe: (remainingMs) => listCancellationProcesses(sandboxExecutable, remainingMs),
accept: (processes) => processes.length >= 2,
settledEarly: () => cancellationSettled,
});
controller.abort();
await cancellation;
assertCondition(
cancellationError instanceof FilesystemWorkerClientError &&
cancellationError.reason === 'aborted' &&
cancellationError.stage === 'launch' &&
cancellationError.dispatched === true,
`Client cancellation did not report an unknown dispatched outcome: ${renderError(
cancellationError,
)}`,
);
await waitForObservation({
description: 'packaged client-cancel AppContainer child exit',
probe: (remainingMs) => listCancellationProcesses(sandboxExecutable, remainingMs),
accept: (processes) => processes.length === 0,
});
// The process runner terminates the one-shot broker. Its kill-on-close Job
// tears down the AppContainer child. The next ordinary launch owns any
// stale-ledger replay before it establishes its own grants; a future
// graceful-cancel path is also valid if it already cleaned the ledger.
await client.execute({
operation: { kind: 'read', path: targetPath },
cwd: workspace,
mode: 'ask',
});
assertCondition(
(await findRecoveryRecord(launchRequestId)) === undefined,
'Normal packaged launch did not remove the client-cancel recovery ledger.',
);
} catch (error) {
primaryError = error;
} finally {
controller.abort();
await cancellation;
if (primaryError) {
try {
await client.execute({
operation: { kind: 'read', path: targetPath },
cwd: workspace,
mode: 'ask',
});
} catch (cleanupError) {
throw new AggregateError(
[primaryError, cleanupError],
'Client-cancel evidence failed and its recovery launch also failed.',
);
}
}
}
if (primaryError) throw primaryError;
}
async function listCancellationProcesses(sandboxExecutable, timeoutMs = 10_000) {
const script = String.raw`
$imageName = [IO.Path]::GetFileName($env:MAKA_CANCEL_SANDBOX)
$matches = @(
Get-CimInstance Win32_Process | ForEach-Object {
if ($_.Name -eq $imageName) {
[PSCustomObject]@{
processId = $_.ProcessId
commandLine = [string]$_.CommandLine
}
}
}
)
@($matches) | ConvertTo-Json -Compress
`;
const { stdout } = await execFileAsync(
'powershell.exe',
['-NoProfile', '-NonInteractive', '-Command', script],
{
env: {
...process.env,
MAKA_CANCEL_SANDBOX: sandboxExecutable,
},
timeout: Math.min(timeoutMs, 10_000),
windowsHide: true,
},
);
if (!stdout.trim()) return [];
const parsed = JSON.parse(stdout);
return Array.isArray(parsed) ? parsed : [parsed];
}
async function findRecoveryRecord(requestId) {
const ledgerRoot = join(tmpdir(), 'maka-sandbox-acl-ledgers');
const entries = await readdir(ledgerRoot, { withFileTypes: true }).catch((error) => {
if (error?.code === 'ENOENT') return [];
throw error;
});
for (const entry of entries) {
if (!entry.isFile()) continue;
const path = join(ledgerRoot, entry.name);
const contents = await readFile(path, 'utf8').catch(() => undefined);
if (contents?.includes(`"requestId":"${requestId}"`)) return path;
}
return undefined;
}
async function findRecoveryRecordsByPrefix(prefix) {
const ledgerRoot = join(tmpdir(), 'maka-sandbox-acl-ledgers');
const entries = await readdir(ledgerRoot, { withFileTypes: true }).catch((error) => {
if (error?.code === 'ENOENT') return [];
throw error;
});
const matches = [];
for (const entry of entries) {
if (!entry.isFile()) continue;
const path = join(ledgerRoot, entry.name);
const contents = await readFile(path, 'utf8').catch(() => undefined);
if (contents?.includes(`"requestId":"${prefix}`)) matches.push(path);
}
return matches;
}
async function raceWithTimeout(promise, timeoutMs, message) {
let timer;
try {
return await Promise.race([
promise,
new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
}),
]);
} finally {
clearTimeout(timer);
}
}
async function waitForObservation({
description,
probe,
accept,
settledEarly = () => false,
timeoutMs = 30_000,
}) {
const deadline = Date.now() + timeoutMs;
let lastObservation;
let lastError;
while (Date.now() < deadline) {
if (settledEarly()) {
throw new Error(`${description} settled before the expected state was observed.`);
}
try {
lastObservation = await probe(Math.max(1, deadline - Date.now()));
lastError = undefined;
if (accept(lastObservation)) return lastObservation;
} catch (error) {
lastError = error;
}
await new Promise((resolvePromise) => setTimeout(resolvePromise, 100));
}
throw new Error(
`${description} was not observed within ${timeoutMs}ms.` +
`${lastObservation === undefined ? '' : ` Last observation: ${JSON.stringify(lastObservation)}.`}` +
`${lastError ? ` Last probe error: ${renderError(lastError)}.` : ''}`,
);
}
function renderError(error) {
if (error instanceof Error) {
return `${error.name}: ${error.message}`;
}
return JSON.stringify(error);
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
if (process.argv[2] === '--runtime-host-mid-launch-child') {
const input = JSON.parse(process.argv[3] ?? 'null');
assertCondition(input && typeof input === 'object', 'Missing Runtime Host fixture input.');
await runRuntimeHostMidLaunchChild(input);
process.exitCode = 1;
} else {
const appDirectory = process.argv[2];
if (!appDirectory || basename(appDirectory).endsWith('.exe')) {
throw new Error(
'Usage: node scripts/verify-windows-sandbox-e2e.mjs <win-unpacked app directory>',
);
}
await verifyWindowsSandboxWorkerE2E(appDirectory);
console.log('Packaged Windows filesystem-worker E2E verified.');
}
}