Skip to content

fix: block critical recursive delete paths#524

Open
Vasyl198 wants to merge 1 commit into
FailproofAI:mainfrom
Vasyl198:fix/block-rm-rf-critical-paths
Open

fix: block critical recursive delete paths#524
Vasyl198 wants to merge 1 commit into
FailproofAI:mainfrom
Vasyl198:fix/block-rm-rf-critical-paths

Conversation

@Vasyl198

@Vasyl198 Vasyl198 commented Jul 16, 2026

Copy link
Copy Markdown

What

Extends block-rm-rf coverage to deny recursive deletion of critical paths:

  • $HOME, ${HOME}, and home-relative paths such as ~/Documents
  • user home directories such as /home/<user> and /Users/<user>
  • critical system directories such as /var/lib, /var/log, /usr/lib, and /usr/local
  • find / -delete and equivalent home-root targets

An explicit allowPaths entry still permits the configured home-relative path.

Why

The policy previously recognized only the root, ~, and single-segment absolute paths. Common destructive forms could bypass it while ordinary project cleanup should remain allowed.

Addresses #520.

Validation

  • npx vitest run __tests__/hooks/builtin-policies.test.ts -t "block-rm-rf" — 37 passed
  • npx eslint src/hooks/builtin-policies.ts __tests__/hooks/builtin-policies.test.ts
  • npx tsc --noEmit

The full policy test file still has an unrelated pre-existing failure in block-read-outside-cwd allow-path handling. Docker smoke testing was unavailable because Docker Desktop's local API did not become ready.

Summary by CodeRabbit

  • Bug Fixes
    • Improved protection against catastrophic recursive deletions involving home directories, system paths, and critical filesystem locations.
    • Added safeguards for destructive find ... -delete commands targeting critical directories.
    • Confirmed configured safe paths, such as ~/scratch, remain permitted.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

blockRmRf now centralizes critical-path detection, blocks additional find -delete cases, and adds parameterized tests for denied targets and an allowed home-relative path.

Changes

Recursive-delete policy

Layer / File(s) Summary
Critical path classification
src/hooks/builtin-policies.ts
Adds isCriticalRecursiveDeletePath to normalize and match root, home, and system-directory targets.
Policy enforcement and coverage
src/hooks/builtin-policies.ts, __tests__/hooks/builtin-policies.test.ts
Uses the helper in blockRmRf, blocks selected find -delete commands, and adds deny and allow cases.
Estimated code review effort: 2 (Simple) ~10 minutes

Possibly related issues

  • FailproofAI/failproofai issue 520 — Directly covers the expanded blockRmRf detection, find -delete blocking, and related tests.

Suggested reviewers: niveditjain

Poem

I hopped through paths both near and far,
And stopped the rm commands before they scar.
Home and root now heed the call,
While scratch may still be used by all.
Tests bloom bright in rows of green—
A careful bunny guards the machine.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: blocking critical recursive delete paths.
Description check ✅ Passed The description covers what, why, and validation, though the type-of-change and checklist sections are omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Vasyl198
Vasyl198 marked this pull request as ready for review July 16, 2026 11:19
@hermes-exosphere

Copy link
Copy Markdown
Contributor

Automated code review started - full review. Results will be posted here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
__tests__/hooks/builtin-policies.test.ts (1)

621-630: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add test coverage for $HOME/ relative paths and multiple slashes.

To ensure the edge-case bypasses identified in the implementation review are prevented, consider adding these permutations to the parameterized tests.

