Commit 0249e6ac4665

Vincent Demeester <vincent@sbr.pm>
2026-02-02 12:34:39
feat(claude-hooks): add git add validation to prevent dangerous staging
- Block git add -A, git add --all, and git add . commands - Prevent accidentally adding unintended files to git staging - Update README to document the enhanced git validation - Existing git push validation remains unchanged
1 parent 0358860
Changed files (2)
tools
claude-hooks
cmd
validate-git-push
tools/claude-hooks/cmd/validate-git-push/main.go
@@ -27,6 +27,26 @@ func hasExplicitRefspec(command string) bool {
 	return refspecPattern.MatchString(command)
 }
 
+// Check if this is a dangerous git add command
+func isDangerousGitAdd(command string) bool {
+	// Match patterns like:
+	// git add -A
+	// git add --all
+	// git add .
+	dangerousAddPatterns := []*regexp.Regexp{
+		regexp.MustCompile(`(^|&&|\|\||;|\||\$\()\s*git\s+add\s+-A(\s|$)`),
+		regexp.MustCompile(`(^|&&|\|\||;|\||\$\()\s*git\s+add\s+--all(\s|$)`),
+		regexp.MustCompile(`(^|&&|\|\||;|\||\$\()\s*git\s+add\s+\.(\s|$)`),
+	}
+
+	for _, pattern := range dangerousAddPatterns {
+		if pattern.MatchString(command) {
+			return true
+		}
+	}
+	return false
+}
+
 // Check if this is a git push command (not just the string "git push" inside arguments)
 func isGitPush(command string) bool {
 	// Match git push only when it appears as an actual command:
@@ -79,6 +99,21 @@ func main() {
 		os.Exit(0)
 	}
 
+	// Check for dangerous git add commands first
+	if isDangerousGitAdd(command) {
+		fmt.Fprintln(os.Stderr, "BLOCKED: Dangerous git add command detected!")
+		fmt.Fprintln(os.Stderr, "")
+		fmt.Fprintln(os.Stderr, "Commands like 'git add -A', 'git add --all', and 'git add .' can add unintended files.")
+		fmt.Fprintln(os.Stderr, "")
+		fmt.Fprintln(os.Stderr, "Use explicit file paths instead:")
+		fmt.Fprintln(os.Stderr, "  git add path/to/specific/file.txt")
+		fmt.Fprintln(os.Stderr, "  git add path/to/directory/")
+		fmt.Fprintln(os.Stderr, "  git add *.go  # for specific patterns")
+		fmt.Fprintln(os.Stderr, "")
+		fmt.Fprintf(os.Stderr, "Blocked command: %s\n", command)
+		os.Exit(2) // Non-zero exit blocks the tool
+	}
+
 	// Check if this is a git push command
 	if !isGitPush(command) {
 		os.Exit(0)
tools/claude-hooks/README.md
@@ -44,6 +44,16 @@ Migrated from TypeScript/Bun implementation in `dots/.claude/hooks/`.
 - Works with `/save-session` slash command for creating session entries
 - Saves to `~/.config/claude/history/sessions/YYYY-MM/YYYY-MM-DD-HHMMSS_SESSION_description.md`
 
+### 5. `claude-hooks-validate-git-push`
+**Event**: PreToolUse (via extensions)
+
+**What it does**:
+- Validates git commands to prevent dangerous operations
+- Blocks `git add -A`, `git add --all`, and `git add .` to prevent adding unintended files
+- Blocks git push commands without explicit refspecs (e.g., `git push origin main`)
+- Warns about pushes to protected branches (main/master)
+- Exit code 0 if command is safe, non-zero if command should be blocked
+
 ## Architecture
 
 ```