// Runs only inside Bubblewrap's network/PID namespaces. No host TCP/UDP handle // is inherited: the single TCP listener relays bytes to the fixed Unix proxy. import { spawn } from 'node:child_process'; import { createServer, createConnection } from 'node:net'; import { closeSync, readlinkSync } from 'node:fs'; import { networkInterfaces } from 'node:os'; const [hostNamespace, socket, port, executable, ...args] = process.argv.slice(2); if (readlinkSync('/proc/self/ns/net') === hostNamespace || Object.keys(networkInterfaces()).some(name => name !== 'lo')) { throw new Error('isolated browser network required'); } const sockets = new Set(); const track = s => { sockets.add(s); s.once('close', () => sockets.delete(s)); return s; }; const server = createServer(peer => { track(peer); const upstream = track(createConnection(socket)); peer.on('error', () => upstream.destroy()); upstream.on('error', () => peer.destroy()); peer.once('close', () => upstream.destroy()); upstream.once('close', () => peer.destroy()); peer.pipe(upstream); upstream.pipe(peer); }); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(Number(port), '127.0.0.1', resolve); }); const child = spawn(executable, args, { stdio:['ignore','ignore','ignore',3,4] }); // Chromium alone owns the CDP pipes. Do not hold its EOF open on startup failure. closeSync(3); closeSync(4); let closing; const close = () => closing ??= (async () => { const closed = new Promise(resolve => server.close(resolve)); for (const s of sockets) s.destroy(); await closed; })(); child.once('error', () => close().then(() => { process.exitCode = 1; })); child.once('exit', (code, signal) => close().then(() => { process.exitCode = signal ? 1 : code; })); for (const signal of ['SIGTERM','SIGINT']) process.once(signal, () => child.kill(signal));