From a44a02b06d6d4f74703196d785b1923f764017bc Mon Sep 17 00:00:00 2001 From: yurukusa Date: Tue, 24 Mar 2026 21:17:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20add=203=20example=20hooks=20using=20the?= =?UTF-8?q?=20cchooks=20SDK=20-=20destructive=5Fguard.py=20=E2=80=94=20blo?= =?UTF-8?q?cks=20rm=20-rf,=20git=20reset=20--hard=20(#36339)=20-=20secret?= =?UTF-8?q?=5Fguard.py=20=E2=80=94=20blocks=20git=20add=20.env,=20credenti?= =?UTF-8?q?al=20files=20(#16561)=20-=20branch=5Fguard.py=20=E2=80=94=20blo?= =?UTF-8?q?cks=20push=20to=20main,=20force-push=20Each=20example=20demonst?= =?UTF-8?q?rates=20the=20create=5Fcontext()=20+=20output.block()=20pattern?= =?UTF-8?q?.=20All=20born=20from=20real=20GitHub=20Issues=20with=20links.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/branch_guard.py | 20 ++++++++++++++++++++ examples/destructive_guard.py | 31 +++++++++++++++++++++++++++++++ examples/secret_guard.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 examples/branch_guard.py create mode 100644 examples/destructive_guard.py create mode 100644 examples/secret_guard.py diff --git a/examples/branch_guard.py b/examples/branch_guard.py new file mode 100644 index 0000000..969d083 --- /dev/null +++ b/examples/branch_guard.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +"""Branch protection using cchooks SDK. + +Blocks push to main/master and force-push on all branches. +Install: Add to PreToolUse hooks matching "Bash" in settings.json. +""" +import re +from cchooks import create_context +from cchooks.contexts import PreToolUseContext + +ctx = create_context() +if isinstance(ctx, PreToolUseContext): + cmd = ctx.tool_input.get("command", "") + + if re.search(r'\bgit\s+push\b.*\b(main|master)\b', cmd, re.I): + ctx.output.block(reason="Direct push to main/master branch") + elif re.search(r'\bgit\s+push\s+.*--force\b', cmd, re.I): + ctx.output.block(reason="Force push overwrites remote history") + elif re.search(r'\bgit\s+push\s+.*-f\b', cmd, re.I): + ctx.output.block(reason="Force push (-f shorthand)") diff --git a/examples/destructive_guard.py b/examples/destructive_guard.py new file mode 100644 index 0000000..1d80032 --- /dev/null +++ b/examples/destructive_guard.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Destructive command guard using cchooks SDK. + +Blocks rm -rf /, git reset --hard, and similar destructive commands. +Install: Add to PreToolUse hooks matching "Bash" in settings.json. + +Born from: https://github.com/anthropics/claude-code/issues/36339 + (User lost entire C:\Users directory to rm -rf via NTFS junctions) +""" +import re +from cchooks import create_context +from cchooks.contexts import PreToolUseContext + +DANGEROUS = [ + re.compile(r'\brm\s+.*-rf\s+(/|~/?\s*$|\.\./)', re.I), + re.compile(r'\bgit\s+reset\s+--hard', re.I), + re.compile(r'\bgit\s+clean\s+-[a-zA-Z]*f', re.I), + re.compile(r'\bchmod\s+(-R\s+)?777\s+/', re.I), + re.compile(r'\bfind\s+/\s+-delete', re.I), + re.compile(r'Remove-Item.*-Recurse.*-Force', re.I), + re.compile(r'--no-preserve-root', re.I), +] + +ctx = create_context() +if isinstance(ctx, PreToolUseContext): + cmd = ctx.tool_input.get("command", "") + if cmd and not cmd.lstrip().startswith(("echo ", "printf ")): + for pattern in DANGEROUS: + if pattern.search(cmd): + ctx.output.block(reason=f"Destructive command: {cmd[:80]}") + break diff --git a/examples/secret_guard.py b/examples/secret_guard.py new file mode 100644 index 0000000..d387add --- /dev/null +++ b/examples/secret_guard.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""Secret leak prevention using cchooks SDK. + +Blocks git add .env, credential files, and git add . when .env exists. +Install: Add to PreToolUse hooks matching "Bash" in settings.json. + +Born from: https://github.com/anthropics/claude-code/issues/16561 + (API keys committed via git add .) +""" +import os +import re +from cchooks import create_context +from cchooks.contexts import PreToolUseContext + +ctx = create_context() +if isinstance(ctx, PreToolUseContext): + cmd = ctx.tool_input.get("command", "") + + # Block git add .env* + if re.search(r'\bgit\s+add\s+.*\.env\b', cmd, re.I): + ctx.output.block(reason="Adding .env file to git exposes secrets") + + # Block git add of credential files + elif re.search(r'\bgit\s+add\s+.*(\.pem|\.key|id_rsa|credentials)', cmd, re.I): + ctx.output.block(reason="Adding credential file to git") + + # Warn on git add . / git add -A (may include .env) + elif re.search(r'\bgit\s+add\s+(-A|--all|\.)\s*$', cmd): + if os.path.exists('.env') or os.path.exists('.env.local'): + ctx.output.block(reason="git add all with .env present — use specific files")