feat: lift shell read restrictions and page bash/read tool outputs to 200 lines
This commit is contained in:
1 parent
ee78e6d3a8
commit
de37af4b8c
7 files changed
+213
-32
No files matched your search
+34
-1
@@ -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' })
|
||||
})
|
||||
Reference in new issue
Block a user