import test from 'node:test'; import assert from 'node:assert/strict'; import { verifyHuman, readProcess } from '../src/human.mjs'; const cli = '/opt/mosaic/human-cli.mjs'; function processTable() { return new Map([ [ 50, { pid: 50, ppid: 40, startTime: '100', uid: process.getuid(), argv: ['node', cli], env: { MOSAIC_BUS_CLI_NONCE: 'a'.repeat(64) }, }, ], [40, { pid: 40, ppid: 1, startTime: '90', uid: process.getuid(), argv: ['bash'], env: {} }], ]); } const proof = { pid: 50, startTime: '100', nonce: 'a'.repeat(64), business: 'demo' }; function check(table, extra = {}) { return verifyHuman(proof, { cliPath: cli, readProcess: (pid) => { if (!table.has(pid)) throw Error('gone'); return table.get(pid); }, launches: [], ...extra, }); } test('human proof binds CLI entry, process start and nonce; agents and incomplete ancestry refuse', () => { const table = processTable(); assert.equal(check(table).business, 'demo'); for (const change of [ { startTime: '101' }, { uid: process.getuid() + 1 }, { argv: ['node', 'other.mjs'] }, { env: { MOSAIC_BUS_CLI_NONCE: 'x' } }, ]) { const t = processTable(); Object.assign(t.get(50), change); assert.throws(() => check(t), /human-required/); } for (const env of [{ MOSAIC_RUN_ID: 'run-x' }, { CLAUDECODE: '1' }, { CODEX_THREAD_ID: 'thread' }]) { const t = processTable(); t.get(40).env = env; assert.throws(() => check(t), /human-required/); } assert.throws(() => check(table, { launches: [{ pid: 40, startTime: '90' }] }), /human-required/); const broken = processTable(); broken.delete(40); assert.throws(() => check(broken), /human-required/); const loop = processTable(); loop.get(40).ppid = 50; assert.throws(() => check(loop), /human-required/); }); test('process reader gets own kernel identity without exposing environment values', () => { const p = readProcess(process.pid); assert.equal(p.pid, process.pid); assert.equal(p.uid, process.getuid()); assert.match(p.startTime, /^[0-9]+$/); assert.equal(p.ppid, process.ppid); }); test('EACCES ancestor environments skip only markers; commands and registered launches still refuse', () => { const table = processTable(); table.get(40).ppid = 30; table.set(30, { pid: 30, ppid: 20, startTime: '80', uid: process.getuid(), argv: ['systemd', '--user'], envError: 'EACCES', }); table.set(20, { pid: 20, ppid: 1, startTime: '70', uid: 0, argv: ['plasmalogin-helper'], envError: 'EACCES', }); const io = { stat: (path) => ({ uid: table.get(Number(path.split('/')[2])).uid }), readFile: (path) => { const r = table.get(Number(path.split('/')[2])); if (!r) throw Object.assign(Error(), { code: 'ENOENT' }); if (path.endsWith('/environ')) { if (r.envError) throw Object.assign(Error(), { code: r.envError }); return Object.entries(r.env ?? {}) .map(([k, v]) => k + '=' + v) .join('\0'); } if (path.endsWith('/cmdline')) return r.argv.join('\0'); const fields = Array(20).fill('0'); fields[0] = 'S'; fields[1] = String(r.ppid); fields[19] = r.startTime; return `${r.pid} (fixture) ${fields.join(' ')}`; }, }; const read = (pid) => readProcess(pid, io); assert.equal(check(table, { readProcess: read }).business, 'demo'); assert.throws( () => check(table, { readProcess: read, launches: [{ pid: 30, startTime: '80' }] }), /human-required/, ); table.get(30).argv = ['pi']; assert.throws(() => check(table, { readProcess: read }), /human-required/); table.get(30).argv = ['systemd']; table.get(30).envError = 'ENOENT'; assert.throws(() => check(table, { readProcess: read }), /human-required/); table.get(30).envError = 'EACCES'; table.get(50).envError = 'EACCES'; assert.throws(() => check(table, { readProcess: read }), /human-required/); }); test('real pid 1 remains inspectable when its environment is protected', () => { const p = readProcess(1); assert.equal(p.pid, 1); assert.match(p.startTime, /^[0-9]+$/); assert.ok(Array.isArray(p.argv)); if (p.uid !== process.getuid()) assert.equal(p.env, null); });