Skip to content

Instantly share code, notes, and snippets.

@nateberkopec
Last active January 22, 2026 10:43
Show Gist options
  • Select an option

  • Save nateberkopec/b1edeff1e4b60c5a7cb54dc7481887cb to your computer and use it in GitHub Desktop.

Select an option

Save nateberkopec/b1edeff1e4b60c5a7cb54dc7481887cb to your computer and use it in GitHub Desktop.
Protecting Against Autonomous Agent rm -rf Commands

Protecting Against Agent rm -rf Commands

AI coding agents can run shell commands. Sometimes they run rm -rf by mistake. This deletes files forever. While of course I always read and approve all tool calls manually, by hand, and never let my agents work except under direct supervision 100% of the time, sometimes I miss things.

How It Works

  1. Hooks catch rm -rf before it runs
  2. Rewrite to trash so files go to Trash instead
  3. Lock the hook files so agents cannot delete them

Setup

Claude Code

Add to ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "$HOME/.claude/hooks/deny-rm-rf.py"
          }
        ]
      }
    ]
  }
}

Create ~/.claude/hooks/deny-rm-rf.py:

#!/usr/bin/env python3
import json
import re
import sys

RM_RF_PATTERN = re.compile(
    r"\brm\s+-[a-z]*r[a-z]*f[a-z]*\s+|\brm\s+-[a-z]*f[a-z]*r[a-z]*\s+", re.IGNORECASE
)

def main() -> int:
    try:
        input_data = json.load(sys.stdin)
    except json.JSONDecodeError as exc:
        print(f"Error: Invalid JSON input: {exc}", file=sys.stderr)
        return 1

    if input_data.get("tool_name") != "Bash":
        return 0

    tool_input = input_data.get("tool_input", {})
    command = tool_input.get("command", "")

    if RM_RF_PATTERN.search(command):
        new_command = RM_RF_PATTERN.sub("trash ", command)
        print(json.dumps({"updatedInput": {"command": new_command}}))

    return 0

if __name__ == "__main__":
    sys.exit(main())

Opencode

Create ~/.config/opencode/plugin/deny-rm-rf.js:

const RM_RF_PATTERN = /\brm\s+-[a-z]*r[a-z]*f[a-z]*\s+|\brm\s+-[a-z]*f[a-z]*r[a-z]*\s+/i

export const RewriteRmRf = async () => {
  return {
    "tool.execute.before": async (input, output) => {
      if (input.tool !== "bash") return

      const command = output?.args?.command ?? ""

      if (RM_RF_PATTERN.test(command)) {
        output.args.command = command.replaceAll(RM_RF_PATTERN, "trash ")
      }
    }
  }
}

Make Hook Files Immutable

An agent might try to delete the hooks. Make them immutable with macOS's chflags schg:

sudo chflags schg ~/.claude/hooks/deny-rm-rf.py
sudo chflags schg ~/.config/opencode/plugin/deny-rm-rf.js

Only root can remove the immutable flag. Agents cannot bypass this.

What You Need

  • macOS
  • trash: brew install trash
  • sudo access to lock files

Limits

  • macOS only
  • Only catches rm -rf

When an agent runs rm -rf /path, the hook changes it to trash /path. Files go to Trash. You can get them back.

See the full code at nateberkopec/dotfiles.

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