82 lines
3.1 KiB
JavaScript
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));
|