import { readFileSync, lstatSync } from 'node:fs'; import { basename, resolve } from 'node:path'; import { BusError } from './broker.mjs'; const MARKERS = [ 'MOSAIC_BUS_CAP', 'MOSAIC_RUN_ID', 'MOSAIC_AGENT_RUN', 'CLAUDECODE', 'CLAUDE_CODE_ENTRYPOINT', 'CODEX_THREAD_ID', 'PI_AGENT_DIR', ]; const refuse = () => { throw new BusError('human-required'); }; export function readProcess(pid, { readFile = readFileSync, stat = lstatSync } = {}) { if (!Number.isSafeInteger(pid) || pid < 1) refuse(); try { const dir = `/proc/${pid}`, raw = readFile(dir + '/stat', 'utf8'), fields = raw.slice(raw.lastIndexOf(')') + 2).split(' '); let env = {}; let environment; try { environment = readFile(dir + '/environ', 'utf8'); } catch (e) { if (e.code !== 'EACCES') throw e; env = null; } if (environment !== undefined) for (const entry of environment.split('\0')) { const i = entry.indexOf('='); const key = entry.slice(0, i); if (key === 'MOSAIC_BUS_CLI_NONCE' || MARKERS.includes(key)) env[key] = entry.slice(i + 1); } return { pid, ppid: Number(fields[1]), startTime: fields[19], uid: stat(dir).uid, argv: readFile(dir + '/cmdline', 'utf8') .split('\0') .filter(Boolean), env, }; } catch { refuse(); } } // Cooperative same-UID process checks, not peer credentials or a hostile-process wall. // A nonce in the CLI's launch environment binds the submitted PID to a live CLI. export function verifyHuman(proof, { cliPath, launches = [], readProcess: read = readProcess }) { if ( !proof || typeof proof !== 'object' || Object.keys(proof).some((k) => !['pid', 'startTime', 'nonce', 'business'].includes(k)) || !Number.isSafeInteger(proof.pid) || proof.pid < 2 || typeof proof.business !== 'string' || typeof proof.nonce !== 'string' || !/^[a-f0-9]{64}$/.test(proof.nonce) ) refuse(); try { const p = read(proof.pid); if ( p.uid !== process.getuid() || p.startTime !== proof.startTime || p.env?.MOSAIC_BUS_CLI_NONCE !== proof.nonce || p.argv[1] !== resolve(cliPath) ) refuse(); const seen = new Set(); let current = p; for (let depth = 0; depth < 128; depth++) { if (seen.has(current.pid)) refuse(); seen.add(current.pid); if ( (current.env !== null && MARKERS.some((k) => current.env[k])) || launches.some((r) => r.pid === current.pid && r.startTime === current.startTime) ) refuse(); const command = basename(current.argv[0] ?? ''); if ( /^(pi|claude|claude-code|codex)(?:\.js)?$/.test(command) || current.argv.some((v) => /\/(?:pi-coding-agent|codex)\/(?:dist|bin)\//.test(v)) ) refuse(); if (current.ppid === 1) { const again = read(proof.pid); if (again.startTime !== proof.startTime || again.ppid !== p.ppid) refuse(); return { business: proof.business }; } if (current.ppid < 2) refuse(); current = read(current.ppid); } } catch { refuse(); } refuse(); }