#!/usr/bin/env node // Real worker startup + browser CSP regression. All canaries are loopback-only. import assert from 'node:assert/strict'; import { createServer } from 'node:http'; import { once } from 'node:events'; import { mkdtemp, writeFile, rm, mkdir, copyFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { spawn, spawnSync } from 'node:child_process'; import { chromium } from 'playwright'; import { WebSocketServer } from 'ws'; import express from 'express'; import { registerBrowserEgress } from '../../dist/bridge/egress-csp.js'; const root = resolve(import.meta.dirname, '../..'); const temporary = await mkdtemp(join(tmpdir(), 'maestro-egress-')); const secret = 'egress-canary-secret-must-not-be-logged'; const servers = []; let browser; let worker; async function listen(server) { server.listen(0, '127.0.0.1'); await once(server, 'listening'); servers.push(server); return `http://127.0.0.1:${server.address().port}`; } const delay = ms => new Promise(resolveDelay => setTimeout(resolveDelay, ms)); async function stopWorker() { if (!worker || worker.exitCode !== null || worker.signalCode !== null) return; const exiting = once(worker, 'exit'); worker.kill('SIGTERM'); const timer = setTimeout(() => worker?.kill('SIGKILL'), 5000); try { await exiting; } finally { clearTimeout(timer); worker = undefined; } } try { await mkdir(join(temporary, 'dist/db'), { recursive: true }); await copyFile(join(root, 'dist/db/schema.sql'), join(temporary, 'dist/db/schema.sql')); const invalidPolicy = join(temporary, 'invalid-policy.json'); await writeFile(invalidPolicy, JSON.stringify({ secret })); for (const entry of ['main.js', 'index.js']) { const invalidStart = spawnSync(process.execPath, [join(root, 'dist', entry)], { cwd: temporary, env: { PATH: process.env.PATH, HOME: temporary, AAO_EGRESS_POLICY: invalidPolicy }, encoding: 'utf8', timeout: 5000, }); assert.equal(invalidStart.status, 1, 'invalid policy rejects startup'); assert(invalidStart.stderr.includes('Invalid egress policy')); assert(!invalidStart.stderr.includes(secret)); assert(!invalidStart.stdout.includes('maestro starting'), 'no worker startup with invalid policy'); } let allowedHits = 0, deniedHits = 0; const makeCanary = hit => { const server = createServer((req, res) => { hit(); res.setHeader('Access-Control-Allow-Origin', '*'); if (req.url.startsWith('/image')) { res.setHeader('Content-Type', 'image/svg+xml'); res.end(''); } else { res.end('ok'); } }); const ws = new WebSocketServer({ server }); ws.on('connection', socket => { hit(); socket.close(); }); return server; }; const allowed = await listen(makeCanary(() => allowedHits++)); const denied = await listen(makeCanary(() => deniedHits++)); let llmHits = 0; const llm = await listen(createServer((_req, res) => { llmHits++; res.setHeader('Content-Type', 'application/json'); res.end('{"data":[{"id":"egress-test-model"}],"models":[]}'); })); const policyPath = join(temporary, 'policy.json'); const configPath = join(temporary, 'config.yaml'); await writeFile(configPath, 'safety:\n bash_allow_network: true\n'); browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] }); for (const mode of ['report-only', 'enforce']) { const reserve = createServer(); reserve.listen(0, '127.0.0.1'); await once(reserve, 'listening'); const port = reserve.address().port; await new Promise(resolveClose => reserve.close(resolveClose)); const base = `http://127.0.0.1:${port}`; const policy = { version: 1, observeNode: true, browser: { mode, connectOrigins: [allowed, allowed.replace('http:', 'ws:')], imageOrigins: [allowed], frameOrigins: [], } }; await writeFile(policyPath, JSON.stringify(policy)); const fixtureApp = express(); registerBrowserEgress(fixtureApp, policy); fixtureApp.get('/sandbox', (_req, res) => { res.setHeader('Content-Security-Policy', 'sandbox'); res.type('html').send(``); }); fixtureApp.get('/', (_req, res) => res.type('html').send('')); const fixture = await listen(createServer(fixtureApp)); const initialLlmHits = llmHits; let output = ''; worker = spawn(process.execPath, [join(root, 'dist/main.js')], { cwd: temporary, // Do not inherit real integrations, credentials, proxies, preload scripts or developer config. env: { PATH: process.env.PATH, HOME: temporary, NODE_ENV: 'test', AAO_MODE: 'worker', AAO_CONFIG: configPath, AAO_EGRESS_POLICY: policyPath, PORT: String(port), LOG_LEVEL: 'info', DB_PATH: join(temporary, `${mode}.db`), WORKTREE_DIR: join(temporary, `${mode}-workspaces`), UI_DIST_PATH: join(root, 'ui/dist'), OLLAMA_BASE_URL: `${llm}/v1`, OLLAMA_MODEL: 'egress-test-model' }, stdio: ['ignore', 'pipe', 'pipe'], }); worker.on('error', error => { output += error.message; }); const capture = chunk => { output += chunk.toString(); if (output.length > 2_000_000) worker?.kill('SIGTERM'); }; worker.stdout.on('data', capture); worker.stderr.on('data', capture); let ready = false; for (let attempt = 0; attempt < 300; attempt++) { if (worker.exitCode !== null) throw new Error(`Worker failed to start: ${output.slice(-6000)}`); try { const response = await fetch(`${base}/ui/`, { signal: AbortSignal.timeout(1000) }); if (response.ok) { ready = true; break; } } catch {} await delay(100); } assert(ready, `worker ready (${mode})`); const context = await browser.newContext({ serviceWorkers: 'block' }); context.setDefaultTimeout(15000); const unexpected = []; const pageErrors = []; await context.route('**/*', route => { const url = route.request().url(); if (!/^https?:/.test(url) || [base, allowed, denied, fixture].includes(new URL(url).origin)) return route.continue(); unexpected.push(new URL(url).origin); return route.abort(); }); await context.routeWebSocket('**/*', route => { const origin = new URL(route.url()).origin.replace(/^ws/, 'http'); if ([base, allowed, denied, fixture].includes(origin)) route.connectToServer(); else { unexpected.push(origin); route.close(); } }); const page = await context.newPage(); // App errors only: Playwright's service-worker disabling init script itself // throws inside an intentionally opaque sandbox frame. The fixture has no scripts. page.on('pageerror', error => pageErrors.push(error.message)); const response = await page.goto(`${base}/ui/`); const header = mode === 'enforce' ? 'content-security-policy' : 'content-security-policy-report-only'; assert(response.headers()[header]?.includes('connect-src'), `${mode} CSP on real UI`); await page.getByTestId('nav-spaces').waitFor({ state: 'visible' }); await page.getByTestId('nav-spaces').click(); await page.getByTestId('space-rail').waitFor({ state: 'visible' }); const attempt = async (origin, kind) => page.evaluate(async ({ origin, kind, secret }) => { const url = `${origin}/${kind}?token=${secret}`; return await new Promise(resolveResult => { const timeout = setTimeout(() => resolveResult('timeout'), 4000); const finish = value => { clearTimeout(timeout); resolveResult(value); }; if (kind === 'fetch') fetch(url).then(r => finish(r.ok ? 'ok' : 'error'), () => finish('error')); else if (kind === 'image') { const img = new Image(); img.onload = () => finish('ok'); img.onerror = () => finish('error'); img.src = url; } else { const socket = new WebSocket(url.replace('http:', 'ws:')); socket.onopen = () => { socket.close(); finish('ok'); }; socket.onerror = () => finish('error'); } }); }, { origin, kind, secret }); const startAllowed = allowedHits, startDenied = deniedHits; for (const kind of ['fetch', 'image', 'websocket']) { assert.equal(await attempt(allowed, kind), 'ok', `${mode} allowed ${kind}`); assert.equal(await attempt(denied, kind), mode === 'enforce' ? 'error' : 'ok', `${mode} unlisted ${kind}`); } assert.equal(allowedHits - startAllowed, 3); assert.equal(deniedHits - startDenied, mode === 'enforce' ? 0 : 3); const sandboxPage = await context.newPage(); const sandboxResponse = await context.request.get(`${fixture}/sandbox`); assert(sandboxResponse.headers()['content-security-policy'].includes('sandbox'), 'sandbox is preserved'); assert(sandboxResponse.headers()[header].includes('img-src'), 'egress CSP survives later sandbox header'); const beforeFrame = deniedHits; await sandboxPage.goto(fixture); const canary = sandboxPage.frameLocator('iframe').locator('#canary'); await canary.waitFor({ state: 'attached' }); for (let i = 0; i < 50 && !await canary.evaluate(img => img.complete); i++) await delay(100); assert(await canary.evaluate(img => img.complete), 'iframe image request completes'); assert.equal(await canary.evaluate(img => img.naturalWidth > 0), mode === 'report-only', 'same-origin sandbox iframe egress'); assert.equal(deniedHits - beforeFrame, mode === 'enforce' ? 0 : 1); await sandboxPage.close(); for (let i = 0; i < 30 && !output.includes(`[security:csp] blocked="${denied}"`); i++) await delay(100); assert(output.includes(`[security:csp] blocked="${denied}"`), 'browser reports reach real worker'); assert(!output.includes(secret), 'canary secret absent from all worker logs'); assert.deepEqual(unexpected, [], 'normal UI must not contact unknown origins'); // Stay idle across the observation interval and backend status refresh cycle. if (mode === 'enforce') { for (let i = 0; i < 13; i++) { await delay(5000); assert.equal(worker.exitCode, null); } assert(output.includes('[egress-summary]'), 'idle observation summary'); } assert(output.includes('[egress] Node destination observation enabled')); const observationLines = output.split('\n').filter(line => /\[egress(?:-summary)?\]/.test(line)); assert(!observationLines.some(line => line.includes(secret)), 'secrets absent from observations'); let sawLlmHttp = false, sawLlmTcp = false; for (const line of observationLines) { const json = line.slice(line.indexOf('] {') + 2); if (!json.startsWith('{')) continue; const value = JSON.parse(json); const entries = value.entries ?? [value]; for (const entry of entries) { sawLlmHttp ||= entry.kind === 'http' && entry.destination === llm; sawLlmTcp ||= entry.kind === 'tcp' && entry.destination === llm.replace('http:', 'tcp:'); assert([llm, allowed, denied, base].includes(entry.destination) || [llm, allowed, denied, base].map(origin => origin.replace('http:', 'tcp:')).includes(entry.destination), 'only controlled destinations observed'); } } assert(llmHits > initialLlmHits, 'worker actually contacts fake LLM'); assert(sawLlmHttp && sawLlmTcp, 'real worker HTTP and TCP observations are nonempty'); assert.deepEqual(pageErrors, [], 'normal UI has no uncaught errors'); assert.deepEqual(unexpected, [], 'idle UI has no unexpected destinations'); await context.close(); await stopWorker(); console.log(`${mode}: real UI startup, HTTP/image/WebSocket canaries, destination-only observation passed`); } } finally { await stopWorker(); await browser?.close(); for (const server of servers) { server.closeAllConnections(); server.close(); } await rm(temporary, { recursive: true, force: true }); }