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

+10 -14
View File
@@ -62,37 +62,33 @@ Every tagged `read`/`grep`/`edit` records a snapshot. When the file changed sinc
A tag that was never issued for that file (e.g. copied from another file) is rejected with a hint.
## Paging: read, grep and glob
## Paging: read, grep, glob and bash
Like oh-my-pi, outputs are cut into pages so the model never pulls a whole file or huge search into context at once:
- **`read`**: at most **200 lines** per call (`limit` above 200 is capped); the footer gives the `offset` to continue.
Outputs are cut into pages so the model never pulls a whole file, huge search, or giant shell stream into context at once:
- **`read`**: at most **200 lines** per call (`limit` above 200 is capped); the footer gives the `offset` to continue. The UI row indicates the line range being read (`Read lines A–B`).
- **`grep`**: at most **200 matches** per call, grouped under `[PATH#TAG]` headers. Pass `offset` for the next page (`… call grep again with offset=200`).
- **`glob`**: at most **200 paths** per call, with the same `offset` cursor.
- **`bash`**: output is capped at **200 lines** per stream (`stdout` / `stderr`). The complete, uncut output is saved to a temporary file (`/tmp/omp-edit-output/...txt`), and the footer instructs the model how to continue reading with `read` (`read file_path="..." offset=201`).
Change the page size with `pageLines`.
## Guard rule
A `tools/pre-execute` listener makes the agent use these tools instead of working around them through the shell. Denied calls get a short reason that names the right tool.
A `tools/pre-execute` listener ensures file modifications go through appropriate tools while lifting restrictions on reading files via shell. Writing through shell is blocked.
| Denied | Use instead |
| --- | --- |
| `sed -i`, `perl -i`, `ed`/`ex`, `tee`, `>`/`>>` into files, heredocs, `truncate`/`dd` | `edit` (hashline) |
| Inline scripts that can touch files, processes or the network (`node -e "require('fs')…"`, `node --input-type=module --eval "import …"`, `python3 -c "open(…)"`, `ruby -e "File.write…"`, `deno eval`, `bun -e`, `php -r`…), or scripts piped in through stdin (`… \| node`, `node - < f`) | `read`/`grep`/`edit` |
| `cat`/`head`/`tail`/`less`/`bat`/`nl FILE`, `sed -n`, `awk` | `read` (paged) |
| `grep`/`rg`/`ag`/`ack` over files | `grep` (paged, tagged) |
| `find`/`fd`/`locate`, `ls -R` | `glob` |
| Inline scripts that write files (`node -e "require('fs').writeFileSync…"`, `python3 -c "open(…,'w')"`, `ruby -e "File.write…"`, `awk` writes) | `edit` / `write` |
| `find -delete`, `find -fprint` | `edit` / file tools |
| `str_replace_editor`, `apply_patch` tools; `write` on an **existing** file | `edit` (`write` stays allowed for brand-new files) |
What stays allowed:
- **Reading files and searching via shell:** `cat`, `head`, `tail`, `less`, `more`, `bat`, `nl`, `sed -n`, `grep`, `rg`, `ag`, `find`, `ls -R`, read-only `awk` are all permitted. Their output is automatically paged to 200 lines by the `bash` wrapper.
- Text inside quotes is ignored (`git commit -m "fix > bug"`).
- Redirection to `/dev/null` is fine.
- Redirection to `/dev/null`, `/dev/stderr`, `/dev/stdout` is fine.
- Filters on piped output keep working (`npm test 2>&1 | tail -5`, `ps aux | grep node`).
- Computation-only one-liners: `node -e "console.log(1+1)"`, `node -p "2**10"`, `python3 -c "print(sum(range(9)))"`, `ruby -e 'puts 1+1'`. Running a file is a normal command (`node --test test/`, `node build.mjs`).
How inline scripts are judged: the guard works out which interpreter runs (also through `env`, `sudo` and absolute paths) and pulls out its program from `-e`/`-p`/`--eval`/`--print`/`--eval=`/`-c`/`-r`/`eval`. The program is allowed only if it contains **none** of the identifiers that start any I/O path, for example:
- JavaScript: `require`, `import`, `process`, `globalThis`, `eval`, `Function`, `constructor`, `fetch`, `Deno`, `Bun`, or bracket access with a string (`x['y']`);
- Python: `import`, `__import__`, `open`, `os`, `subprocess`, dunder attributes;
- Computation-only one-liners: `node -e "console.log(1+1)"`, `node -p "2**10"`, `python3 -c "print(sum(range(9)))"`.
- Ruby/Perl: `File`, `IO`, `system`, `open`, backticks.
Banning the identifiers that start an I/O path covers obfuscation too: `fs['write'+'File']` still needs `require` or `import` to get `fs`. A program the guard can't read (stdin, `-`) is denied.