Rocko's round 2 candidate, approved by Darkwing (#1519 comment 26757).
build.patch 40d7e838, manifest 61519059, 24 files under packages/bus,
schema v3b (179ffe35, lead decision 60). Integration gate in a git
worktree of 942dca9e (S1 in the tree) plus the patch: bus 43/43 and
business 60/60 on Node 24 and 26, every package test and every
scripts/test-*.sh green, test-task 98/98 with the live-provider cases.
Rulings from lead decisions 62 and 63: the human proof is cooperative in
slice 1, and a self-raised cross-role decision routes to the human.
Single-use gated approvals follow in row 43.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
126 lines
4.2 KiB
JavaScript
126 lines
4.2 KiB
JavaScript
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);
|
|
});
|