#!/bin/bash # Blocks writes to build artifacts, binary files, and dependency directories. # Used as a PreToolUse hook for Edit|Write operations. # Exit 2 = block the action. Exit 0 = allow. # Requires jq for JSON parsing — fail closed if missing if ! command -v jq >/dev/null 2>&1; then echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"jq is required for file protection hooks but is not installed.\"}}" exit 2 fi INPUT=$(cat) FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') if [ -z "$FILE_PATH" ]; then exit 0 fi # Block dependency and build directories case "$FILE_PATH" in node_modules/*|*/node_modules/*) REASON="Cannot write into node_modules/ — install dependencies via package manager instead." ;; vendor/*|*/vendor/*) REASON="Cannot write into vendor/ — use dependency manager instead." ;; dist/*|*/dist/*|build/*|*/build/*|.next/*|*/.next/*) REASON="Cannot write into build output directories — these are generated by the build process." ;; __pycache__/*|*/__pycache__/*) REASON="Cannot write into __pycache__/ — these are generated by Python." ;; .venv/*|*/.venv/*|venv/*|*/venv/*) REASON="Cannot write into virtual environment directories." ;; *) REASON="" ;; esac if [ -n "$REASON" ]; then echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"$REASON\"}}" exit 2 fi # Block binary and archive file extensions BASENAME=$(basename "$FILE_PATH") case "$BASENAME" in *.wasm|*.so|*.dylib|*.dll|*.exe|*.o|*.a) REASON="Cannot write binary files — these should be compiled, not hand-written." ;; *.zip|*.tar|*.tar.gz|*.tar.bz2|*.tgz|*.rar|*.7z) REASON="Cannot write archive files." ;; *.mp4|*.mov|*.avi|*.mkv|*.mp3|*.wav|*.flac) REASON="Cannot write media files — add these manually outside Claude Code." ;; *.pyc|*.pyo|*.class) REASON="Cannot write compiled bytecode files." ;; esac if [ -n "$REASON" ]; then echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"$REASON\"}}" exit 2 fi exit 0