/** * Path / proc / apropos helpers (VM, same preamble as /bin/agent). */ import test from 'brittle' import { readFileSync } from 'node:fs' import vm from 'node:vm' const CODE = readFileSync(new URL('../lib/agent/agent-helpers.js', import.meta.url), 'utf8') function load() { const sandbox = { TextDecoder, TextEncoder, Uint8Array, console } vm.createContext(sandbox) vm.runInContext(CODE, sandbox, { filename: 'agent-helpers.js' }) return sandbox } test('mutate paths use a denylist (base system read-only, rest writable)', async (t) => { const s = load() t.ok(s.bareAgentPathAllowedMutate('/home/guest/x')) t.ok(s.bareAgentPathAllowedMutate('/tmp/a')) t.ok(s.bareAgentPathAllowedMutate('/var/log/app.log')) t.ok(s.bareAgentPathAllowedMutate('/opt/pkg/bin')) t.ok(s.bareAgentPathAllowedMutate('/mnt/extra/file')) t.ok(s.bareAgentPathAllowedMutate('/root/.ssh/config')) t.absent(s.bareAgentPathAllowedMutate('/bin/foo')) t.absent(s.bareAgentPathAllowedMutate('/etc/passwd')) t.absent(s.bareAgentPathAllowedMutate('/proc/x')) t.absent(s.bareAgentPathAllowedMutate('/usr/lib/x')) t.absent(s.bareAgentPathAllowedMutate('/')) }) test('normalize guest script strips ctx redeclare and export', async (t) => { const s = load() const n = s.bareAgentNormalizeGuestScript const exported = n( 'export async function run(ctx, argv) {\n const vfs = ctx.vfs\n}\nexport { run }\n' ) t.ok(exported.includes('async function run(ctx, argv)')) t.absent(exported.includes('export')) const rebound = n('const ctx = {}\nctx.console.log(1)\n') t.ok(rebound.includes('async function run(ctx, argv)')) t.absent(/const ctx/.test(rebound)) const plain = n('ctx.console.log("hi")') t.ok(plain.includes('async function run(ctx, argv)')) t.ok(plain.includes('ctx.console.log')) }) test('reads allow any absolute path including kernel /proc', async (t) => { const s = load() t.ok(s.bareAgentPathAllowedRead('/home/guest/x')) t.ok(s.bareAgentPathAllowedRead('/bin/sh')) t.ok(s.bareAgentPathAllowedRead('/proc/bare_os/other.json')) t.ok(s.bareAgentProcReadPathAllowed('/proc/bare_os/other.json')) t.ok(s.bareAgentProcReadPathAllowed('/proc/bare_os/features')) t.absent(s.bareAgentPathAllowedRead('../etc/passwd')) t.absent(s.bareAgentProcReadPathAllowed('/home/guest/x')) t.absent(s.bareAgentProcReadPathAllowed('/proc/../proc/bare_os/swarm.json')) }) test('apropos substring matches kw like man -k', async (t) => { const s = load() const db = { pages: [ { name: 'grep', section: 1, title: 'pattern search' }, { name: 'true', section: 1, title: 'exit successfully' } ], apropos: [ { kw: 'grep', pageRef: 0 }, { kw: 'pattern', pageRef: 0 }, { kw: 'true', pageRef: 1 } ] } const r = s.bareAgentManAproposHits(db, 'pat', 10) t.is(r.lines.length, 1) t.ok(r.lines[0].startsWith('grep(')) })