#!/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 });
}