/** * Security tests for the brainstorm server's per-session key. * * The companion server is reachable by any local browser tab (default loopback * bind) and by any host that can route to it (remote `--host 0.0.0.0` bind). * A per-session secret key gates every endpoint so that neither a browser * confused-deputy nor a direct remote client can read screens/files or inject * events into state/events (prompt injection into a live agent session). * * Auth = a valid `?key=` query param OR a valid session cookie. * * Uses the `ws` npm package as a test client (test-only dependency). */ const { spawn } = require('child_process'); const http = require('http'); const WebSocket = require('ws'); const fs = require('fs'); const path = require('path'); const assert = require('assert'); const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs'); const TEST_PORT = 3335; const TEST_DIR = '/tmp/brainstorm-auth-test'; const CONTENT_DIR = path.join(TEST_DIR, 'content'); const TOKEN = 'testtoken-0123456789abcdef0123456789abcdef'; const COOKIE_NAME = `brainstorm-key-${TEST_PORT}`; const EXPECTED_SECURITY_HEADERS = { 'referrer-policy': 'no-referrer', 'cache-control': 'no-store', 'x-frame-options': 'DENY', 'content-security-policy': "frame-ancestors 'none'", 'cross-origin-resource-policy': 'same-origin' }; function cleanup() { if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true }); } async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Raw HTTP GET with optional key query and Cookie header. function get(pathname, { key, cookie } = {}) { const url = `http://localhost:${TEST_PORT}${pathname}` + (key !== undefined ? `?key=${key}` : ''); const headers = {}; if (cookie) headers['Cookie'] = cookie; return new Promise((resolve, reject) => { http.get(url, { headers }, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: data })); }).on('error', reject); }); } // Try to open a WebSocket; resolve 'opened' or 'rejected'. function wsConnect({ key, cookie, origin } = {}) { const url = `ws://localhost:${TEST_PORT}/` + (key !== undefined ? `?key=${key}` : ''); const headers = {}; if (cookie) headers['Cookie'] = cookie; if (origin) headers['Origin'] = origin; const opts = Object.keys(headers).length ? { headers } : {}; const ws = new WebSocket(url, opts); return new Promise((resolve) => { let settled = false; const done = (outcome) => { if (!settled) { settled = true; resolve({ outcome, ws }); } }; ws.on('open', () => done('opened')); ws.on('error', () => done('rejected')); ws.on('close', () => done('rejected')); setTimeout(() => done('rejected'), 1500); }); } function startServer() { return spawn('node', [SERVER_PATH], { env: { ...process.env, BRAINSTORM_PORT: TEST_PORT, BRAINSTORM_DIR: TEST_DIR, BRAINSTORM_TOKEN: TOKEN } }); } function assertSecurityHeaders(headers) { for (const [name, value] of Object.entries(EXPECTED_SECURITY_HEADERS)) { assert.strictEqual(headers[name], value, `${name} should be ${value}`); } } function runBootstrapScript(html, sessionStorage) { const match = html.match(/