125 lines
4.6 KiB
JavaScript
125 lines
4.6 KiB
JavaScript
/**
|
|
* 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('expandGuestPath maps tilde and $HOME onto the session home', async (t) => {
|
|
const s = load()
|
|
const exp = s.bareAgentExpandGuestPath
|
|
const home = '/home/e16d7bc4ce14'
|
|
t.is(exp('~/test_work', home), home + '/test_work')
|
|
t.is(exp('~/test_work/hello.js', home), home + '/test_work/hello.js')
|
|
t.is(exp('~', home), home)
|
|
t.is(exp('$HOME/test_work', home), home + '/test_work')
|
|
t.is(exp('/tmp/x', home), '/tmp/x')
|
|
t.is(exp('rel/file.js', home, home + '/.agent/workspace'), home + '/.agent/workspace/rel/file.js')
|
|
t.is(s.bareAgentPickPathArg({ file_path: '~/a.js' }), '~/a.js')
|
|
t.is(s.bareAgentPickPathArg({ dir: '/tmp/d' }), '/tmp/d')
|
|
})
|
|
|
|
test('js script wrapping curl/fetch diverts to a shell command', async (t) => {
|
|
const s = load()
|
|
const fromJs = s.bareAgentShellCommandFromJsScript
|
|
t.is(
|
|
fromJs("await ctx.execLine('curl http://api.ipify.org')"),
|
|
'curl http://api.ipify.org'
|
|
)
|
|
t.is(
|
|
fromJs("const r = await fetch('https://api.ipify.org')"),
|
|
'curl -s https://api.ipify.org'
|
|
)
|
|
t.is(fromJs('curl http://api.ipify.org'), 'curl http://api.ipify.org')
|
|
t.is(fromJs('const vfs = ctx.vfs\nawait vfs.readFile("/tmp/x")'), '')
|
|
})
|
|
|
|
test('failed task_complete detector matches screenshot surrender', async (t) => {
|
|
const s = load()
|
|
const fn = s.bareAgentLooksLikeFailedTaskComplete
|
|
t.ok(
|
|
fn(
|
|
'Done: Attempted to retrieve IP address using JavaScript. Error occurred due to async/await in top-level scope. Alternative method required.'
|
|
)
|
|
)
|
|
t.absent(fn('Fetched public IP via curl; stdout is 1.2.3.4'))
|
|
})
|
|
|
|
test('guest CLI request detector matches run curl for IP', async (t) => {
|
|
const s = load()
|
|
const fn = s.bareAgentLooksLikeGuestCliRequest
|
|
t.ok(fn('run curl to find our IP Address'))
|
|
t.ok(fn('get our public ip'))
|
|
t.absent(fn('refactor the agent prompt'))
|
|
})
|
|
|
|
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('))
|
|
})
|