| /* |
| * 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.'); |
| } |
| } |