Files
stack/agents/researcher/work/i1-human-proof/tools/forge.mjs
T

82 lines
3.1 KiB
JavaScript

// usage: node forge.mjs <sock> find <cliPath> | random | cap <capFile> | direct
// Spike H2's hostile client. It sends one request, `decisions {list:'roles'}` (a verb that
// only a human or reader session may call), over a raw unix socket and prints one JSON
// line: the connect result (errno on refusal) and the reply. It does not use Client, so
// a refused connect is distinguishable from a refused proof.
// find <cliPath> claim a LIVE human-cli process on this host: scan /proc for argv[1] ==
// cliPath, copy its pid, start time and MOSAIC_BUS_CLI_NONCE (a same-UID
// process may read them). The forger then speaks for it on its own connection.
// random claim an arbitrary pid with a made-up nonce (what a process with no
// access to the host /proc can do)
// cap <file> send a capability, as an agent would on the agent socket
// direct send {human:{business}} with no proof, as a process that reaches the
// broker's human socket directly would
import { connect } from 'node:net';
import { readFileSync, readdirSync } from 'node:fs';
import { randomBytes } from 'node:crypto';
const [sock, mode, arg] = process.argv.slice(2);
const out = (o) => console.log(JSON.stringify(o));
function find(cliPath) {
for (const d of readdirSync('/proc')) {
if (!/^\d+$/.test(d)) continue;
try {
const argv = readFileSync(`/proc/${d}/cmdline`, 'utf8').split('\0');
if (argv[1] !== cliPath) continue;
const env = readFileSync(`/proc/${d}/environ`, 'utf8').split('\0');
const nonce = env.find((e) => e.startsWith('MOSAIC_BUS_CLI_NONCE='))?.slice(21);
if (!nonce) continue;
const raw = readFileSync(`/proc/${d}/stat`, 'utf8');
return { pid: Number(d), startTime: raw.slice(raw.lastIndexOf(')') + 2).split(' ')[19], nonce };
} catch {}
}
return null;
}
let envelope;
if (mode === 'find') {
const p = find(arg);
if (!p) {
out({ connect: 'not-attempted', note: 'no live human-cli process with a readable nonce' });
process.exit(0);
}
envelope = { human: { business: 'demo', ...p }, claimed: p.pid };
} else if (mode === 'random') {
envelope = { human: { business: 'demo', pid: 4242, startTime: '1', nonce: randomBytes(32).toString('hex') } };
} else if (mode === 'cap') {
envelope = { cap: readFileSync(arg, 'utf8').trim() };
} else if (mode === 'direct') {
envelope = { human: { business: 'demo' } };
} else {
throw Error('usage');
}
const claimed = envelope.claimed;
delete envelope.claimed;
const request = JSON.stringify({ ...envelope, verb: 'decisions', args: { list: 'roles' } }) + '\n';
const s = connect(sock);
let buf = '';
s.setTimeout(8000, () => {
out({ connect: 'ok', reply: 'timeout', claimed });
s.destroy();
});
s.on('connect', () => s.write(request));
s.on('error', (e) => {
out({ connect: e.code ?? 'error', claimed });
process.exit(0);
});
s.on('data', (b) => {
buf += b;
if (!buf.includes('\n')) return;
let reply;
try {
reply = JSON.parse(buf);
} catch {
reply = 'unparsable';
}
out({ connect: 'ok', reply, claimed });
s.destroy();
});
s.on('close', () => process.exit(0));