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
5 changes: 5 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/sh
set -eu

repo_root=$(git rev-parse --show-toplevel)
exec "$repo_root/scripts/secret-scan.sh" staged
41 changes: 41 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Secret scan

on:
pull_request:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read

jobs:
gitleaks:
name: Gitleaks working tree and history
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out complete history
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0

- name: Install verified Gitleaks binary
env:
GITLEAKS_VERSION: 8.30.1
run: |
set -euo pipefail
archive="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
base="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
curl --fail --silent --show-error --location --output "$RUNNER_TEMP/$archive" "$base/$archive"
curl --fail --silent --show-error --location --output "$RUNNER_TEMP/checksums.txt" "$base/gitleaks_${GITLEAKS_VERSION}_checksums.txt"
cd "$RUNNER_TEMP"
grep " $archive$" checksums.txt | sha256sum --check --strict
tar --extract --gzip --file "$archive" gitleaks
chmod 700 gitleaks

- name: Scan working tree and full Git history
env:
GITLEAKS_BIN: ${{ runner.temp }}/gitleaks
run: ./scripts/secret-scan.sh all
4 changes: 4 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title = "Repository secret scanning"

[extend]
useDefault = true
35 changes: 35 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Security and secret handling

Live credentials, private keys, bearer values, recovery material, and
secret-bearing production exports do not belong in this repository, whether it
is private or public.

## Secret scanning

Install Gitleaks and run:

```bash
./scripts/secret-scan.sh staged
./scripts/secret-scan.sh all
```

Enable the versioned local pre-commit hook with:

```bash
git config core.hooksPath .githooks
```

The hook fails closed when Gitleaks is unavailable. CI downloads the pinned
Gitleaks release, verifies its upstream checksum, and scans both the working
tree and complete fetched history with read-only repository permissions.

Do not bypass a failed scan. If a finding is real, revoke or rotate it first,
then remove the material and rescan. Do not copy a credential into an issue,
pull request, log, or remediation note. Any allowlist exception must be narrow
to the exact rule, path, and proven synthetic or false-positive value.

Before making a private repository public, also review every branch and tag,
Git LFS and submodule content, release and Actions artifacts, generated
archives, source maps, notebooks, fixtures, logs, screenshots, workflows, and
private topology. Repeat the scan from a fresh clone and obtain repository-owner
approval before changing visibility.
64 changes: 64 additions & 0 deletions scripts/secret-scan.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail

mode="${1:-all}"
repo_root=$(git rev-parse --show-toplevel)
gitleaks_bin="${GITLEAKS_BIN:-gitleaks}"

if [[ "$gitleaks_bin" == */* ]]; then
if [[ ! -x "$gitleaks_bin" ]]; then
printf 'error: Gitleaks executable not found at %s\n' "$gitleaks_bin" >&2
exit 2
fi
elif ! command -v "$gitleaks_bin" >/dev/null 2>&1; then
printf 'error: gitleaks is required; install it or set GITLEAKS_BIN\n' >&2
exit 2
fi

common=(
--no-banner
--no-color
--redact
--config "$repo_root/.gitleaks.toml"
)

scan_staged() {
(
cd "$repo_root"
"$gitleaks_bin" git "${common[@]}" --pre-commit --staged .
)
}

scan_tree() {
(
cd "$repo_root"
"$gitleaks_bin" dir "${common[@]}" .
)
}

scan_history() {
(
cd "$repo_root"
"$gitleaks_bin" git "${common[@]}" --log-opts="--all" .
)
}

case "$mode" in
staged)
scan_staged
;;
tree)
scan_tree
;;
history)
scan_history
;;
all)
scan_tree
scan_history
;;
*)
printf 'usage: %s [staged|tree|history|all]\n' "$0" >&2
exit 2
;;
esac
Loading