Add a Known gaps section to docs/agent-guardrails.md and tighten the bash_protected_port row. Each gap was probed at the hook boundary: - the plugin inspects only task, call_omo_agent, edit, write and bash, so webfetch of localhost:8080 passes (seen live 2026-10-04) - bash_protected_port matches literal host:port text, so [::1], the machine address and URLs held in variables are missed - bash -c / sh -c wrappers evade bash_banned and bash_main_checkout (the port rule still sees inside them) - a relative cd chain after an absolute one can be mis-anchored Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkCGRantZsSwmcFpet6FTa
71 lines
3.7 KiB
Markdown
71 lines
3.7 KiB
Markdown
# Agent guardrails
|
|
|
|
An opencode plugin (`deploy/opencode-plugin/guardrails.js`) that intercepts
|
|
tool.execute calls and blocks or warns on patterns that have caused expensive
|
|
failures on this repo.
|
|
|
|
## Install
|
|
|
|
```
|
|
command cp -f deploy/opencode-plugin/guardrails.js ~/.config/opencode/plugins/
|
|
cp -n deploy/opencode-plugin/guardrails.example.json .omo/guardrails.json
|
|
```
|
|
|
|
Restart opencode. Check `~/.local/share/opencode/log/opencode.log` for
|
|
`failed to load plugin`.
|
|
|
|
## Uninstall
|
|
|
|
Delete `.omo/guardrails.json` (plugin goes inert) or remove
|
|
`guardrails.js` from `~/.config/opencode/plugins/`.
|
|
|
|
## Rule modes
|
|
|
|
Each rule can be `"block"` (default), `"warn"` (logs only, allows the
|
|
call), or `"off"` (no check). Edit in `.omo/guardrails.json`.
|
|
|
|
## Rules
|
|
|
|
| Rule | Blocks | Evidence |
|
|
|---|---|---|
|
|
| `task_needs_agent` | `task()`/`call_omo_agent` with no category, subagent_type, AND task_id | 2026-09-26..28: three such calls reported "completed" with zero work |
|
|
| `task_banned_agent` | `task()`/`call_omo_agent` to `oh-my-claudecode:*` agents | 2026-09-26: 12 dispatches to `oh-my-claudecode:writer` did zero work (pinned Anthropic model) |
|
|
| `task_worktree_line` | task prompt without `WORKTREE:` line when boulder has worktree_path, or mismatch | 2026-09-28: worker rewrote the main checkout's live config.yaml |
|
|
| `write_outside_worktree` | writes to files outside the worktree scope | same 2026-09-28 incident |
|
|
| `bash_main_checkout` | git/redirect ops targeting the main checkout from a worktree task | same class as 2026-09-28 main-checkout rewrite |
|
|
| `bash_banned` | `pkill`, `git stash`, `kill`, `systemctl` | 2026-09-26: unasked PR; 2026-09-29: git stash in wrong checkout |
|
|
| `bash_protected_port` | bash commands containing `localhost`, `127.0.0.1` or `0.0.0.0` followed by `:8080` (list: `protected_ports`) | 8080 is the production router and opencode's own model traffic runs through it (CLAUDE.md); 2026-08-29 incident #1: an agent `pkill`ed it to free the port for a throwaway, which belongs on 8081 |
|
|
| `plan_tick_gate` | tick gate blocks a todo tick while verify_commit fails | 2026-09-28: todo committed 3 failing tests and was reported done |
|
|
|
|
## Known gaps
|
|
|
|
Accepted on purpose; each was probed at the hook boundary. Revisit when an agent
|
|
is seen doing real damage through one of them, not before.
|
|
|
|
- **The plugin inspects only `task`, `call_omo_agent`, `edit`, `write` and
|
|
`bash`.** `bash_protected_port` therefore does not see `webfetch` (or any MCP
|
|
fetch tool): on 2026-10-04 a blocked `curl localhost:8080/health` was retried
|
|
through `webfetch` and succeeded. A fetch is a GET; the calls that spend or
|
|
change production (`/dispatch`, admin POSTs) normally go through bash, which
|
|
is covered.
|
|
- **`bash_protected_port` matches literal text.** It misses `[::1]:8080`, the
|
|
machine's own address, a URL held in a variable, and a script file that calls
|
|
8080 internally. In the other direction, a commit message or argument that
|
|
names `localhost:8080` is blocked. Text-only commands (`echo`, `grep`, `cat`,
|
|
...) and heredoc bodies are skipped unless fed to an interpreter. It does see
|
|
inside `bash -c '...'` and `python3 -c '...'`, because it reads the raw string.
|
|
- **`bash -c '...'` and `sh -c '...'` wrappers are not inspected by
|
|
`bash_banned` or `bash_main_checkout`.** `bash -c 'pkill -f foo'` and
|
|
`bash -c 'git stash'` both pass.
|
|
- **`bash_main_checkout` can mis-anchor a relative `cd` chain that follows an
|
|
absolute one** (one block in the replay report).
|
|
|
|
## Log
|
|
|
|
Blocks, warnings, and internal errors go to `.omo/guardrails.log` (JSON
|
|
lines, relative to the project root).
|
|
|
|
## Upstream
|
|
|
|
`scripts/verify_commit.py` and `scripts/oc_dispatch_audit.py` are the two
|
|
audit scripts the plugin and the agent procedures rely on. |