From 955118e48390d8c4a443c8447c960b3b9da0d14d Mon Sep 17 00:00:00 2001 From: Mick Grove Date: Wed, 23 Jul 2025 18:14:43 -0700 Subject: [PATCH] Added precommit and prereceive hook installations. Fixing Gitlab support --- README.md | 22 +++++++++++++++++++++- install-precommit-hook.sh | 32 ++++++++++++++++++++++++++++++++ install-prereceive-hook.sh | 34 ++++++++++++++++++++++++++++++++++ src/git_binary.rs | 36 ++++++++++++++++++++++++++---------- 4 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 install-precommit-hook.sh create mode 100644 install-prereceive-hook.sh diff --git a/README.md b/README.md index 9a7895e..f7e7c0b 100644 --- a/README.md +++ b/README.md @@ -318,7 +318,27 @@ _If no token is provided Kingfisher still works for public repositories._ | 200 | Findings discovered | | 205 | Validated findings discovered | ---- + +## Install a Pre-Commit Hook + +Run the provided helper script to add a hook that scans staged files before each commit: + +```bash +./install-precommit-hook.sh +``` + +This creates `.git/hooks/pre-commit` that scans the files staged for commit with `kingfisher scan --no-update-check` and blocks the commit if any secrets are found. + +### Install a Pre-Receive Hook + +To check incoming pushes on a server-side repository, install the pre-receive hook: + +```bash +./install-prereceive-hook.sh +``` + +The resulting `.git/hooks/pre-receive` script scans the files in each pushed commit and rejects the push if any secrets are detected. + ## Update Checks diff --git a/install-precommit-hook.sh b/install-precommit-hook.sh new file mode 100644 index 0000000..6a6283f --- /dev/null +++ b/install-precommit-hook.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOOK_DIR="$(git rev-parse --git-dir)/hooks" +HOOK_PATH="$HOOK_DIR/pre-commit" + +if [ -e "$HOOK_PATH" ]; then + echo "Error: $HOOK_PATH already exists. Move or remove the existing hook to continue." >&2 + exit 1 +fi + +cat > "$HOOK_PATH" <<'HOOK' +#!/usr/bin/env bash +# Pre-commit hook to run Kingfisher scan on staged changes +set -euo pipefail + +if ! command -v kingfisher >/dev/null 2>&1; then + echo "kingfisher not found in PATH" >&2 + exit 1 +fi + +git diff --cached --name-only -z | \ + xargs -0 --no-run-if-empty kingfisher scan --no-update-check +status=$? +if [ "$status" -ne 0 ]; then + echo "Kingfisher detected secrets in staged files. Commit aborted." >&2 + exit "$status" +fi +HOOK + +chmod +x "$HOOK_PATH" +echo "Pre-commit hook installed to $HOOK_PATH" diff --git a/install-prereceive-hook.sh b/install-prereceive-hook.sh new file mode 100644 index 0000000..3ca4081 --- /dev/null +++ b/install-prereceive-hook.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +HOOK_DIR="$(git rev-parse --git-dir)/hooks" +HOOK_PATH="$HOOK_DIR/pre-receive" + +if [ -e "$HOOK_PATH" ]; then + echo "Error: $HOOK_PATH already exists. Move or remove the existing hook to continue." >&2 + exit 1 +fi + +cat > "$HOOK_PATH" <<'HOOK' +#!/usr/bin/env bash +# Pre-receive hook to scan pushed commits with Kingfisher +set -euo pipefail + +if ! command -v kingfisher >/dev/null 2>&1; then + echo "kingfisher not found in PATH" >&2 + exit 1 +fi + +while read -r oldrev newrev refname; do + git diff-tree --no-commit-id --name-only -r "$oldrev" "$newrev" -z | + xargs -0 --no-run-if-empty kingfisher scan --no-update-check + status=$? + if [ "$status" -ne 0 ]; then + echo "Kingfisher detected secrets in push. Push rejected." >&2 + exit "$status" + fi +done +HOOK + +chmod +x "$HOOK_PATH" +echo "Pre-receive hook installed to $HOOK_PATH" diff --git a/src/git_binary.rs b/src/git_binary.rs index a0e7c2e..b2b6918 100644 --- a/src/git_binary.rs +++ b/src/git_binary.rs @@ -36,17 +36,33 @@ impl Git { /// Create a new `Git` instance. /// /// * `ignore_certs`: If `true`, disables SSL certificate verification for `git` operations. - pub fn new(ignore_certs: bool) -> Self { - let credentials = if std::env::var("KF_GITHUB_TOKEN").is_ok() { - vec![ - "-c".into(), - r#"credential.helper="#.into(), - "-c".into(), +pub fn new(ignore_certs: bool) -> Self { + let mut credentials = Vec::new(); + + // If either GitHub or GitLab token is set, first clear existing credential.helpers + if std::env::var("KF_GITHUB_TOKEN").is_ok() + || std::env::var("KF_GITLAB_TOKEN").is_ok() + { + credentials.push("-c".into()); + credentials.push(r#"credential.helper="#.into()); + } + + // Inject GitHub token helper + if std::env::var("KF_GITHUB_TOKEN").is_ok() { + credentials.push("-c".into()); + credentials.push( r#"credential.helper=!_ghcreds() { echo username="kingfisher"; echo password="$KF_GITHUB_TOKEN"; }; _ghcreds"#.into(), - ] - } else { - Vec::new() - }; + ); + } + + // Inject GitLab token helper + if std::env::var("KF_GITLAB_TOKEN").is_ok() { + credentials.push("-c".into()); + credentials.push( + r#"credential.helper=!_glcreds() { echo username="oauth2"; echo password="$KF_GITLAB_TOKEN"; }; _glcreds"#.into(), + ); + } + Self { credentials, ignore_certs } }