#!/usr/bin/env bash set -euo pipefail # Colors readonly RED='\033[0;31m' readonly GREEN='\033[0;32m' readonly YELLOW='\033[1;33m' readonly NC='\033[0m' readonly REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" readonly LOG_DIR="${REPO_ROOT}/.checks/logs" log() { echo -e "${GREEN}[INFO] $*${NC}"; } warn() { echo -e "${YELLOW}[WARN] $*${NC}"; } err() { echo -e "${RED}[ERROR] $*${NC}" >&2; } prepare_logs() { rm -rf "$LOG_DIR" mkdir -p "$LOG_DIR" } # Files that git considers "scannable" (tracked + untracked not ignored) scannable_files() { git ls-files --cached --modified --others --exclude-standard } # Files listed as ignored by git (used to build tool-specific skip args) ignored_files() { git ls-files -o -i --exclude-standard } # Build Trivy skip args from git-ignored paths trivy_skip_args() { local path dir while IFS= read -r path; do [[ -z "$path" ]] && continue dir="$(dirname "$path")" printf -- '--skip-files=%q ' "$path" printf -- '--skip-dirs=%q ' "$dir" done < <(ignored_files) } run_tool() { local name="$1"; shift if ! command -v "$name" &>/dev/null; then warn "$name not installed; skipping." return 0 fi "$@" } run_trivy() { run_tool trivy bash -c ' files=("$@") if [[ ${#files[@]} -eq 0 ]]; then echo "no files"; exit 0 fi # Expand skip args passed via environment variable SKIP_ARGS eval trivy fs "${files[@]}" $SKIP_ARGS --exit-code 1 --severity CRITICAL,HIGH,MEDIUM,LOW,UNKNOWN --no-progress ' _ $(scannable_files) } run_trufflehog() { run_tool trufflehog bash -c ' files=("$@") tmpf="$(mktemp)" printf "%s\n" "${files[@]}" | xargs trufflehog filesystem --json >"$tmpf" 2>"'"$LOG_DIR"'/trufflehog.log" || true if command -v jq &>/dev/null; then if jq -e '"'"'any(inputs; .verified==true)'"'"' "$tmpf" >/dev/null 2>&1; then cp "$tmpf" "'"$LOG_DIR"'/trufflehog-findings.json" echo "verified"; exit 2 fi fi rm -f "$tmpf" ' _ $(git ls-files) local rc=$?; [[ $rc -eq 2 ]] && { err "TruffleHog found verified secrets. See $LOG_DIR/trufflehog-findings.json"; return 1; } return 0 } run_git_secrets() { run_tool git-secrets git-secrets --scan >"$LOG_DIR/git-secrets.log" 2>&1 || { err "git-secrets detected potential secrets. See $LOG_DIR/git-secrets.log"; return 1; } } run_bandit() { run_tool bandit bash -c ' py=("$@") [[ ${#py[@]} -eq 0 ]] && exit 0 bandit -r "${py[@]}" -n 5 -lll -f json -o "'"$LOG_DIR"'/bandit.json" 2>"'"$LOG_DIR"'/bandit.log" ' _ $(git ls-files '*.py') } run_npm_audit() { if [[ -f package.json ]]; then run_tool npm npm audit --audit-level=high >"$LOG_DIR/npm-audit.log" 2>&1 || { err "npm audit found issues. See $LOG_DIR/npm-audit.log"; return 1; } fi } run_pip_audit() { if [[ -f requirements.txt ]] || [[ -f pyproject.toml ]]; then run_tool pip-audit pip-audit -r requirements.txt >"$LOG_DIR/pip-audit.log" 2>&1 || { err "pip-audit found issues. See $LOG_DIR/pip-audit.log"; return 1; } fi } run_stylelint() { run_tool stylelint bash -c ' files=("$@") [[ ${#files[@]} -eq 0 ]] && exit 0 stylelint "${files[@]}" --formatter json > "'"$LOG_DIR"'/stylelint.json" 2>"'"$LOG_DIR"'/stylelint.log" ' _ $(git ls-files '*.{css,scss,sass,less}') } main() { prepare_logs # Precompute skip args for trivy (safe quoting) export SKIP_ARGS SKIP_ARGS="$(trivy_skip_args || true)" local failed=0 run_trivy || failed=1 run_trufflehog || failed=1 run_git_secrets || failed=1 run_bandit || failed=1 run_npm_audit || failed=1 run_pip_audit || failed=1 run_stylelint || failed=1 if [[ $failed -ne 0 ]]; then err "Pre-push checks failed. Check logs in $LOG_DIR" exit 1 fi log "All pre-push checks passed." } main "$@"