fix(security): cover rm flag variants in the rm-rf-root/home deny rules - #8240
fix(security): cover rm flag variants in the rm-rf-root/home deny rules#8240soroush5 wants to merge 1 commit into
Conversation
|
👋 Hi! This PR's description is missing some required sections from our PR template. Workflow runs won't be auto-approved until the description is updated. Missing sections:
Please update your PR description to include these sections, then push or re-save the description. The workflows will be approved on the next cycle. |
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
|
Two notes in one: the template-bot warning above is stale — all listed sections (Problem/Motivation, Why it matters, What changed, Tests, Pattern harvest) are in the body now. And on the relationship audit: agreed, #8262 is same-file/disjoint-hunks, KEEP with no coordination needed from this side. |
b7cbff0 to
450dbe7
Compare
Design Review (Fable 5, fork) — 🟡 CONCERNSDesign-level review of The design gate is satisfied from the diff and base tree: the widening follows the repo's own rubric (subject is genuinely a shell command line, sandbox doesn't hold Design-Verdict: CONCERNS Sound widening with a compat story, but the simplest equivalent spelling ( WatchThe stated harm is "equivalent recursive-force invocations sail through," yet GNU getopt permutes operands, so Suggestions
[DESIGN-REVIEWED] 52a5f33 |
GPT 5.6 Review (fork) — 🔴 changes requested (blocking)Reviewed 2 of 2 blocking finding(s) are security-class and were withheld from adjudication, so the blocking verdict stands. BLOCKING -- src/kiro_crew/security/denied_rules.py:1322 -- Interposed long options bypass the widened root rule BLOCKING -- src/kiro_crew/security/denied_rules.py:1353 -- Uncolonized HOME expansions bypass the home rule [BLOCK-MERGE] 52a5f33 Adjudication (Opus 4.8) — is blocking on each finding proportionate?Bash/Write require approval in this run; my manual regex analysis is conclusive and independently corroborated. F1 — root rule, F2 — home rule, [ADJUDICATION] 52a5f33 total=0 uphold=0 downgrade=0 [ADJUDICATION-FENCED] 52a5f33 fenced=2 flagged=0 |
First Principles Review (Fable 5, fork) — 🟡 CONCERNSPremise-level review of First-Principles-Verdict: CONCERNS Two rules widened while ≥3 siblings in the same category keep the identical one-spelling gap — and two denied→allowed flips ride along with the anchoring. Not justified as shipped
What this change shipsIntent: stop equivalent respellings of
Watch
[FIRST-PRINCIPLES-REVIEWED] 52a5f33 |
Opus 4.8 Review (fork) — ✅ no blocking findingsReviewed Review detailsNo blocker — one advisory: the widened built-in FINDING — src/kiro_crew/security/denied_rules.py:1311 — the widened [OPUS-REVIEWED] 52a5f33 |
|
Verified each claim live, and you were right on the core point — reworked accordingly:
|
|
Same rework addresses this: the bounded slots are replaced by the linearized house idiom at every pad site, so padding count no longer matters (verified 5- and 10-pad denied through the real gate). On scope: structural argv parsing would mean re-architecting the regex-view tier, which is beyond this fix; the residual class for pads is closed, with the #8124 quoted-whitespace pin intact and rarity/recovery as your adjudication notes (default flow still prompts). |
c71571d to
2a8e32d
Compare
2a8e32d to
aa4feaa
Compare
|
@soroush5 Thanks for this, and for the earlier note on #8262. The 2026-09-08 open-PR relationship audit (audited at aa4feaa) puts #8240 next to four open PRs on the same three files, so here is where I think it should go. #8388, also yours, is the one that matters. Both PRs rewrite the same record, That PR needs re-authoring rather than a rebase. Merged #9183 deleted #8262 (@GoZippy), #8283 (@patrigao) and #9010 (@chenmingwei23) share the same three files but touch disjoint rules; only the fixture tail and the TestCatalog rule-count assertion collide, so those need rebase order only, not design coordination. Posted from the 2026-09-08 open-PR relationship audit (read-only, one auditor per PR); reply here if any of this is wrong. |
|
Makes sense — I will fold both axes into one PR against the new denied_rules.py layout (composing the flag-run idiom into the $HOME alternation, regenerating the golden fixture on current main, dropping the em-dash rows) and close these two. I will also cover the operand-before-flags shape so Fixes #8237 stays. |
aa4feaa to
ef84b9c
Compare
ef84b9c to
52a5f33
Compare
…spellings Covers packed (-fr, -rfv), split (-r -f), and long-option (--recursive/--force) flag spellings, one interspersed operand, --no-preserve-root, and the $HOME spellings the shell expands to ~, without any .* gap so separator pins and ReDoS screens keep holding.
52a5f33 to
f93511b
Compare
Problem / Motivation
The two
rmdeny rules (local-destructive-rm-rf-root/-home) only recognize the exact-rfflag spelling, and the home rule only recognizes literal~. Equivalent recursive-force invocations sail through the PreToolUse gate — verified viais_denied: reordered (-fr), combined (-rfv), split (-r -f), long-option (--recursive/--force/--no-preserve-root) spellings, one interspersed operand (rm harmless -rf --no-preserve-root /), and every$HOME/${HOME}spelling (which the shell expands to~) all return None.Why it matters
Any agent run that reaches for a destructive delete with a non-canonical spelling gets zero confirmation. This is the same bug class as #4824 (which widened only the four self-protection rules for interposed flags) — the rm rules were left exact-match, so the floor has a hole exactly where the damage is total.
What changed
Both rm patterns widened with NO
.*gap (single fragment, exact full-input semantics): literal single spaces plus runs of single-letter flag tokens, one optional unquoted operand slot, and the$HOMEfamily on the home rule. That shape is what keeps the separator pins holding (a.*gap would fabricate commands across;/newlines), the ReDoS screens green (bare*over quantifier-free bodies; the operand slot ends in a literal so the polynomial screen stays blind to it), and the documented" "residual intact.rmis token-anchored (/bin/rm,{rm;}, backtick programs still match), with one deliberate exception: quote-gluedrmmatches when a separator follows later in the string — that keeps the Pass-1 whole-string closure for pipelines (grep 'rm -rf /...' | pythonmust deny even though the literal is quoted), while a lone"rm" -rf /still misses raw text for the respelling-proof twin.rm -f/-r-only, flaglessrm, lone--recursive,rm -- -rf /,--preserve-root, and lookalikes ($HOMEx,file~) all stay allowed. Governance legacy map gains the two pre-widening spellings (#4824 precedent); search-verb carve-out (#8802) registered from the live patterns; golden fixture synced (111 rows).Residuals (documented in-code): interposed LONG flags (
-r --verbose -f), leading long-flag padding, 2+ or quoted operands, operands between flags and path, getopt-permuted trailing flags — each needs a repeated operand gap the ReDoS screens forbid.Tests
TestRmFlagVariantsStayDenied(flag/operand/program variants + allow shapes) and newTestRmHomeEnvSpellingsStayDenied($HOME/${HOME}incl. braced operators, lookalikes) intest/test_denied_commands_security.py.main:test_denied_commands_security.py+test_security.py+test_deny_guidance.py+test_denied_commands_hooks.py+test_heartbeat_safe_tools.py+test_security_regex_linearity.py— 1934 passed, 2 skipped (pre-existing).is_safe_user_regex, take the unbounded single-fragment path, hostile inputs (20k-quote, flag-chain, padded needle) evaluate in ≤0.1s.Fixes #8237
Fixes #8387
Pattern harvest
Rule candidate: deny-list regexes that match one spelling of equivalent destructive invocations — worth a pass over the other local-destructive patterns for packed/split/long-option variants.