Merge pull request 'feat(security): secret-scan в CI + pre-push хуки (#142)' (#1) from feat/secret-scan#142 into main
Release / GoReleaser (push) Skipped
Lint / Lint (push) Successful in 40s
Release Please / Release Please (push) Failing after 2s
Secret scan / Secret scan (gitleaks) (push) Successful in 3s
Tests / Test (Go 1.22 / ubuntu-latest) (push) Successful in 14s
Tests / Test (Go 1.23 / ubuntu-latest) (push) Successful in 15s
Tests / Test (Go stable / ubuntu-latest) (push) Successful in 22s
Tests / Test (Go stable / macos-latest) (push) Canceled after 0s
Tests / Test (Go stable / windows-latest) (push) Canceled after 0s
Release / GoReleaser (push) Skipped
Lint / Lint (push) Successful in 40s
Release Please / Release Please (push) Failing after 2s
Secret scan / Secret scan (gitleaks) (push) Successful in 3s
Tests / Test (Go 1.22 / ubuntu-latest) (push) Successful in 14s
Tests / Test (Go 1.23 / ubuntu-latest) (push) Successful in 15s
Tests / Test (Go stable / ubuntu-latest) (push) Successful in 22s
Tests / Test (Go stable / macos-latest) (push) Canceled after 0s
Tests / Test (Go stable / windows-latest) (push) Canceled after 0s
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
name: Secret scan
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
secret-scan:
|
||||
name: Secret scan (gitleaks)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Compute scan range (diff of new commits only)
|
||||
id: range
|
||||
run: |
|
||||
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
|
||||
RANGE="${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}"
|
||||
else
|
||||
BEFORE="${{ github.event.before }}"
|
||||
if [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then
|
||||
RANGE="$(git rev-list --max-parents=0 HEAD | tail -1)..$GITHUB_SHA"
|
||||
else
|
||||
RANGE="$BEFORE..$GITHUB_SHA"
|
||||
fi
|
||||
fi
|
||||
echo "RANGE=$RANGE" >> "$GITHUB_ENV"
|
||||
echo "Scanning range: $RANGE"
|
||||
|
||||
- name: Gitleaks (diff-only, fail on leak)
|
||||
uses: docker://zricethezav/gitleaks:latest
|
||||
env:
|
||||
GITLEAKS_RANGE: ${{ env.RANGE }}
|
||||
with:
|
||||
args: detect --source "${{ github.workspace }}" --log-opts="$GITLEAKS_RANGE" --redact --verbose
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# install.sh — symlinks the shared githooks (pre-push, pre-commit) into .git/hooks
|
||||
# for this repository. Safe to run repeatedly.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/githooks/install.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
SRC="$ROOT/scripts/githooks"
|
||||
HOOKS="$ROOT/.git/hooks"
|
||||
|
||||
mkdir -p "$HOOKS"
|
||||
chmod +x "$SRC"/secret-scan.sh "$SRC"/pre-push "$SRC"/pre-commit
|
||||
|
||||
for h in pre-push pre-commit; do
|
||||
if [[ -e "$HOOKS/$h" ]] && [[ ! -L "$HOOKS/$h" ]]; then
|
||||
echo "error: $HOOKS/$h already exists and is not a symlink; remove it first" >&2
|
||||
exit 1
|
||||
fi
|
||||
ln -sfn "$SRC/$h" "$HOOKS/$h"
|
||||
echo "installed $h -> $SRC/$h"
|
||||
done
|
||||
echo "githooks installed for $(basename "$ROOT")"
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# pre-commit git hook — blocks a commit if staged changes contain a secret.
|
||||
# Scans only the staged (index) diff with gitleaks (via secret-scan.sh).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
if [[ "$SCRIPT_DIR" == "$ROOT/scripts/githooks" ]]; then
|
||||
SCAN="$SCRIPT_DIR/secret-scan.sh"
|
||||
else
|
||||
SCAN="$ROOT/scripts/githooks/secret-scan.sh"
|
||||
fi
|
||||
|
||||
if ! "$SCAN" --staged; then
|
||||
echo "pre-commit: LEAK FOUND in staged changes; commit BLOCKED. Remove the secret first." >&2
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# pre-push git hook — blocks a push if any NEW commit leaks a secret.
|
||||
#
|
||||
# Reads the refs being pushed from stdin (format:
|
||||
# <local ref> <local sha> <remote ref> <remote sha>
|
||||
# per ref). Scans only the new commits with gitleaks (via secret-scan.sh) and
|
||||
# aborts (exit 1) if anything is found.
|
||||
#
|
||||
# Install: ln -s ../../scripts/githooks/pre-push .git/hooks/pre-push
|
||||
# (or run scripts/githooks/install.sh)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Resolve symlinks so the hook works whether copied into .git/hooks or
|
||||
# symlinked from scripts/githooks (and in worktrees sharing the main repo hooks).
|
||||
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
|
||||
ROOT="$(git rev-parse --show-toplevel)"
|
||||
if [[ "$SCRIPT_DIR" == "$ROOT/scripts/githooks" ]]; then
|
||||
SCAN="$SCRIPT_DIR/secret-scan.sh"
|
||||
else
|
||||
SCAN="$ROOT/scripts/githooks/secret-scan.sh"
|
||||
fi
|
||||
|
||||
zero=0000000000000000000000000000000000000000
|
||||
failed=0
|
||||
|
||||
while read -r local_ref local_sha remote_ref remote_sha; do
|
||||
[[ -n "$local_sha" ]] || continue
|
||||
|
||||
# Deletion push — nothing to scan.
|
||||
if [[ "$local_sha" == "$zero" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# New branch (no remote ref yet): scan only the commits this branch ADDS over
|
||||
# its merge-base with the integration branch (PR target), NOT all history.
|
||||
# This keeps pre-existing historical leaks (see #140) from blocking new work.
|
||||
if [[ "$remote_sha" == "$zero" ]]; then
|
||||
base=""
|
||||
for base_ref in origin/release/v1 origin/release/v2 origin/master origin/main; do
|
||||
if git rev-parse --verify "$base_ref" >/dev/null 2>&1; then
|
||||
base="$(git merge-base "$base_ref" "$local_sha" 2>/dev/null)"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ -z "$base" ]] && git rev-parse --verify origin/HEAD >/dev/null 2>&1; then
|
||||
base="$(git merge-base origin/HEAD "$local_sha" 2>/dev/null)"
|
||||
fi
|
||||
[[ -z "$base" ]] && base="$(git rev-list --max-parents=0 "$local_sha" 2>/dev/null | tail -1)"
|
||||
range="${base}..${local_sha}"
|
||||
else
|
||||
range="${remote_sha}..${local_sha}"
|
||||
fi
|
||||
|
||||
echo "secret-scan: scanning new commits ${range} (ref ${local_ref})"
|
||||
if ! "$SCAN" --range "$range"; then
|
||||
echo "secret-scan: LEAK FOUND in ${local_ref}; push BLOCKED. Remove the secret before pushing." >&2
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$failed" -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# secret-scan.sh — shared secret scanner used by git hooks (pre-push, pre-commit)
|
||||
# and by SE agents before any push.
|
||||
#
|
||||
# Scans ONLY the diff of new commits (or staged changes) with gitleaks, never the
|
||||
# full history. Fails (exit non-zero) on any finding, so a leaking push is blocked.
|
||||
#
|
||||
# Uses the gitleaks Docker image (gitleaks/gitleaks) if docker is available,
|
||||
# otherwise a locally installed `gitleaks` binary. No secret VALUES are ever
|
||||
# printed: findings are emitted redacted.
|
||||
#
|
||||
# Usage:
|
||||
# secret-scan.sh <range> scan a git log range, e.g. origin/release/v1..HEAD
|
||||
# secret-scan.sh --staged scan staged (index) changes
|
||||
# secret-scan.sh --all scan full history (warning: not for normal use)
|
||||
#
|
||||
# Exit codes: 0 = clean, 1 = leaks found (caller should abort), 2 = scan failed.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITLEAKS_IMAGE="zricethezav/gitleaks:latest"
|
||||
|
||||
run_gitleaks() {
|
||||
# $@ = gitleaks args; runs in current dir (a git repo).
|
||||
if command -v gitleaks >/dev/null 2>&1; then
|
||||
gitleaks "$@"
|
||||
elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
||||
docker run --rm -v "$PWD:/repo" -w /repo "$GITLEAKS_IMAGE" "$@"
|
||||
else
|
||||
echo "error: secret-scan: neither 'gitleaks' binary nor docker image available" >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
mode="${1:---range}"
|
||||
shift || true
|
||||
|
||||
case "$mode" in
|
||||
--range)
|
||||
range="${1:?usage: secret-scan.sh <range>}"
|
||||
run_gitleaks detect --source "$PWD" --no-banner --redact --log-opts="$range" >&2
|
||||
;;
|
||||
--staged)
|
||||
# Scan only staged (index) content: pipe `git diff --cached` through gitleaks --pipe.
|
||||
git diff --cached --binary | run_gitleaks detect --pipe --no-banner --redact >&2
|
||||
;;
|
||||
--all)
|
||||
run_gitleaks detect --source "$PWD" --no-banner --redact >&2
|
||||
;;
|
||||
*)
|
||||
echo "error: secret-scan: unknown mode '$mode'" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user