♻️ Proposed test additions
     it.each([
       "rm -rf $HOME",
       "rm -rf ${HOME}",
+      "rm -rf $HOME/Documents",
       "rm -rf ~/Documents",
+      "rm -rf ~//Documents",
       "rm -rf /home/chetan",
       "rm -rf /var/lib",
+      "rm -rf //var/lib",
     ])("blocks critical recursive-delete target %s", async (command) => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/hooks/builtin-policies.test.ts` around lines 621 - 630, Extend the
parameterized cases in the “blocks critical recursive-delete target” test to
cover recursive-delete commands using the relative `$HOME/` form and paths
containing multiple slashes. Keep the existing context setup and deny assertion
unchanged, ensuring both added permutations are rejected by policy.fn.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/builtin-policies.ts`:
- Around line 649-653: Replace the raw-command regex in the find deletion check
with parsed-token analysis: identify an actual find executable invocation,
require a -delete argument, and validate its target paths using the same
critical-path logic as rm handling (including paths such as // and /var/lib).
Preserve the existing deny response while avoiding matches inside quoted
arguments or unrelated commands.
- Around line 634-644: Update isCriticalRecursiveDeletePath to collapse
consecutive slashes in the normalized path before applying the protected-path
checks, so inputs such as //usr and /var//lib match the existing rules. Extend
the home-relative checks to recognize paths beginning with $HOME/ and ${HOME}/
in addition to ~/ while preserving the existing exact home-token handling.

---

Nitpick comments:
In `@__tests__/hooks/builtin-policies.test.ts`:
- Around line 621-630: Extend the parameterized cases in the “blocks critical
recursive-delete target” test to cover recursive-delete commands using the
relative `$HOME/` form and paths containing multiple slashes. Keep the existing
context setup and deny assertion unchanged, ensuring both added permutations are
rejected by policy.fn.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c3323c33-ff33-4b23-a793-c18560c08d85

📥 Commits

Reviewing files that changed from the base of the PR and between 446484b and 259b783.

📒 Files selected for processing (2)
  • __tests__/hooks/builtin-policies.test.ts
  • src/hooks/builtin-policies.ts

Comment on lines +634 to +644
function isCriticalRecursiveDeletePath(token: string): boolean {
const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
return normalized === "/"
|| normalized === "~"
|| normalized === "$HOME"
|| normalized === "${HOME}"
|| normalized.startsWith("~/")
|| /^\/[A-Za-z_][\w.-]*$/.test(normalized)
|| /^\/(?:home|Users)\/[^/]+$/.test(normalized)
|| /^\/(?:var\/(?:lib|log)|usr\/(?:lib|local))$/.test(normalized);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fix path normalization to prevent bypasses and include $HOME relative paths.

  1. Multiple slashes bypass: Paths like //usr, //var/lib, or /var//lib will bypass the exact regex matches because multiple consecutive slashes are not collapsed (e.g., //usr is perfectly valid in Bash but fails /^\/[A-Za-z_]/).
  2. Missing $HOME/ coverage: The logic correctly covers ~/ with normalized.startsWith("~/"), but misses $HOME/ and ${HOME}/. This leaves rm -rf $HOME/Documents exposed, contradicting the PR objective to protect home-relative paths.

Normalize paths by collapsing consecutive slashes, and add checks for $HOME/ and ${HOME}/.

🛡️ Proposed fix
 function isCriticalRecursiveDeletePath(token: string): boolean {
-  const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
+  const normalized = token.replace(/\/{2,}/g, "/").replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
   return normalized === "/"
     || normalized === "~"
     || normalized === "$HOME"
     || normalized === "${HOME}"
     || normalized.startsWith("~/")
+    || normalized.startsWith("$HOME/")
+    || normalized.startsWith("${HOME}/")
     || /^\/[A-Za-z_][\w.-]*$/.test(normalized)
     || /^\/(?:home|Users)\/[^/]+$/.test(normalized)
     || /^\/(?:var\/(?:lib|log)|usr\/(?:lib|local))$/.test(normalized);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function isCriticalRecursiveDeletePath(token: string): boolean {
const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
return normalized === "/"
|| normalized === "~"
|| normalized === "$HOME"
|| normalized === "${HOME}"
|| normalized.startsWith("~/")
|| /^\/[A-Za-z_][\w.-]*$/.test(normalized)
|| /^\/(?:home|Users)\/[^/]+$/.test(normalized)
|| /^\/(?:var\/(?:lib|log)|usr\/(?:lib|local))$/.test(normalized);
}
function isCriticalRecursiveDeletePath(token: string): boolean {
const normalized = token.replace(/\/{2,}/g, "/").replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");
return normalized === "/"
|| normalized === "~"
|| normalized === "$HOME"
|| normalized === "${HOME}"
|| normalized.startsWith("~/")
|| normalized.startsWith("$HOME/")
|| normalized.startsWith("${HOME}/")
|| /^\/[A-Za-z_][\w.-]*$/.test(normalized)
|| /^\/(?:home|Users)\/[^/]+$/.test(normalized)
|| /^\/(?:var\/(?:lib|log)|usr\/(?:lib|local))$/.test(normalized);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/builtin-policies.ts` around lines 634 - 644, Update
isCriticalRecursiveDeletePath to collapse consecutive slashes in the normalized
path before applying the protected-path checks, so inputs such as //usr and
/var//lib match the existing rules. Extend the home-relative checks to recognize
paths beginning with $HOME/ and ${HOME}/ in addition to ~/ while preserving the
existing exact home-token handling.

Comment on lines +649 to +653
const hasDestructivePath = parseArgvTokens(cmd).some(isCriticalRecursiveDeletePath);

if (/\bfind\s+(?:\/|~|\$HOME|\$\{HOME\})(?=\s|$)[\s\S]*?\s-delete\b/.test(cmd)) {
return deny("Catastrophic deletion blocked");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Refine find -delete detection to avoid false positives and bypasses.

The current regex for find operates on the raw cmd string:

  1. False positives: Commands like echo "find / -delete" or git commit -m "find / -delete" will incorrectly trigger a block because token boundaries and quotes are ignored.
  2. Bypasses: find // -delete bypasses the exact string match.
  3. Inconsistent coverage: Hardcoding (?:\/|~|\$HOME|\$\{HOME\}) misses other critical paths (like /var/lib) that are correctly protected against rm -rf.

You can fix all of these by leveraging the parsed tokens to verify that find and -delete are actual executable tokens, holding find target paths to the exact same standard as rm.

🛡️ Proposed fix
-  const hasDestructivePath = parseArgvTokens(cmd).some(isCriticalRecursiveDeletePath);
-
-  if (/\bfind\s+(?:\/|~|\$HOME|\$\{HOME\})(?=\s|$)[\s\S]*?\s-delete\b/.test(cmd)) {
-    return deny("Catastrophic deletion blocked");
-  }
+  const tokens = parseArgvTokens(cmd);
+  const hasDestructivePath = tokens.some(isCriticalRecursiveDeletePath);
+
+  const findIdx = tokens.indexOf("find");
+  const deleteIdx = tokens.indexOf("-delete");
+  if (findIdx !== -1 && deleteIdx > findIdx) {
+    const findArgs = tokens.slice(findIdx + 1, deleteIdx);
+    if (findArgs.some(isCriticalRecursiveDeletePath)) {
+      return deny("Catastrophic deletion blocked");
+    }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasDestructivePath = parseArgvTokens(cmd).some(isCriticalRecursiveDeletePath);
if (/\bfind\s+(?:\/|~|\$HOME|\$\{HOME\})(?=\s|$)[\s\S]*?\s-delete\b/.test(cmd)) {
return deny("Catastrophic deletion blocked");
}
const tokens = parseArgvTokens(cmd);
const hasDestructivePath = tokens.some(isCriticalRecursiveDeletePath);
const findIdx = tokens.indexOf("find");
const deleteIdx = tokens.indexOf("-delete");
if (findIdx !== -1 && deleteIdx > findIdx) {
const findArgs = tokens.slice(findIdx + 1, deleteIdx);
if (findArgs.some(isCriticalRecursiveDeletePath)) {
return deny("Catastrophic deletion blocked");
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/builtin-policies.ts` around lines 649 - 653, Replace the
raw-command regex in the find deletion check with parsed-token analysis:
identify an actual find executable invocation, require a -delete argument, and
validate its target paths using the same critical-path logic as rm handling
(including paths such as // and /var/lib). Preserve the existing deny response
while avoiding matches inside quoted arguments or unrelated commands.

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Review failed: Could not clone repository.

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Your PR is awaiting review by a moderator. Till then you can join the Discord for conversation: https://discord.gg/qaFM2uYFb

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Your PR is awaiting review by a moderator. Till then you can join the Discord for conversation: https://discord.befailproof.ai

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Your PR is awaiting review by a reviewer. Till then you can join the Discord for conversation: https://discord.befailproof.ai

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Automated code review started - full review. Results will be posted here.

@hermes-exosphere

Copy link
Copy Markdown
Contributor

Phase 2 in progress - deep code analysis. Detected a focused security-focused PR: extends block-rm-rf with critical path protection. Continuing to Phase 3 (build + test) next.

}

function isCriticalRecursiveDeletePath(token: string): boolean {
const normalized = token.replace(/\/\*$/, "").replace(/\/+$/, "") || (token.startsWith("/") ? "/" : "");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path normalization bypass via multiple slashes

The isCriticalRecursiveDeletePath function at line 635 normalizes trailing slashes but does not collapse multiple consecutive slashes in the middle. Paths like //usr, /var//lib, or //home/user bypass all the pattern checks.

Fix: Add .replace(/\/{2,}/g, '/') before existing normalizations. Also add startsWith('$HOME/') and startsWith('${HOME}/') checks.

});
const hasDestructivePath = parseArgvTokens(cmd).some(isCriticalRecursiveDeletePath);

if (/\bfind\s+(?:\/|~|\$HOME|\$\{HOME\})(?=\s|$)[\s\S]*?\s-delete\b/.test(cmd)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

find with -delete flag: false positives and bypasses

The regex at line 651 checks raw command text: /\bfind\s+(?:\/|~|\/home/failproofai|\$\{HOME\})[\s\S]*?\s-delete\b/. This has two issues:

  1. False positives: Commands like echo "find /" && echo "-delete" or git commit -m "find / with cleanup to delete" will incorrectly trigger a block because the regex doesn't respect token boundaries or quotes.
  2. Bypasses: find // --delete, find / -exec rm -rf {} \;, and other delete-equivalent forms slip through.
  3. Incomplete coverage: Only checks for / | ~ | $HOME paths but misses /var/lib, /usr/local, /home/user, and other critical paths that isCriticalRecursiveDeletePath already handles.

Fix: Use parsed tokens instead of raw regex. Check whether find and -delete (or --delete) are actual argv tokens, and whether any target paths between them match isCriticalRecursiveDeletePath. This aligns the find check with the rm check and eliminates both false positives and bypasses.

|| normalized === "~"
|| normalized === "$HOME"
|| normalized === "${HOME}"
|| normalized.startsWith("~/")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing $HOME/ and ${HOME}/ relative-path protection

The function checks normalized === "$HOME" and normalized === "${HOME}" as exact tokens, and checks normalized.startsWith("~/") for home-relative paths. But it does NOT check normalized.startsWith("$HOME/") or normalized.startsWith("${HOME}/").

This means rm -rf $HOME/Documents is blocked (because parseArgvTokens splits on whitespace, so $HOME/Documents is one token that fails startsWith("~/") and ===$HOME), but rm -rf "$HOME/Documents" (quoted single token) is treated differently — wait, actually it IS blocked because isCriticalRecursiveDeletePath receives the raw token $HOME/Documents which matches none of the patterns. After parseArgvTokens splits on whitespace and strips quotes, the token becomes just $HOME/Documents, which does not trigger any critical path check.

Fix: Add normalized.startsWith("$HOME/") and normalized.startsWith("${HOME}/") alongside the existing tilde check to ensure consistent protection for all home-relative path notations.

@hermes-exosphere

Copy link
Copy Markdown
Contributor

🔍 Automated Code Review

📋 Executive Summary

This PR extends the block-rm-rf builtin policy to recognize critical recursive-delete targets that were previously missed: $HOME, ${HOME}, home-relative paths (~/Documents), user home directories (/home/user), system directories (/var/lib, /usr/local), and find / -delete. The refactoring extracts a clean isCriticalRecursiveDeletePath helper and adds 6 targeted test cases. Code quality is good — linter and TypeScript pass cleanly, all 37 block-rm-rf tests pass. Three moderate findings noted below; none are blocking.


📊 Change Architecture

graph TD
    A["blockRmRf() policy"] -->|"extracts"| B["isCriticalRecursiveDeletePath()"]
    B --> C["Exact matches: / , ~ , $HOME , ${HOME}"]
    B --> D["Prefix check: ~/ paths"]
    B --> E["Regex: single-segment /X paths"]
    B --> F["Regex: /home/user, /Users/user"]
    B --> G["Regex: /var/lib, /var/log, /usr/lib, /usr/local"]
    A --> H["Raw regex: find PATH -delete"]
    A --> I["Existing: rm flag detection (combined + separated)"]
    style B fill:#90EE90
    style H fill:#FFD700
Loading

Legend: 🟢 New | 🟡 Risk (regex-based, no token parsing)


🔴 Breaking Changes

✅ No breaking changes detected. This is a pure additive extension to an existing policy — nothing removed, no API changed, no config modified.


⚠️ Issues Found

  1. 🟡 Warningsrc/hooks/builtin-policies.ts:635 — Path normalization missing multiple-slash collapse (see inline comment)
  2. 🟡 Warningsrc/hooks/builtin-policies.ts:651 — find path detection uses raw regex instead of parsed tokens (see inline comment)
  3. 🟡 Warningsrc/hooks/builtin-policies.ts:640 — Missing $HOME/ and ${HOME}/ relative-path startsWith checks (see inline comment)

🔬 Logical / Bug Analysis

What works well:

  • Extracting isCriticalRecursiveDeletePath is clean refactoring. It's a pure function, easy to test in isolation, and reused correctly.
  • The test coverage additions are targeted and effective. Each new test case maps directly to a bypass documented in issue block-rm-rf waves through several catastrophic-deletion forms #520.
  • The allowPaths integration works correctly — the test for ~/scratch with allowPaths: ["~/scratch"] confirms the allowlist path is preserved.
  • ESLint and TypeScript pass without errors.

What needs attention:

  • The find check still uses raw-command regex matching instead of the parsed-token approach used for rm. This creates both false positives (triggering on unrelated strings) and bypasses (e.g., find // with multi slashes, find ... -exec rm -rf).
  • Path normalization only strips trailing slashes but doesn't collapse internal multiple slashes (//usr bypass).
  • startsWith("~/") is checked but not startsWith("$HOME/") or startsWith("${HOME}/") — though in practice this may be less critical since $HOME/Documents gets split into two tokens by parseArgvTokens.

No logic errors found:

  • No race conditions (sync code only)
  • No null/undefined issues (parseArgvTokens always returns array, fallback handles empty)
  • No off-by-one issues
  • No resource leaks

🧪 Evidence — Build & Test Results

Test Results
RUN  v4.1.10 /tmp/pr524

 ✓ __tests__/hooks/builtin-policies.test.ts (431 tests | 394 skipped) 21ms
   block-rm-rf: 37 passed, 0 failed

 Test Files  1 passed (1)
      Tests  37 passed | 394 skipped (431)
   Start at  13:47:41
   Duration  1.18s
Full test suite: 2070 passed, 1 failed (pre-existing in Pi integration)
Lint & TypeScript
bunx eslint src/hooks/builtin-policies.ts __tests__/hooks/builtin-policies.test.ts
→ exit 0 (clean)

bunx tsc --noEmit
→ exit 0 (clean)

Docker smoke test (package build): ✅ npm pack succeeded, Next.js build completed.


🔗 Issue Linkage

This PR addresses #520 ("block-rm-rf waves through several catastrophic-deletion forms"). Changes directly close 3 of the 4 reported bypasses (rm -rf on home vars/home dirs/system dirs, find path with -delete) and partially address the rm -rf via command substitution (still acknowledged as out of scope for this pass, as noted in the issue).


👥 External Review Feedback

  • @coderabbitai (2 unresolved threads, both on src/hooks/builtin-policies.ts):
    • Thread 1 (line 644): Path normalization — slashes + $HOME/ coverage → ⚠️ Still present in current code. This review re-identifies the same issue at the updated line 635.
    • Thread 2 (line 653): find -delete raw regex → ⚠️ Still present. This review re-identifies this at line 651.
    • Both CodeRabbit suggestions remain unaddressed in this commit.

No human reviewer feedback found on this PR.


💡 Suggestions

  1. Consider find ... -exec rm -rf — Currently only -delete is checked, but -exec rm -rf {} \; and -exec rm -rf {} + have the same catastrophic effect. A follow-up could extend find checking to cover these patterns too.
  2. Consider a follow-up for command substitutionrm -rf $(echo /) remains unaddressed (the PR body acknowledges this as out of scope), but it's documented in issue block-rm-rf waves through several catastrophic-deletion forms #520.
  3. Test for false positives — Add a test confirming that echo "find / with cleanup to delete" is NOT blocked, to validate future token-based improvements.

🏆 Verdict

VERDICT: APPROVED_WITH_SUGGESTIONS


Automated code review · 2026-07-17 08:24:52 UTC

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: Approved with suggestions. See inline comments and summary for details. ✅

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/hooks/builtin-policies.ts --items all --match 'blockRmRf|isCriticalRecursiveDeletePath|parseArgvTokens' --view expanded
printf '\n--- Relevant policy definitions and call sites ---\n'
rg -n -C 12 'function (isCriticalRecursiveDeletePath|blockRmRf|parseArgvTokens)|const (isCriticalRecursiveDeletePath|blockRmRf|parseArgvTokens)|\b(parseArgvTokens|isCriticalRecursiveDeletePath)\s*\(' src/hooks/builtin-policies.ts

printf '\n--- Focused block-rm-rf tests ---\n'
rg -n -C 8 'block-rm-rf|find .*-(delete|exec)|\$HOME|~/' __tests__/hooks/builtin-policies.test.ts

printf '\n--- parseArgvTokens definitions outside the policy file ---\n'
rg -n -C 6 '\b(parseArgvTokens)\s*[=:<(]' -g '*.{ts,tsx,js,mjs,cjs}' .

Length of output: 19031


@hermes-exosphere Confirmed: the three warnings are valid in the current implementation.

  • Repeated-slash bypass: //usr is not canonicalized to /usr, so rm -rf //usr misses the critical-path matcher.
  • Home-relative environment paths: parseArgvTokens() preserves $HOME/Documents and ${HOME}/Documents as single tokens, so neither is caught without explicit prefix checks.
  • find false positives: the whole-command regex can match quoted text, e.g. echo "find / -delete", rather than a real find invocation.

The suggested find / -exec rm -rf … case is already denied when its path is /, because the existing rm detection sees both / and the recursive/force flags. It is still reasonable to improve command-aware parsing as broader hardening.

The targeted repairs should canonicalize repeated separators, recognize $HOME/ and ${HOME}/ prefixes, and derive find’s first path argument plus -delete from tokens rather than matching the raw command string.

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review: Approved. ✅

@chhhee10 chhhee10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Vasyl198 — all the #520 cases close properly and the baselines hold, nice to see tests with it.

One blocker: startsWith("~/") matches at any depth, so rm -rf ~/dev/myapp/node_modules now denies (allowed on main) — that's normal cleanup. Same root: ~/Documents denies but
/home//Documents allows, and find /home/ -delete slips through.

A depth-based rule should fix all three — deny the home dir + its immediate children, allow anything deeper, same for both notations, and reuse it for find.

Non-blocking: $(echo /) still gets through, can be its own issue. Ping me when it's updated.

@hermes-exosphere hermes-exosphere left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hermes Agent Code Review

Verdict: APPROVED

Summary

This PR extends the block-rm-rf policy to cover additional critical recursive-delete paths and find ... -delete variants targeting root or home. The changes are clean, well-factored, and thoroughly tested.

What Changed

  • src/hooks/builtin-policies.ts: Extracted isCriticalRecursiveDeletePath() from the inline logic, expanding it to match $HOME, ${HOME}, ~/-prefixed paths, /home/<user>, /Users/<user>, /var/lib, /var/log, /usr/lib, /usr/local. Added a separate find ... -delete check for find /, find ~, find $HOME, find ${HOME} variants.

  • __tests__/hooks/builtin-policies.test.ts: Added tests for the new blocked paths, the find / -delete case, and the allowPaths override for ~/scratch.

What Works Well

  • Clean factoring: The isCriticalRecursiveDeletePath function isolates path classification from the delete-detection logic, making both easier to read and maintain.
  • Comprehensive regex patterns: Each critical path category is covered by a specific regex, avoiding over-broad matches (e.g., /usr/local/bin is correctly not flagged as critical).
  • allowPaths integration: The ~/scratch test confirms that configured safe paths override the new restrictions. The rmTargetIsAllowed function handles this correctly via path-boundary comparison.
  • find ... -delete catch: Early return before the rm-specific checks correctly blocks find / -delete even though it has nothing to do with rm.
  • All CI green: 10/10 checks passing (build, test, e2e, lint, type-check, docs, quality, CodeRabbit, OSV-Scanner, Socket Security).
  • 30 block-rm-rf tests pass locally (npx vitest run -t "block-rm-rf").

Edge Cases Verified

The following patterns were evaluated against the new logic:

Command Expected Result Actual
rm -rf /var/lib Deny ✅ Blocked
rm -rf /usr/local Deny ✅ Blocked
rm -rf /usr/local/bin Allow (not critical) ✅ Allowed
rm -rf ~/Documents Deny ✅ Blocked
rm -rf ~/scratch (with allowPaths) Allow ✅ Allowed
rm -rf /home/user/project Allow (depth 2+) ✅ Allowed
find / -delete Deny ✅ Blocked
find $HOME -delete Deny ✅ Blocked
rm -rf /var/lib/mysql (subdirectory) Allow ✅ Allowed
rm -rf /tmp Deny ✅ Blocked
Quoted paths (single/double) Deny ✅ Blocked
Chained via && Deny ✅ Blocked

Observations (Non-Blocking)

  1. $HOME/some/path is not flagged: A command like rm -rf $HOME/Documents passes through because isCriticalRecursiveDeletePath only matches the bare $HOME string, not variable-prefixed paths. This is a fundamental limitation of string-based policy analysis before shell expansion and is out of scope for this PR.

  2. find /var/lib -delete is not caught: The find check only covers find /, find ~, find $HOME, and find ${HOME} roots. Other critical paths like /var/lib are only checked in the rm-specific logic. If future iterations want to expand the find check to all isCriticalRecursiveDeletePath tokens, it would be straightforward — the function is already available.

  3. allowPaths for the find check: The find ... -delete early return does not consult allowPaths. This is fine because find / -delete in any form is catastrophic, but worth noting if an allow-path override for find is ever needed.

New Tests Added

✅ blocks critical recursive-delete target: rm -rf $HOME
✅ blocks critical recursive-delete target: rm -rf ${HOME}
✅ blocks critical recursive-delete target: rm -rf ~/Documents
✅ blocks critical recursive-delete target: rm -rf /home/chetan
✅ blocks critical recursive-delete target: rm -rf /var/lib
✅ blocks find / -delete
✅ allows a configured home-relative path (~/scratch with allowPaths)

Reviewed by Hermes Agent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants