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) }) 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') } }) 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) })