Skip to content
Closed
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
129 changes: 129 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Secret scanning for clean CI checkouts.
#
# The leaktk.git.pre-commit hook only scans staged changes and ignores the
# filenames passed by pre-commit, so it is a no-op on a clean CI checkout:
# a green "pre-commit run --all-files" is not evidence that PR content was
# scanned. This job closes that gap.
#
# Scope: the working tree of the fresh pull_request checkout, which is the
# tracked content at the merge HEAD. It is not a scan of git history and it
# does not depend on the index being populated.
#
# Behavior: fail-closed. Findings, scanner errors, missing, invalid or
# non-correlated responses and timeouts all fail the job. Scanner stdout and
# stderr are captured and never printed; findings are reported with rule,
# path and line only.
#
# The LeakTK revision is the one pinned by this repository in
# .pre-commit-config.yaml (full commit SHA required; tag-only pins are
# rejected). This is a secondary, non-required gate.
name: secret-scan

on:
pull_request:

permissions: {}

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

jobs:
secret-scan:
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 15
env:
CGO_ENABLED: "0"
LEAKTK_ID: ci-secret-scan
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
cache: false

- name: Resolve the pinned LeakTK revision
run: |
set -euo pipefail
LEAKTK_REV=$(awk '
/^[[:space:]]*-[[:space:]]*repo:[[:space:]]*https:\/\/github\.com\/leaktk\/leaktk[[:space:]]*$/ { in_repo = 1; next }
in_repo && /^[[:space:]]*rev:/ { print $2; exit }
in_repo && /^[[:space:]]*-[[:space:]]*repo:/ { in_repo = 0 }
' .pre-commit-config.yaml | tr -d '"')
if [ -z "${LEAKTK_REV:-}" ]; then
echo "::error::No leaktk rev found in .pre-commit-config.yaml. Adopting the secret scan in a repository without LeakTK requires an explicit scope decision; refusing to scan silently."
exit 1
fi
if ! printf '%s' "$LEAKTK_REV" | grep -qE '^[0-9a-f]{40}$'; then
echo "::error::The leaktk rev in .pre-commit-config.yaml must be a full 40-character commit SHA; got: ${LEAKTK_REV}. Tag-only pins are rejected."
exit 1
fi
echo "LeakTK revision from .pre-commit-config.yaml: ${LEAKTK_REV}"
echo "LEAKTK_REV=${LEAKTK_REV}" >> "$GITHUB_ENV"

- name: Install LeakTK at the pinned revision
run: |
set -euo pipefail
LEAKTK_BIN="$RUNNER_TEMP/leaktk-bin/leaktk"
mkdir -p "$(dirname "$LEAKTK_BIN")"
GOBIN="$(dirname "$LEAKTK_BIN")" go install github.com/leaktk/leaktk@"$LEAKTK_REV"
"$LEAKTK_BIN" version || true

- name: Scan tracked content for secrets
run: |
set -euo pipefail
LEAKTK_BIN="$RUNNER_TEMP/leaktk-bin/leaktk"

HEAD_SHA=$(git rev-parse HEAD)
TRACKED_FILES=$(git ls-files | wc -l | tr -d ' ')
RESPONSE_FILE="$RUNNER_TEMP/leaktk-response.json"
STDERR_FILE="$RUNNER_TEMP/leaktk-stderr.log"

set +e
timeout 480 "$LEAKTK_BIN" scan \
--kind Files \
--leak-exit-code 1 \
--format json \
--id "$LEAKTK_ID" \
. >"$RESPONSE_FILE" 2>"$STDERR_FILE"
SCAN_STATUS=$?
set -e

# Fail closed unless the scanner returns a valid response for this request.
if ! jq -e --arg id "$LEAKTK_ID" '.kind == "ScanResults" and .request_id == $id' "$RESPONSE_FILE" >/dev/null 2>&1; then
echo "::error::LeakTK did not return a valid response for this scan (exit status: ${SCAN_STATUS}); failing closed. Raw scanner output is withheld."
exit 1
fi

if jq -e '.error != null' "$RESPONSE_FILE" >/dev/null; then
echo "::error::LeakTK scan failed: $(jq -r '.error.message' "$RESPONSE_FILE") (code $(jq -r '.error.code' "$RESPONSE_FILE"))"
exit 1
fi

if ! jq -e '(.results | type) == "array"' "$RESPONSE_FILE" >/dev/null; then
echo "::error::LeakTK response has no results array; failing closed."
exit 1
fi

FINDINGS=$(jq '.results | length' "$RESPONSE_FILE")

if [ "$FINDINGS" -gt 0 ]; then
echo "::error::Secret scan found ${FINDINGS} potential secret(s) in the tracked content at HEAD ${HEAD_SHA}."
echo "Redacted findings (secret values are withheld):"
jq -r '.results[] | "- \(.rule.description) at \(.location.path):\(.location.start.line) (rule id: \(.rule.id))"' "$RESPONSE_FILE"
exit 1
fi

if [ "$SCAN_STATUS" -ne 0 ]; then
echo "::error::LeakTK exited with status ${SCAN_STATUS} without reporting findings or errors; failing closed."
exit 1
fi

echo "Secret scan clean: ${TRACKED_FILES} tracked files at HEAD ${HEAD_SHA} scanned with LeakTK ${LEAKTK_REV} (kind=Files on the fresh checkout)."