Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/scripts/gitleaks-changes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Scan only the changes being introduced for secrets, not content that was already
# scanned elsewhere. Run by the gitleaks hook in .pre-commit-config.yaml.
#
# - GITLEAKS_BASE set (CI): the commits in GITLEAKS_BASE..HEAD, plus the lines that
# merge commits in that range introduced themselves (e.g. conflict resolutions),
# not what they brought in from the merged branch. Set but empty: an error, so a
# run without a base cannot pass without scanning anything.
# - Merge commit in progress: what differs from the branch being merged in (this
# branch's changes and the conflict resolutions), not everything merged in.
# - Otherwise: the staged changes (the upstream hook's behavior).
set -euo pipefail

# Lines of a combined diff (git show --cc) that are in none of the merge's parents,
# with the file headers. With N parents those lines start with N "+".
merge_introduced_lines() {
local merge=$1 parents
parents=$(($(git rev-list --parents -n 1 "$merge" | wc -w) - 1))
git show --cc --format= "$merge" | awk -v n="$parents" '
BEGIN { plus = sprintf("%" n "s", ""); gsub(/ /, "+", plus) }
/^diff --cc / { print; next }
/^\+\+\+ (b\/|\/dev\/null)/ { next }
substr($0, 1, n) == plus { print }
'
}

if [[ -n "${GITLEAKS_BASE+set}" ]]; then
if [[ -z "${GITLEAKS_BASE}" ]]; then
echo "gitleaks: GITLEAKS_BASE is set but empty, refusing to pass without scanning" >&2
exit 1
elif git rev-parse --quiet --verify "${GITLEAKS_BASE}^{commit}" >/dev/null; then
range=("${GITLEAKS_BASE}..HEAD")
else
# e.g. first push of a branch (all zeros) or a base that was force-pushed away
echo "gitleaks: base ${GITLEAKS_BASE} not found, scanning the last commit only" >&2
range=(-1 HEAD)
fi

status=0
echo "gitleaks: scanning commits ${range[*]}"
gitleaks git --redact --verbose --log-opts="${range[*]}" || status=1

# git log -p (used by gitleaks git) shows no diff for merge commits
for merge in $(git rev-list --merges "${range[@]}"); do
echo "gitleaks: scanning the lines merge commit ${merge} introduced"
merge_introduced_lines "$merge" | gitleaks stdin --redact --verbose || status=1
done
exit "$status"
fi

if git rev-parse --quiet --verify MERGE_HEAD >/dev/null; then
# Added lines only (with the file headers), removed lines are not introduced
git diff --cached --unified=0 MERGE_HEAD | { grep -E '^(diff --git |\+)' || true; } | gitleaks stdin --redact --verbose
exit
fi

exec gitleaks git --pre-commit --redact --staged --verbose
44 changes: 44 additions & 0 deletions .github/workflows/gitleaks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Gitleaks

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
gitleaks:
name: gitleaks
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# gitleaks scans the commits since the base, which needs the history
fetch-depth: 0

- name: Install gitleaks
env:
GITLEAKS_VERSION: 8.30.1
GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
run: |
archive="$RUNNER_TEMP/gitleaks.tar.gz"
curl -sSfL -o "$archive" "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
echo "${GITLEAKS_SHA256} ${archive}" | sha256sum --check --strict -
mkdir "$RUNNER_TEMP/gitleaks-bin"
tar -xzf "$archive" -C "$RUNNER_TEMP/gitleaks-bin" gitleaks
echo "$RUNNER_TEMP/gitleaks-bin" >> "$GITHUB_PATH"

# The commits of the PR (or push), plus the lines merge commits introduced
# themselves. A manual run has no base and scans the whole history.
- name: Scan the commits of the PR (or push) for secrets
env:
GITLEAKS_BASE: ${{ github.event.pull_request.base.sha || github.event.before }}
run: .github/scripts/gitleaks-changes.sh
Loading