kilo-loop/test/guard.test.mjs

102 lines
3.9 KiB
JavaScript
Raw Normal View History

2026-09-13 16:29:03 +00:00
import { test } from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { scanCommand, scanToolUse, buildGuardConfigContent, DENY_GLOBS, isSafeRecursiveRm } from '../src/guard.mjs'
test('scanCommand blocks destructive git and shell commands', () => {
for (const cmd of ['git commit -m "x"', 'git push origin main', 'rm -rf build', 'git reset --hard HEAD~1', 'npm publish', 'git filter-branch --tree-filter x']) {
assert.ok(scanCommand(cmd), `expected blocked: ${cmd}`)
}
})
test('scanCommand allows harmless commands', () => {
for (const cmd of ['git status', 'git diff HEAD', 'pnpm test', 'npm run build', 'ls -la', 'rm file.txt']) {
assert.equal(scanCommand(cmd), null, `expected allowed: ${cmd}`)
}
})
test('scanCommand allows recursive deletes that only touch temp dirs', () => {
const cmds = [
'rm -rf /tmp/kilo/tf-test',
'rm -rf /tmp/build /var/tmp/cache',
"rm -rf /tmp/kilo/tf-test && mkdir -p /tmp/kilo/tf-test && cd /tmp/kilo/tf-test",
'rm -fr /tmp/scratch',
'rm -r -f /tmp/one /tmp/two',
]
for (const cmd of cmds) assert.equal(scanCommand(cmd), null, `expected allowed: ${cmd}`)
assert.equal(isSafeRecursiveRm('rm -rf /tmp/kilo/tf-test'), true)
})
2026-09-13 19:38:01 +00:00
test('scanCommand allows relative recursive deletes after cd into a temp dir', () => {
const cmds = [
'cd /tmp && rm -rf tpl-onlineshop',
'cd /tmp/kilo && rm -rf ./scratch',
'cd /tmp && rm -rf a b && git clone ssh://x/a b',
'mkdir -p /tmp/x && cd /tmp/x && rm -rf build',
'cd /var/tmp && rm -rf cache',
]
for (const cmd of cmds) assert.equal(scanCommand(cmd), null, `expected allowed: ${cmd}`)
})
test('scanCommand blocks relative recursive deletes after cd into a non-temp dir', () => {
const cmds = [
'cd /repo && rm -rf src',
'cd /home/joe/work && rm -rf node_modules',
'cd /tmp && rm -rf /home/joe/work', // an unsafe absolute target wins
'cd /tmp && rm -rf ..', // escapes the temp root
]
for (const cmd of cmds) {
const hit = scanCommand(cmd)
assert.ok(hit, `expected blocked: ${cmd}`)
assert.equal(hit.id, 'rm-recursive-force')
}
})
2026-09-13 16:29:03 +00:00
test('scanCommand still blocks recursive deletes outside temp dirs', () => {
const cmds = [
'rm -rf build',
'rm -rf .',
'rm -rf ..',
'rm -rf /',
'rm -rf /tmp',
'rm -rf /home/joe/sources/wiz4apps',
'rm -rf /tmp/ok /home/joe/work', // one unsafe target makes the whole command unsafe
'cd /repo && rm -rf src',
]
for (const cmd of cmds) {
const hit = scanCommand(cmd)
assert.ok(hit, `expected blocked: ${cmd}`)
assert.equal(hit.id, 'rm-recursive-force')
}
})
test('DENY_GLOBS no longer blanket-denies every rm -rf', () => {
assert.ok(!DENY_GLOBS.includes('*rm -rf*'))
assert.ok(!DENY_GLOBS.includes('*rm -fr*'))
assert.ok(DENY_GLOBS.some((g) => g.includes('/home/')))
})
test('scanToolUse only inspects bash tool calls', () => {
assert.equal(scanToolUse({ tool: 'edit', state: { input: { filePath: 'x' } } }), null)
const hit = scanToolUse({ tool: 'bash', state: { input: { command: 'git commit -am x' } } })
assert.equal(hit.id, 'git-commit')
assert.equal(hit.command, 'git commit -am x')
})
test('buildGuardConfigContent emits deny globs when bash is unspecified', () => {
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'kilo-guard-'))
fs.writeFileSync(path.join(project, 'kilo.json'), JSON.stringify({ permission: { glob: 'allow' } }))
const content = buildGuardConfigContent(project)
const parsed = JSON.parse(content)
for (const glob of DENY_GLOBS) assert.equal(parsed.permission.bash[glob], 'deny')
})
test('buildGuardConfigContent bails out on a scalar bash permission', () => {
const project = fs.mkdtempSync(path.join(os.tmpdir(), 'kilo-guard-'))
fs.writeFileSync(path.join(project, 'kilo.json'), JSON.stringify({ permission: { bash: 'allow' } }))
assert.equal(buildGuardConfigContent(project), null)
})