diff --git a/actions/leak-scan/run.sh b/actions/leak-scan/run.sh index 24bd105..6b98d8d 100755 --- a/actions/leak-scan/run.sh +++ b/actions/leak-scan/run.sh @@ -207,18 +207,40 @@ run_pr_diff_scan() { printf '%s\n' "$changed_files" > changed-files.txt + # Build a temporary gitleaks config that extends the default rules and adds + # allowlist.paths entries for any EXCLUDE_PATHS prefixes. gitleaks 8.x does + # not support path filtering flags, so path exclusion must be expressed via the + # config allowlist. The temp config is cleaned up after the scan. + local gitleaks_config_flag=() + local gitleaks_tmp_config="" + local regex_prefix + if [[ -n "${EXCLUDE_PATHS:-}" ]]; then + gitleaks_tmp_config=$(mktemp --suffix=.toml) + { + printf '[extend]\nuseDefault = true\n\n[allowlist]\npaths = [\n' + for excl_prefix in $EXCLUDE_PATHS; do + # Escape the prefix as a regex: anchor with ^ and escape dots/special chars. + regex_prefix=$(printf '%s' "$excl_prefix" | sed 's/\./\\./g') + printf " '^%s',\n" "$regex_prefix" + done + printf ']\n' + } > "$gitleaks_tmp_config" + gitleaks_config_flag=(--config="$gitleaks_tmp_config") + fi + local gitleaks_exit=0 if command -v gitleaks >/dev/null 2>&1; then - gitleaks detect \ - --source=. \ - --files-at-commit="$HEAD_REF" \ - --include-paths=changed-files.txt \ + gitleaks git \ + --log-opts="${BASE_REF}..${HEAD_REF}" \ + "${gitleaks_config_flag[@]}" \ --redact \ --report-path=gitleaks-diff-report.json \ + . \ 2>&1 || gitleaks_exit=$? else printf '::warning::gitleaks not found; skipping gitleaks pr-diff scan\n' fi + [[ -n "$gitleaks_tmp_config" ]] && rm -f "$gitleaks_tmp_config" # Run path deny-list on changed files PATHS="$(printf '%s\n' "$changed_files" | tr '\n' ' ')" diff --git a/tests/test_deploy_platform_d.py b/tests/test_deploy_platform_d.py index 9337b74..1f888ad 100644 --- a/tests/test_deploy_platform_d.py +++ b/tests/test_deploy_platform_d.py @@ -230,6 +230,36 @@ def test_all_refs_mode_line_in_script(self) -> None: "run.sh must not pass --all as a standalone flag to gitleaks detect (gitleaks v8.x removed it)", ) + def test_pr_diff_mode_uses_gitleaks_git_with_log_opts(self) -> None: + """T-D2: pr-diff mode uses 'gitleaks git --log-opts' (not --files-at-commit or --include-paths). + + gitleaks 8.x removed --files-at-commit and --include-paths from the detect + subcommand. The correct replacement is 'gitleaks git --log-opts=BASE..HEAD' + which scans the git commit range that corresponds to the PR diff. + """ + script_text = LEAK_SCAN_RUN.read_text(encoding="utf-8") + self.assertIn( + "gitleaks git", + script_text, + "run.sh pr-diff block must use 'gitleaks git' (not 'gitleaks detect' with removed flags)", + ) + self.assertIn( + '--log-opts="${BASE_REF}..${HEAD_REF}"', + script_text, + "run.sh pr-diff block must pass --log-opts=BASE..HEAD to gitleaks git", + ) + # Regression guards: these flags were removed in gitleaks 8.x + self.assertNotIn( + "--files-at-commit", + script_text, + "run.sh must not use --files-at-commit (removed in gitleaks 8.x; causes 'unknown flag' exit 1)", + ) + self.assertNotIn( + "--include-paths", + script_text, + "run.sh must not use --include-paths (removed in gitleaks 8.x; causes 'unknown flag' exit 1)", + ) + def test_path_mode_detects_ipv4_literal(self) -> None: """T-D2: path mode detects IPv4 literal and emits redacted gate-summary.""" with tempfile.TemporaryDirectory() as tmpdir: @@ -789,6 +819,36 @@ def test_pr_diff_keeps_graceful_warning_when_gitleaks_absent(self) -> None: "run.sh pr-diff mode must keep the graceful gitleaks-missing warning (not fail-closed)", ) + # T-D7g: pr-diff mode must not contain removed gitleaks v8.x flags + def test_pr_diff_does_not_use_removed_gitleaks_flags(self) -> None: + """T-D7g: --files-at-commit and --include-paths were removed in gitleaks 8.x. + + These flags cause 'gitleaks detect' to print help and exit 1, breaking + every pipeline run. This test is a static drift guard that catches any + re-introduction of the removed flags. + """ + self.assertNotIn( + "--files-at-commit", + self.run_sh_text, + "run.sh must not contain --files-at-commit (removed in gitleaks 8.x; breaks CI)", + ) + self.assertNotIn( + "--include-paths", + self.run_sh_text, + "run.sh must not contain --include-paths (removed in gitleaks 8.x; breaks CI)", + ) + # Positive guard: pr-diff must use the correct v8 invocation + self.assertIn( + "gitleaks git", + self.run_sh_text, + "run.sh pr-diff block must use 'gitleaks git' subcommand (correct for gitleaks 8.x)", + ) + self.assertIn( + "--log-opts=", + self.run_sh_text, + "run.sh pr-diff block must use --log-opts to specify commit range (gitleaks 8.x API)", + ) + if __name__ == "__main__": unittest.main()