feat: lift shell read restrictions and page bash/read tool outputs to 200 lines

This commit is contained in:
Agent committed 2026-10-12 22:33:11 +00:00
1 parent ee78e6d3a8
commit de37af4b8c
7 files changed
+213 -32

No files matched your search

+34 -1
View File
@@ -1,7 +1,7 @@
// Guard rules for shell commands, focused on inline interpreter scripts.
import test from 'node:test'
import assert from 'node:assert/strict'
import { shellViolation, inlineScriptViolation } from '../index.js'
import { shellViolation, inlineScriptViolation, pageText, pageShellResult } from '../index.js'
const ALLOW = [
// harmless computation one-liners
@@ -25,6 +25,14 @@ const ALLOW = [
// unrelated commands
'git status',
'npm test 2>&1 | tail -5',
// reading / searching through the shell is allowed (output is paged by the bash wrapper)
'cat README.md',
'head -50 index.js',
'sed -n 10,20p index.js',
'grep -rn foo .',
'find . -name "*.js"',
'ls -R lib',
`awk '{print $1}' f.txt`,
]
const BLOCK = [
@@ -61,6 +69,12 @@ const BLOCK = [
`sudo node -e "require('fs')"`,
`/usr/bin/node -e "require('fs')"`,
`ls && node -e "require('fs')"`,
// shell writes stay blocked
`awk '{print > "out.txt"}' f.txt`,
`awk 'BEGIN{system("rm x")}'`,
'find . -name "*.tmp" -delete',
'sed -i s/a/b/ f.txt',
'echo x > f.txt',
]
test('harmless inline one-liners and normal commands are allowed', () => {
@@ -76,3 +90,22 @@ test('denial message points at the right tools', () => {
assert.match(m, /read\/grep\/edit/)
assert.match(m, /computation-only one-liners are fine/)
})
test('pageText caps output at pageLines and names the next read', () => {
const text = Array.from({ length: 450 }, (_, i) => `l${i + 1}`).join('\n') + '\n'
let saved
const p = pageText(text, 200, (t) => { saved = t; return '/tmp/x.txt' })
assert.equal(saved, text)
assert.equal(p.text.split('\n').length, 201)
assert.match(p.text, /\[output lines 1-200 of 450\. Read lines 201-400: read file_path="\/tmp\/x\.txt" offset=201/)
assert.equal(pageText('a\nb\n', 200, () => { throw new Error('no save') }), null)
})
test('pageShellResult pages stdout and leaves short streams alone', () => {
const long = Array.from({ length: 250 }, (_, i) => String(i)).join('\n')
const v = pageShellResult({ kind: 'foreground', stdout: { text: long, truncated: false }, stderr: { text: 'e', truncated: false } }, 200, () => '/tmp/full.txt')
assert.equal(v.stdout.spillPath, '/tmp/full.txt')
assert.match(v.stdout.text, /stdout lines 1-200 of 250/)
assert.equal(v.stderr.text, 'e')
assert.deepEqual(pageShellResult({ kind: 'background', jobId: 'j' }, 200), { kind: 'background', jobId: 'j' })
})