diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ed5c135e..78127bdc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -186,6 +186,22 @@ repos: verbose: true description: "Detects GORM ID leaks and common GORM security mistakes" + - id: semgrep-scan + name: Semgrep Security Scan (Manual) + entry: scripts/pre-commit-hooks/semgrep-scan.sh + language: script + pass_filenames: false + verbose: true + stages: [manual] # Manual stage initially (reversible rollout) + + - id: gitleaks-tuned-scan + name: Gitleaks Security Scan (Tuned, Manual) + entry: scripts/pre-commit-hooks/gitleaks-tuned-scan.sh + language: script + pass_filenames: false + verbose: true + stages: [manual] # Manual stage initially (reversible rollout) + - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.47.0 hooks: diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b11c1779..6362c94c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -430,6 +430,34 @@ "group": "test", "problemMatcher": [] }, + { + "label": "Security: Semgrep Scan (Manual Script)", + "type": "shell", + "command": "bash scripts/pre-commit-hooks/semgrep-scan.sh", + "group": "test", + "problemMatcher": [] + }, + { + "label": "Security: Semgrep Scan (Manual Hook)", + "type": "shell", + "command": "pre-commit run --hook-stage manual semgrep-scan --all-files", + "group": "test", + "problemMatcher": [] + }, + { + "label": "Security: Gitleaks Scan (Tuned Manual Script)", + "type": "shell", + "command": "bash scripts/pre-commit-hooks/gitleaks-tuned-scan.sh", + "group": "test", + "problemMatcher": [] + }, + { + "label": "Security: Gitleaks Scan (Tuned Manual Hook)", + "type": "shell", + "command": "pre-commit run --hook-stage manual gitleaks-tuned-scan --all-files", + "group": "test", + "problemMatcher": [] + }, { "label": "Security: Scan Docker Image (Local)", "type": "shell", diff --git a/scripts/pre-commit-hooks/gitleaks-tuned-scan.sh b/scripts/pre-commit-hooks/gitleaks-tuned-scan.sh new file mode 100755 index 00000000..76e1c6f0 --- /dev/null +++ b/scripts/pre-commit-hooks/gitleaks-tuned-scan.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +readonly REPO_ROOT +readonly DEFAULT_REPORT_PATH="${REPO_ROOT}/test-results/security/gitleaks-tuned-precommit.json" +readonly REPORT_PATH="${GITLEAKS_REPORT_PATH:-${DEFAULT_REPORT_PATH}}" + +if ! command -v rsync >/dev/null 2>&1; then + echo "Error: rsync is not installed or not in PATH" >&2 + exit 127 +fi + +if ! command -v gitleaks >/dev/null 2>&1; then + echo "Error: gitleaks is not installed or not in PATH" >&2 + echo "Install: https://github.com/gitleaks/gitleaks" >&2 + exit 127 +fi + +TEMP_ROOT="$(mktemp -d -t gitleaks-tuned-XXXXXX)" +cleanup() { + rm -rf "${TEMP_ROOT}" +} +trap cleanup EXIT + +readonly FILTERED_SOURCE="${TEMP_ROOT}/source-filtered" +mkdir -p "${FILTERED_SOURCE}" +mkdir -p "$(dirname "${REPORT_PATH}")" + +cd "${REPO_ROOT}" + +echo "Preparing filtered source tree for tuned gitleaks scan" +rsync -a --delete \ + --exclude='.cache/' \ + --exclude='node_modules/' \ + --exclude='frontend/node_modules/' \ + --exclude='backend/.venv/' \ + --exclude='dist/' \ + --exclude='build/' \ + --exclude='coverage/' \ + --exclude='test-results/' \ + ./ "${FILTERED_SOURCE}/" + +echo "Running gitleaks tuned scan (no-git mode)" +gitleaks detect \ + --source "${FILTERED_SOURCE}" \ + --no-git \ + --report-format json \ + --report-path "${REPORT_PATH}" \ + --exit-code 1 \ + --no-banner + +echo "Gitleaks report: ${REPORT_PATH}" diff --git a/scripts/pre-commit-hooks/semgrep-scan.sh b/scripts/pre-commit-hooks/semgrep-scan.sh new file mode 100755 index 00000000..cd2aff39 --- /dev/null +++ b/scripts/pre-commit-hooks/semgrep-scan.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +readonly SCRIPT_DIR +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" +readonly REPO_ROOT + +if ! command -v semgrep >/dev/null 2>&1; then + echo "Error: semgrep is not installed or not in PATH" >&2 + echo "Install: https://semgrep.dev/docs/getting-started/" >&2 + exit 127 +fi + +cd "${REPO_ROOT}" + +readonly SEMGREP_CONFIG_VALUE="${SEMGREP_CONFIG:-auto}" + +echo "Running Semgrep with config: ${SEMGREP_CONFIG_VALUE}" +semgrep scan \ + --config "${SEMGREP_CONFIG_VALUE}" \ + --error \ + backend frontend scripts .github/workflows