From aae36d432afbb5d87c1af232a5b18b294ac6d371 Mon Sep 17 00:00:00 2001 From: yurukusa Date: Tue, 24 Mar 2026 11:21:35 +0900 Subject: [PATCH] feat: add safety hook examples Two ready-to-use Python hooks using cchooks SDK: - block_destructive.py: blocks rm -rf, git reset --hard, force push - block_secrets.py: blocks git add .env and credential files Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/README.md | 28 +++++++++++++++++++++ examples/block_destructive.py | 47 +++++++++++++++++++++++++++++++++++ examples/block_secrets.py | 39 +++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/block_destructive.py create mode 100644 examples/block_secrets.py diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..407cdf8 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,28 @@ +# Examples + +Ready-to-use hooks built with cchooks. + +## Safety Hooks + +| Hook | What It Blocks | +|------|---------------| +| [block_destructive.py](block_destructive.py) | rm -rf /, git reset --hard, force push, push to main | +| [block_secrets.py](block_secrets.py) | git add .env, credential files | + +## Usage + +1. Copy the hook to your hooks directory +2. Add to `~/.claude/settings.json`: + +```json +{ + "hooks": { + "PreToolUse": [{ + "matcher": "Bash", + "hooks": [{"type": "command", "command": "python3 /path/to/block_destructive.py"}] + }] + } +} +``` + +3. Restart Claude Code diff --git a/examples/block_destructive.py b/examples/block_destructive.py new file mode 100644 index 0000000..0b7123a --- /dev/null +++ b/examples/block_destructive.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Block destructive commands (rm -rf, git reset --hard, git clean). + +Usage in settings.json: +{ + "hooks": { + "PreToolUse": [{ + "matcher": "Bash", + "hooks": [{"type": "command", "command": "python3 /path/to/block_destructive.py"}] + }] + } +} +""" + +import re +from cchooks import create_context, PreToolUseContext + +ctx = create_context() + +if not isinstance(ctx, PreToolUseContext): + ctx.allow() + +command = ctx.tool_input.get("command", "") +if not command: + ctx.allow() + +# Block rm -rf on sensitive paths +if re.search(r'rm\s+(-[rf]+\s+)*(\/|~|\.\.\/)', command): + ctx.block("rm on sensitive path detected. Target specific directories instead.") + +# Block git reset --hard +if re.search(r'(^|;|&&)\s*git\s+reset\s+--hard', command): + ctx.block("git reset --hard discards all uncommitted changes. Use git stash first.") + +# Block git clean -fd +if re.search(r'(^|;|&&)\s*git\s+clean\s+-[a-z]*[fd]', command): + ctx.block("git clean removes untracked files permanently. Use git clean -n to preview.") + +# Block force push +if re.search(r'git\s+push\s+.*(-f\b|--force\b)', command): + ctx.block("Force push destroys remote history. Use --force-with-lease for safer option.") + +# Block push to main/master +if re.search(r'git\s+push\s+.*\b(main|master)\b', command): + ctx.block("Push to protected branch. Push to a feature branch and create a PR.") + +ctx.allow() diff --git a/examples/block_secrets.py b/examples/block_secrets.py new file mode 100644 index 0000000..0c164f1 --- /dev/null +++ b/examples/block_secrets.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Block staging secret files (git add .env, credentials). + +Usage in settings.json: +{ + "hooks": { + "PreToolUse": [{ + "matcher": "Bash", + "hooks": [{"type": "command", "command": "python3 /path/to/block_secrets.py"}] + }] + } +} +""" + +import re +from cchooks import create_context, PreToolUseContext + +ctx = create_context() + +if not isinstance(ctx, PreToolUseContext): + ctx.allow() + +command = ctx.tool_input.get("command", "") +if not command: + ctx.allow() + +# Only check git add commands +if not re.search(r'^\s*git\s+add', command): + ctx.allow() + +# Block .env files +if re.search(r'git\s+add\s+.*\.env(\s|$|\.)', command, re.IGNORECASE): + ctx.block(".env file contains secrets. Add to .gitignore instead.") + +# Block credential/key files +if re.search(r'git\s+add\s+.*(credentials|\.pem|\.key|\.p12|id_rsa|id_ed25519)', command, re.IGNORECASE): + ctx.block("Credential/key file should never be committed. Add to .gitignore.") + +ctx.allow()