Skip to content
38 changes: 38 additions & 0 deletions .claude/hooks/git-push-protected-branch.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Prompt before a git push whose target branch is main or master.
# Emits no output for any other push, letting normal permission rules decide.
set -uo pipefail

[[ -t 0 ]] && exit 0
command=$(jq -r '.tool_input.command // ""' 2>/dev/null) || exit 0

# Positional arguments after `git push` are the remote and its refspecs.
read -ra parts <<<"$command"
args=()
for part in "${parts[@]:2}"; do
[[ "$part" == -* ]] || args+=("$part")
done

# With no refspec git pushes the current branch; otherwise the last refspec
# wins, and its destination is whatever follows the colon.
if [[ ${#args[@]} -le 1 ]]; then
target=HEAD
else
target="${args[-1]}"
[[ "$target" == *:* ]] && target="${target##*:}"
fi

target="${target#refs/heads/}"
if [[ -z "$target" || "$target" == HEAD ]]; then
target=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || echo "")
fi

if [[ "$target" =~ ^(main|master)$ ]]; then
jq -n --arg t "$target" '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "ask",
permissionDecisionReason: ("Push targets protected branch: " + $t)
}
}'
fi
40 changes: 37 additions & 3 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
},
"permissions": {
"deny": [
"Bash(rm -rf *)",
"Bash(rm -rf /)",
"Bash(rm -rf ~)",
"Bash(rm -rf ~/)",
"Bash(sudo rm *)",
Comment thread
bertini36 marked this conversation as resolved.
"Bash(dd *)",
"Bash(mkfs *)",
"Bash(shred *)",
Expand All @@ -18,11 +21,31 @@
"Bash(cat /etc/shadow)",
"Bash(cat /etc/ssh/**)",
"Bash(cat ~/.ssh/*)",
"Read(~/.ssh/**)"
"Read(~/.ssh/**)",
"Read(~/.aws/**)",
"Read(**/.env)"
],
"ask": [
"Bash(rm *)",
"Bash(git reset --hard*)",
"Bash(git clean -*)",
"Bash(gh pr merge *)"
],
"defaultMode": "auto"
},
"model": "sonnet",
"autoMode": {
"hard_deny": [
"$defaults",
"Never read, print, copy, or transmit SSH keys, cloud credentials, or .env contents, by any means including cat, head, grep, cp, base64, or a script"
],
"soft_deny": [
"$defaults",
"Never discard uncommitted work (git reset --hard, git clean, git checkout --)",
"Never rewrite published history, force-push, or delete a remote branch",
"Never merge, close, or delete a pull request, release, or repository"
]
},
"model": "opus",
Comment thread
bertini36 marked this conversation as resolved.
"hooks": {
"SessionStart": [
{
Expand All @@ -43,6 +66,17 @@
"command": "rtk hook claude"
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git push*)",
"command": "bash ~/.dotfiles/.claude/hooks/git-push-protected-branch.sh",
"statusMessage": "Checking push target"
}
]
}
]
},
Expand Down