fix(approval): tighten 'delete in root path' regex to system-root targets only - #18089
briandevans wants to merge 2 commits into
Conversation
…gets only The "delete in root path" pattern in DANGEROUS_PATTERNS (tools/approval.py:215) matched any `rm` with an absolute path because it required only a single `/` after the optional flags. Every `rm /home/<user>/...`, `rm /tmp/...`, `rm /mnt/c/...` was flagged as "delete in root path" and forced an approval prompt for routine cleanup of the agent's own working files. Tighten the regex so it fires on actual system-root targets only — plain `/`, root globs (`/*`, `/.*`), and the system top-level dirs already enumerated by the HARDLINE rule (`/bin`, `/boot`, `/dev`, `/etc`, `/lib`, `/lib64`, `/opt`, `/proc`, `/root`, `/run`, `/sbin`, `/srv`, `/sys`, `/usr`, `/var`). Recursive rm of any deeper path remains caught by the separate `-r`/`--recursive` patterns at lines 216-217, so this only removes false positives, not coverage. Adds TestRmRootPathRegexPrecision covering all true-positive system roots (still flagged) and the false-positive cases from the issue (no longer flagged), plus a regression guard that recursive rm under /home stays dangerous via the recursive-delete rule. Fixes NousResearch#18083 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR narrows the dangerous-command detection rule for “delete in root path” so it no longer flags every rm against an absolute path, focusing instead on truly system-root targets and known top-level system directories.
Changes:
- Tightened the
DANGEROUS_PATTERNSregex forrm ... /...to only match/, root globs,/.*/-style root dotglob, and selected system directories. - Added targeted regression tests ensuring common absolute paths like
/home/...,/tmp/..., and/mnt/...no longer trigger the root-path rule while true root/system targets still do.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tools/approval.py | Refines the rm root-path dangerous pattern to reduce false-positive approvals on routine absolute-path deletes. |
| tests/tools/test_approval.py | Adds regression coverage for true-positive and false-positive rm cases related to the tightened regex. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -212,7 +212,7 @@ def _hardline_block_result(description: str) -> dict: | |||
| # ========================================================================= | |||
|
|
|||
| DANGEROUS_PATTERNS = [ | |||
|
|
||
| DANGEROUS_PATTERNS = [ | ||
| (r'\brm\s+(-[^\s]*\s+)*/', "delete in root path"), | ||
| (r'\brm\s+(-[^\s]*\s+)*/(\s|\*|$|\.\*|(bin|boot|dev|etc|lib|lib64|opt|proc|root|run|sbin|srv|sys|usr|var)(/|\s|$))', "delete in root path"), |
|
Fixes #18083 — tightens regex to only match genuine system-root targets. |
1 similar comment
|
Fixes #18083 — tightens regex to only match genuine system-root targets. |
…t path" Tightening the regex in this PR also changes the regex-derived `_legacy_pattern_key`, so existing `command_allowlist` / session approvals stored under the *previous* legacy key (`rm\s+(-[^\s]*\s+)*/`) would be silently dropped and force a re-prompt despite `is_approved()` claiming backwards compatibility for regex-derived keys. Add an explicit `_HISTORICAL_LEGACY_KEYS` table that aliases the prior legacy key to the canonical "delete in root path" description, and add a regression test that exercises the old key shape against the current canonical key. Also document why `DANGEROUS_PATTERNS`' system-dir list intentionally diverges from `HARDLINE_PATTERNS`' recursive-delete list — the two layers cover different shapes (single-file rm vs recursive delete) and unifying them would either over-block routine admin recursive deletes or under-block dangerous single-file rm of `/dev/sda`, `/proc/...`, etc. Addresses Copilot review on PR NousResearch#18089. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@copilot Both findings addressed in commit Finding 1 (legacy approval key drift, line 214): Real backwards-compat regression — the old regex-derived legacy key Finding 2 (duplicated system-dir list, line 215): The lists are intentionally different rather than drift, so I documented the contract in a code comment instead of unifying them. Specifically:
Unifying would either over-block ( |
|
Closing to keep the queue clean — tools/approval.py has been substantially refactored on main since this opened (#26829, sudo askpass/stdin, DELETE DOTALL bypass), so the regex precision concern may already be covered or shaped differently now. Happy to reopen with a rebased take if the 'delete in root path' precision concern from #18083 still reproduces. |
Summary
The
delete in root pathrule inDANGEROUS_PATTERNS(tools/approval.py:215) matched ANYrmwith an absolute path because the regex required only a single/after the optional flags. Tighten it so it fires on real system-root targets only — plain/, root globs, and the system top-level dirs already enumerated byHARDLINE_PATTERNS.The bug
The
/after the flag group matches the leading/of any absolute path, regardless of where in the filesystem the target lives. Everyrm /home/<user>/...,rm /tmp/...,rm /mnt/c/...is flagged asdelete in root pathand forced through an approval prompt — including routine cleanup of the agent's own working files. From the reporter's run:That's deleting one file in the user's home directory; nothing about it is near the system root.
The fix
Constrain what comes after the
/to the genuinely-dangerous shapes:System top-level dirs match the set already enumerated by the
HARDLINE_PATTERNSrules at lines 144-148, so the two layers stay aligned. Recursivermof any deeper path (including/home/...) is still caught by the separate-r/--recursiverules at lines 216-217 — coverage is unchanged for actually-recursive deletes.rm -rf /rm -rf /*rm -rf /.*rm -rf /etc/passwdrm -rf /var/logrm /usr/bin/somethingrm /home/user/foorm -f /home/user/foorm /tmp/xrm -rf /mnt/c/Users/user/foorm -rf /home/user/.cacheTest plan
tests/tools/test_approval.py— 143 passed (11 new inTestRmRootPathRegexPrecision)tests/tools/test_force_dangerous_override.py,test_approval_plugin_hooks.py,test_approval_heartbeat.py,test_cron_approval_mode.py— 37 passedold=True) and passes the new one (new=False); each true-positive case matches both. Verified end-to-end withre.compile(...).search(cmd).rm -rf /home/user/.cachestays dangerous via the separaterecursive deleterule (not via this rule), so coverage of recursive deletes is unchanged.Pre-existing baseline failures in
tests/tools/test_mcp_tool.py,test_mcp_oauth.py,test_mcp_reconnect_signal.py,test_tts_kittentts.pyare orthogonal — those modules are untouched.Related
DANGEROUS_PATTERNSwith the existingHARDLINE_PATTERNSset at tools/approval.py:144-148