Skip to content

Block Dangerous Commands

Reject rm -rf, curl | sh, writes to .env, and similar at the PreToolUse step.

ts
#!/usr/bin/env bun
// .hooks/guard.ts
import { defineHook, run } from "@pivanov/agent-hooks-bridge";

const DANGEROUS_BASH = [
  /\brm\s+(-[a-zA-Z]*r|--recursive)/,    // rm -rf, rm -Rfv, rm --recursive
  /curl[^|]*\|\s*(sh|bash|zsh)/,           // curl ... | sh
  /\bsudo\b/,                              // anything sudo
  /\bDROP\s+(TABLE|DATABASE)/i,            // SQL DROP
];

const PROTECTED_PATHS = [
  /^\.env(\.|$)/,
  /^secrets?\//,
  /\.pem$/,
  /\.key$/,
];

const hooks = defineHook({
  PreToolUse: (event) => {
    if (event.tool === "Bash") {
      const command = String(event.tool_input.command ?? "");
      for (const pattern of DANGEROUS_BASH) {
        if (pattern.test(command)) {
          return {
            decision: "deny",
            reason: `command matches policy block: /${pattern.source}/`,
            user_message: `Blocked: dangerous bash command (${pattern.source})`,
          };
        }
      }
    }

    if (event.tool === "Write" || event.tool === "Edit") {
      const file = event.tool_input.file_path;
      if (typeof file === "string") {
        for (const pattern of PROTECTED_PATHS) {
          if (pattern.test(file)) {
            return {
              decision: "deny",
              reason: `writes to ${file} are policy-blocked`,
              user_message: `Blocked: cannot write to ${file}`,
            };
          }
        }
      }
    }

    return { decision: "allow" };
  },
});

await run(hooks);

Install:

bash
chmod +x .hooks/guard.ts
bunx @pivanov/agent-hooks-bridge install ./.hooks/guard.ts --events PreToolUse

Per-host behavior

Hostdecision: "deny" surface
ClaudehookSpecificOutput.permissionDecision = "deny", exit 2. reason shown to model.
Cursorpermission: "deny", exit 2. reasonagent_message (model). user_message → human UI.
CodexhookSpecificOutput.permissionDecision = "deny", exit 2. reason shown to model. user_message dropped + warn.

The user_message field is honored only on Cursor's permission events. Claude and Codex have no human-facing channel. If you set both reason and user_message, set them to similar content; the bridge drops user_message on the hosts that can't show it.

Pairing with format-on-edit

You can run both hooks in one script. Either:

  1. Single script, multiple events: one defineHook map handling PreToolUse (guard) and PostToolUse (format).
  2. Two scripts: wire each separately:
bash
bunx @pivanov/agent-hooks-bridge install ./.hooks/guard.ts  --events PreToolUse
bunx @pivanov/agent-hooks-bridge install ./.hooks/format.ts --events PostToolUse

Both work. The single-script form is simpler; the two-script form lets you toggle policies independently.

Not affiliated with Anthropic, Anysphere, or OpenAI. Supported by LogicStar AI.