From 305a0ece5ed5090df964993d2da82bcf24a42627 Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 24 Jan 2026 09:58:50 -0500 Subject: [PATCH] Add Trivy security scanning to git hooks - Add mandatory Trivy image scanning to both pre-commit and pre-push hooks - Remove interactive prompts from install script, add --force flag instead - Add automatic cleanup of temporary Docker images after scanning - Check for Docker and Trivy dependencies before running hooks --- scripts/install-git-hooks.sh | 197 ++++++++++++++++++++++++----------- scripts/scan-trivy-image.sh | 60 +++++++++-- 2 files changed, 187 insertions(+), 70 deletions(-) diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh index ac85ba7..764f2d9 100755 --- a/scripts/install-git-hooks.sh +++ b/scripts/install-git-hooks.sh @@ -1,10 +1,33 @@ #!/bin/bash # Installation script for Git hooks in Hastebin -# This script sets up pre-commit hooks (tests) and pre-push hooks (unused code scans) +# This script sets up pre-commit hooks (tests + security scan) and pre-push hooks (unused code + security scan) +# +# Usage: +# ./install-git-hooks.sh [--force] +# +# Options: +# --force Overwrite existing hooks without prompting (backs up existing hooks) +# Without this flag, fails if hooks already exist set -e +# Parse arguments +FORCE_MODE=false +while [[ $# -gt 0 ]]; do + case $1 in + --force) + FORCE_MODE=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [--force]" + exit 1 + ;; + esac +done + # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' @@ -16,40 +39,49 @@ NC='\033[0m' # No Color REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)") HOOKS_DIR="$REPO_ROOT/.git/hooks" PRE_COMMIT_HOOK="$HOOKS_DIR/pre-commit" +PRE_PUSH_HOOK="$HOOKS_DIR/pre-push" SCRIPT_DIR="$REPO_ROOT/scripts" -echo -e "${BLUE}🔧 Installing Git hooks for Hastebin${NC}" +echo -e "${BLUE}Installing Git hooks for Hastebin${NC}" echo "" # Check if we're in a git repository if [ ! -d "$REPO_ROOT/.git" ]; then - echo -e "${RED}❌ Error: Not a git repository${NC}" + echo -e "${RED}Error: Not a git repository${NC}" exit 1 fi # Create hooks directory if it doesn't exist mkdir -p "$HOOKS_DIR" -# Check if pre-commit hook already exists -if [ -f "$PRE_COMMIT_HOOK" ]; then - echo -e "${YELLOW}⚠️ Pre-commit hook already exists${NC}" - read -p "Do you want to overwrite it? (y/N): " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}Skipping hook installation${NC}" - exit 0 +# Function to handle existing hook +handle_existing_hook() { + local hook_path="$1" + local hook_name="$2" + + if [ -f "$hook_path" ]; then + if [ "$FORCE_MODE" = true ]; then + # Backup existing hook with timestamp + mv "$hook_path" "$hook_path.backup.$(date +%Y%m%d_%H%M%S)" + echo -e "${YELLOW}Backed up existing $hook_name hook${NC}" + else + echo -e "${RED}Error: $hook_name hook already exists at $hook_path${NC}" + echo -e "${YELLOW}Use --force to overwrite (will backup existing hook)${NC}" + exit 1 + fi fi - # Backup existing hook - mv "$PRE_COMMIT_HOOK" "$PRE_COMMIT_HOOK.backup.$(date +%Y%m%d_%H%M%S)" - echo -e "${YELLOW}Backed up existing hook${NC}" -fi +} -# Create the pre-commit hook +# Check for existing hooks +handle_existing_hook "$PRE_COMMIT_HOOK" "pre-commit" +handle_existing_hook "$PRE_PUSH_HOOK" "pre-push" + +# --- Create the pre-commit hook --- cat > "$PRE_COMMIT_HOOK" << 'HOOK_EOF' #!/bin/bash # Git pre-commit hook for Hastebin -# Runs tests before allowing commits to prevent pushing broken code +# Runs tests and security scans before allowing commits set -e @@ -60,11 +92,31 @@ YELLOW='\033[0;33m' NC='\033[0m' # No Color echo -e "${YELLOW}Running pre-commit checks...${NC}" +echo "" # Get the repository root REPO_ROOT=$(git rev-parse --show-toplevel) cd "$REPO_ROOT" +# Check required dependencies +MISSING_DEPS=0 + +if ! command -v docker &> /dev/null; then + echo -e "${RED}Docker not found - required for security scanning${NC}" + MISSING_DEPS=1 +fi + +if ! command -v trivy &> /dev/null; then + echo -e "${RED}Trivy not found - required for security scanning${NC}" + echo " Install: brew install trivy" + MISSING_DEPS=1 +fi + +if [ $MISSING_DEPS -ne 0 ]; then + echo -e "${RED}Missing required dependencies. Commit aborted.${NC}" + exit 1 +fi + # Check if node_modules exists, if not, install dependencies if [ ! -d "node_modules" ]; then echo -e "${YELLOW}Installing dependencies...${NC}" @@ -72,11 +124,11 @@ if [ ! -d "node_modules" ]; then fi # Run the core tests (faster than full test suite) -echo -e "${YELLOW}Running core tests...${NC}" +echo -e "${YELLOW}[1/2] Running core tests...${NC}" if npm run test:core; then - echo -e "${GREEN}✅ Core tests passed${NC}" + echo -e "${GREEN}Core tests passed${NC}" else - echo -e "${RED}❌ Core tests failed. Commit aborted.${NC}" + echo -e "${RED}Core tests failed. Commit aborted.${NC}" echo -e "${YELLOW}To skip this check, use: git commit --no-verify${NC}" exit 1 fi @@ -86,37 +138,31 @@ if [ -d "test-data" ]; then rm -rf test-data fi -echo -e "${GREEN}✅ Pre-commit checks passed${NC}" +# Run Trivy security scan +echo "" +echo -e "${YELLOW}[2/2] Running security scan (Trivy image scan)...${NC}" +if "$REPO_ROOT/scripts/scan-trivy-image.sh"; then + echo -e "${GREEN}Security scan passed${NC}" +else + echo -e "${RED}Security scan found vulnerabilities. Commit aborted.${NC}" + echo -e "${YELLOW}To skip this check, use: git commit --no-verify${NC}" + exit 1 +fi + +echo "" +echo -e "${GREEN}Pre-commit checks passed${NC}" exit 0 HOOK_EOF -# Make the hook executable chmod +x "$PRE_COMMIT_HOOK" +echo -e "${GREEN}Pre-commit hook installed${NC}" -echo -e "${GREEN}✅ Pre-commit hook installed successfully${NC}" - -# --- Pre-push hook --- -PRE_PUSH_HOOK="$HOOKS_DIR/pre-push" - -# Check if pre-push hook already exists -if [ -f "$PRE_PUSH_HOOK" ]; then - echo -e "${YELLOW}⚠️ Pre-push hook already exists${NC}" - read -p "Do you want to overwrite it? (y/N): " -n 1 -r - echo - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}Skipping pre-push hook installation${NC}" - else - mv "$PRE_PUSH_HOOK" "$PRE_PUSH_HOOK.backup.$(date +%Y%m%d_%H%M%S)" - echo -e "${YELLOW}Backed up existing pre-push hook${NC}" - fi -fi - -if [ ! -f "$PRE_PUSH_HOOK" ] || [[ $REPLY =~ ^[Yy]$ ]]; then - cat > "$PRE_PUSH_HOOK" << 'HOOK_EOF' +# --- Create the pre-push hook --- +cat > "$PRE_PUSH_HOOK" << 'HOOK_EOF' #!/bin/bash # Git pre-push hook for Hastebin -# Scans for unused code/dependencies before pushing +# Scans for unused code/dependencies and runs security scan before pushing set -e @@ -127,11 +173,31 @@ YELLOW='\033[0;33m' NC='\033[0m' # No Color echo -e "${YELLOW}Running pre-push checks...${NC}" +echo "" # Get the repository root REPO_ROOT=$(git rev-parse --show-toplevel) cd "$REPO_ROOT" +# Check required dependencies +MISSING_DEPS=0 + +if ! command -v docker &> /dev/null; then + echo -e "${RED}Docker not found - required for security scanning${NC}" + MISSING_DEPS=1 +fi + +if ! command -v trivy &> /dev/null; then + echo -e "${RED}Trivy not found - required for security scanning${NC}" + echo " Install: brew install trivy" + MISSING_DEPS=1 +fi + +if [ $MISSING_DEPS -ne 0 ]; then + echo -e "${RED}Missing required dependencies. Push aborted.${NC}" + exit 1 +fi + # Check if node_modules exists, if not, install dependencies if [ ! -d "node_modules" ]; then echo -e "${YELLOW}Installing dependencies...${NC}" @@ -141,48 +207,61 @@ fi # Scan for unused code/dependencies SCAN_FAILED=0 -echo -e "${YELLOW}Scanning for unused files/exports/dependencies (knip)...${NC}" +echo -e "${YELLOW}[1/3] Scanning for unused files/exports/dependencies (knip)...${NC}" if npx --yes knip 2>/dev/null; then - echo -e "${GREEN}✅ knip passed${NC}" + echo -e "${GREEN}knip passed${NC}" else - echo -e "${RED}❌ knip found unused code${NC}" + echo -e "${RED}knip found unused code${NC}" SCAN_FAILED=1 fi -echo -e "${YELLOW}Scanning for unused npm dependencies (depcheck)...${NC}" +echo "" +echo -e "${YELLOW}[2/3] Scanning for unused npm dependencies (depcheck)...${NC}" if npx --yes depcheck 2>/dev/null; then - echo -e "${GREEN}✅ depcheck passed${NC}" + echo -e "${GREEN}depcheck passed${NC}" else - echo -e "${RED}❌ depcheck found issues${NC}" + echo -e "${RED}depcheck found issues${NC}" SCAN_FAILED=1 fi if [ $SCAN_FAILED -ne 0 ]; then - echo -e "${RED}❌ Unused code/dependencies detected. Push aborted.${NC}" + echo -e "${RED}Unused code/dependencies detected. Push aborted.${NC}" echo -e "${YELLOW}To skip this check, use: git push --no-verify${NC}" exit 1 fi -echo -e "${GREEN}✅ Pre-push checks passed${NC}" +# Run Trivy security scan +echo "" +echo -e "${YELLOW}[3/3] Running security scan (Trivy image scan)...${NC}" +if "$REPO_ROOT/scripts/scan-trivy-image.sh"; then + echo -e "${GREEN}Security scan passed${NC}" +else + echo -e "${RED}Security scan found vulnerabilities. Push aborted.${NC}" + echo -e "${YELLOW}To skip this check, use: git push --no-verify${NC}" + exit 1 +fi + +echo "" +echo -e "${GREEN}Pre-push checks passed${NC}" exit 0 HOOK_EOF - chmod +x "$PRE_PUSH_HOOK" - echo -e "${GREEN}✅ Pre-push hook installed successfully${NC}" -fi +chmod +x "$PRE_PUSH_HOOK" +echo -e "${GREEN}Pre-push hook installed${NC}" + echo "" echo -e "${BLUE}Hooks installed:${NC}" -echo -e "${BLUE} - pre-commit: runs core tests before each commit${NC}" -echo -e "${BLUE} - pre-push: scans for unused code/deps before each push${NC}" +echo -e "${BLUE} - pre-commit: core tests + Trivy security scan${NC}" +echo -e "${BLUE} - pre-push: knip + depcheck + Trivy security scan${NC}" echo -e "${YELLOW}To skip hooks, use: git commit --no-verify / git push --no-verify${NC}" echo "" # Check if dependencies are installed if [ ! -d "$REPO_ROOT/node_modules" ]; then - echo -e "${YELLOW}⚠️ Dependencies not found. Installing...${NC}" + echo -e "${YELLOW}Dependencies not found. Installing...${NC}" cd "$REPO_ROOT" npm ci - echo -e "${GREEN}✅ Dependencies installed${NC}" + echo -e "${GREEN}Dependencies installed${NC}" fi -echo -e "${GREEN}🎉 Git hooks installation complete!${NC}" +echo -e "${GREEN}Git hooks installation complete!${NC}" diff --git a/scripts/scan-trivy-image.sh b/scripts/scan-trivy-image.sh index e93500c..4c106b8 100755 --- a/scripts/scan-trivy-image.sh +++ b/scripts/scan-trivy-image.sh @@ -1,33 +1,65 @@ #!/bin/bash # Run Trivy image security scan -# Builds the Docker image and scans it for vulnerabilities +# Builds the Docker image locally and scans it for vulnerabilities +# +# Usage: +# ./scan-trivy-image.sh [image-name] +# +# Exit codes: +# 0 - No HIGH/CRITICAL vulnerabilities found +# 1 - Vulnerabilities found or error occurred set -e -IMAGE_NAME="${1:-hastebin:test}" +IMAGE_NAME="${1:-hastebin:local-scan}" -echo "🐳 Building Docker image: $IMAGE_NAME" -docker build -t "$IMAGE_NAME" --no-cache . +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +NC='\033[0m' # No Color -echo "" -echo "🔒 Running Trivy image security scan..." +# Cleanup function to remove temporary image +cleanup() { + if docker image inspect "$IMAGE_NAME" &> /dev/null; then + echo -e "${YELLOW}Cleaning up temporary image: $IMAGE_NAME${NC}" + docker rmi "$IMAGE_NAME" --force &> /dev/null || true + fi +} + +# Set trap to cleanup on exit (success or failure) +trap cleanup EXIT + +# Check dependencies BEFORE building +if ! command -v docker &> /dev/null; then + echo -e "${RED}Docker not found. Please install Docker.${NC}" + exit 1 +fi -# Check if trivy is installed if ! command -v trivy &> /dev/null; then - echo "Trivy not found. Please install it:" + echo -e "${RED}Trivy not found. Please install it:${NC}" echo " brew install trivy" echo " or visit: https://aquasecurity.github.io/trivy/latest/getting-started/installation/" exit 1 fi +# Build the Docker image (always clean build, no cache) +echo -e "${YELLOW}Building Docker image: $IMAGE_NAME${NC}" +docker build -t "$IMAGE_NAME" --no-cache . + +echo "" +echo -e "${YELLOW}Running Trivy image security scan...${NC}" + # Show version trivy --version echo "" -echo "📦 Scanning Docker image for vulnerabilities..." +echo -e "${YELLOW}Scanning Docker image for vulnerabilities...${NC}" echo "" # Scan image with exit code 1 (fail on HIGH/CRITICAL vulnerabilities) +# Store result to allow cleanup even on failure +SCAN_RESULT=0 trivy image \ --timeout 10m \ --scanners vuln \ @@ -35,7 +67,13 @@ trivy image \ --ignore-unfixed \ --exit-code 1 \ --format table \ - "$IMAGE_NAME" + "$IMAGE_NAME" || SCAN_RESULT=$? echo "" -echo "✅ Trivy image scan completed!" + +if [ $SCAN_RESULT -eq 0 ]; then + echo -e "${GREEN}Trivy image scan completed - no HIGH/CRITICAL vulnerabilities found!${NC}" +else + echo -e "${RED}Trivy image scan found HIGH/CRITICAL vulnerabilities!${NC}" + exit $SCAN_RESULT +fi