- scripts/setup-gitleaks.sh — installs gitleaks v8.24.2, seeds .gitleaks.toml on first run, writes managed pre-commit hook block; re-run replaces block in place without disturbing other hook content - scripts/gitleaks.toml — base config template extending default ruleset - .gitleaks.toml — repo config with docs/research/ path allowlist (high-entropy terminal captures; v8.24.2 [allowlist] syntax) - tests/test-setup-gitleaks.sh — 6 behavior tests including stale-block replacement and idempotency - .agents/skills/gitleaks/ — cross-cutting skill covering install, update, allowlist tuning, scan modes, and real-finding remediation - .agents/evals/cross-cutting/gitleaks/eval.yaml — 7 trigger + 3 output tests including version-aware allowlist guidance case - docs/spec/overview.md — updated to reflect new tooling and skill Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
125 lines
3.5 KiB
Bash
Executable File
125 lines
3.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Sets up gitleaks as a git pre-commit hook in a target repository.
|
|
# Usage: setup-gitleaks.sh [TARGET_REPO]
|
|
# TARGET_REPO — path to the git repo to configure (default: current directory)
|
|
# Idempotent: safe to re-run; always replaces the hook block with the current version.
|
|
|
|
GITLEAKS_VERSION="8.24.2"
|
|
GITLEAKS_INSTALL_DIR="${GITLEAKS_INSTALL_DIR:-/usr/local/bin}"
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TARGET="${1:-$(pwd)}"
|
|
HOOK_FILE="$TARGET/.git/hooks/pre-commit"
|
|
CONFIG_SRC="$SCRIPT_DIR/gitleaks.toml"
|
|
CONFIG_DEST="$TARGET/.gitleaks.toml"
|
|
MARKER="# managed by setup-gitleaks.sh"
|
|
END_MARKER="# end gitleaks"
|
|
|
|
# --- Install gitleaks if not present ---
|
|
|
|
install_gitleaks() {
|
|
local os arch tarball url tmp_dir
|
|
|
|
case "$(uname -s)" in
|
|
Linux) os="linux" ;;
|
|
Darwin) os="darwin" ;;
|
|
*)
|
|
echo "Error: unsupported OS '$(uname -s)' — install gitleaks manually from https://github.com/gitleaks/gitleaks/releases" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
case "$(uname -m)" in
|
|
x86_64) arch="x64" ;;
|
|
aarch64 | arm64) arch="arm64" ;;
|
|
*)
|
|
echo "Error: unsupported architecture '$(uname -m)' — install gitleaks manually from https://github.com/gitleaks/gitleaks/releases" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
tarball="gitleaks_${GITLEAKS_VERSION}_${os}_${arch}.tar.gz"
|
|
url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${tarball}"
|
|
tmp_dir="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp_dir"' RETURN
|
|
|
|
echo "Installing gitleaks v${GITLEAKS_VERSION}..."
|
|
curl -fsSL "$url" -o "$tmp_dir/$tarball"
|
|
tar -xzf "$tmp_dir/$tarball" -C "$tmp_dir" gitleaks
|
|
install -m 755 "$tmp_dir/gitleaks" "$GITLEAKS_INSTALL_DIR/gitleaks"
|
|
echo "Installed: $GITLEAKS_INSTALL_DIR/gitleaks"
|
|
}
|
|
|
|
if ! command -v gitleaks &>/dev/null; then
|
|
install_gitleaks
|
|
fi
|
|
|
|
# --- Validate ---
|
|
|
|
if [ ! -d "$TARGET/.git" ]; then
|
|
echo "Error: $TARGET is not a git repository" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "$CONFIG_SRC" ]; then
|
|
echo "Error: config template not found at $CONFIG_SRC" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Deploy config ---
|
|
|
|
if [ -f "$CONFIG_DEST" ]; then
|
|
echo "Skipped: $CONFIG_DEST already exists — edit it directly to customise rules."
|
|
else
|
|
cp "$CONFIG_SRC" "$CONFIG_DEST"
|
|
echo "Wrote: $CONFIG_DEST"
|
|
echo " Commit this file — it belongs in version control."
|
|
fi
|
|
|
|
# --- Deploy hook ---
|
|
|
|
hook_block() {
|
|
cat <<BLOCK
|
|
|
|
$MARKER
|
|
if command -v gitleaks &>/dev/null; then
|
|
gitleaks git --staged --redact -v
|
|
else
|
|
echo "Warning: gitleaks not installed — secret scan skipped (https://github.com/gitleaks/gitleaks/releases)" >&2
|
|
fi
|
|
$END_MARKER
|
|
BLOCK
|
|
}
|
|
|
|
write_hook() {
|
|
local hook_file="$1"
|
|
|
|
if grep -qF "$MARKER" "$hook_file"; then
|
|
# Remove old block (start marker through end marker inclusive) then append current version
|
|
awk -v start="$MARKER" -v end="$END_MARKER" '
|
|
$0 == start { skip=1; next }
|
|
skip && $0 == end { skip=0; next }
|
|
!skip { print }
|
|
' "$hook_file" > "${hook_file}.tmp" && mv "${hook_file}.tmp" "$hook_file"
|
|
hook_block >> "$hook_file"
|
|
echo "Updated: $hook_file (gitleaks block replaced)"
|
|
else
|
|
hook_block >> "$hook_file"
|
|
echo "Updated: $hook_file (gitleaks block appended to existing hook)"
|
|
fi
|
|
}
|
|
|
|
if [ -f "$HOOK_FILE" ]; then
|
|
write_hook "$HOOK_FILE"
|
|
else
|
|
{ echo '#!/usr/bin/env bash'; echo 'set -euo pipefail'; hook_block; } > "$HOOK_FILE"
|
|
chmod +x "$HOOK_FILE"
|
|
echo "Created: $HOOK_FILE"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Done. Staged secrets will be scanned on every commit in $TARGET."
|
|
echo "To skip on a single commit: SKIP=gitleaks git commit ..."
|