SuperAPI AI Directory

private-secret-scanning

General & other

View repository

Updated 10-15 22:40

About this skill

Private secret scanning

GitHub push protection and secret scanning are free on public repositories. Private repositories need a paid GitHub Secret Protection license. This skill gives private and shared repositories a deterministic local replacement: a pinned Gitleaks binary, three scan scopes, and git hooks that stop a leak before it leaves the machine.

Everything runs through one script that ships with this skill: scripts/secret-scan.sh. It needs only Bash (3.2 or later, so the macOS default works), git, curl, and python3.

Quick start

SCAN=path/to/private-secret-scanning/scripts/secret-scan.sh

"$SCAN" install          # pinned Gitleaks 8.30.1, SHA-256 checked before install
"$SCAN" self-test        # proves leaks fail and clean repos pass on this machine
cd your-repo
"$SCAN" history --report /tmp/secret-report.json   # one-time audit of every commit
"$SCAN" install-hooks    # pre-commit and pre-push protection from now on

Scan scopes

Command Scans Use it
staged the staged diff only pre-commit hook; fastest
push [RANGE] commits in RANGE, or the commits a git push is about to send (read from pre-push stdin). A new branch or tag is scanned through its full history pre-push hook; catches commits made with --no-verify
history every commit reachable from any ref, including files deleted later first adoption, and before a private repo goes public

A secret that was committed and then deleted is still in history. Only history finds it, and only rotating the credential fixes it. Rewriting history does not undo a clone that already happened.

Exit codes and failure behavior

Exit Meaning
0 no leaks
1 leaks found; each is listed on stderr
2 scanner or configuration error

The script fails closed. It exits 2, never 0, when:

  • Gitleaks is missing, cannot run, or is not the pinned version.
  • SECRET_SCAN_CONFIG names a file that does not exist.
  • Gitleaks exits with anything other than 0 or the script's own leak code. Gitleaks also exits 1 on fatal errors, such as a malformed .gitleaks.toml, so 1 from Gitleaks is treated as an error.
  • The Gitleaks exit code and its report disagree, or the report is not a list of findings.
  • A .gitleaks.toml neither extends the built-in rules nor defines its own (see below).
  • A hook scan finds .gitleaks.toml or .gitleaksignore untracked or changed but not committed (staged is enough for staged).
  • A push sends a ref that points at something other than a commit, such as a tag on a blob. Such a ref cannot be scanned as history.
  • The report cannot be read.

A hook that exits non-zero blocks the commit or push, so a broken scanner blocks work. It does not let a leak through.

What reports contain

Terminal output and --report files hold five fields per finding: RuleID, File, StartLine, Commit, and Fingerprint. They never hold the secret, the match, or the source line. Gitleaks runs with --redact, its raw report goes to a private temp file that is deleted on exit, and the script copies only those five fields out. The report is written with mode 0600.

The fingerprint (commit:file:rule:line) is enough to find and fix the leak. It is also the line you add to .gitleaksignore to accept a finding.

Baselines and allowlists

Adopting scanning on an old repo usually turns up findings you have already rotated. Record them instead of turning the scanner off:

  1. Rotate every live credential first. Accepting a finding does not make it safe.

  2. Run history --report /tmp/secret-report.json, then write its fingerprints to .gitleaksignore at the repo root:

    python3 -c 'import json,sys; print("\n".join(f["Fingerprint"] for f in json.load(open(sys.argv[1]))))' \
      /tmp/secret-report.json >> .gitleaksignore
    
  3. Commit .gitleaksignore. Later scans report only new findings. Each line names one commit, file, rule, and line, so a new leak of the same secret in another commit still fails.

A .gitleaks.toml replaces the built-in rules unless it extends them. An allowlist-only file therefore turns every detector off, and the script refuses one. Start every config from this:

[extend]
useDefault = true

[allowlist]
description = "synthetic keys in test fixtures"
paths = ['''^tests/fixtures/''']

Keep allowlists narrow. Allow a specific path and rule, such as a test fixture, instead of a whole directory or a broad regex. Never allowlist a rule wholesale because it is noisy. A config that silences a rule also silences the next real leak of that type. Review a .gitleaksignore or allowlist change the way you would review an access-control change.

The script picks up .gitleaks.toml and .gitleaksignore from the repo root automatically. Hooks use them only once they are committed (or staged, for staged). A local edit cannot quietly change what gets through. Set SECRET_SCAN_CONFIG to use a shared config from elsewhere; a relative path is resolved from where you run the script.

Hooks

install-hooks writes pre-commit (runs staged) and pre-push (runs push) into the repository's hooks directory. It refuses to overwrite a hook it did not write; add a call to the script inside that hook instead. git commit --no-verify skips pre-commit, which is why pre-push scans the outgoing range again. A new branch is scanned through its full history, even if a remote-tracking ref says a remote already has those commits. Tracking refs can be stale: git remote set-url points a remote at a new URL, and the old refs stay. This matters when a branch moves from a private remote to a public one.

Keep this public, keep your data private

This skill is generic on purpose. Do not add repository inventories, fleet or host paths, credentials, or real findings to a public copy of it. Keep those in a private repository or local config. Reports belong outside the repository you scanned, or in a gitignored path.

CI

Run the same script in CI for a second check that local --no-verify cannot skip:

- uses: actions/checkout@v6
  with:
    fetch-depth: 0     # history scans need every commit
- run: path/to/secret-scan.sh install
- run: path/to/secret-scan.sh history

Run this only on hosted runners, or on push events from trusted branches. Never run untrusted pull-request code on a self-hosted runner. A fork's PR can rewrite the scan script itself and then read whatever that runner can reach.

Pinned version

The script pins Gitleaks 8.30.1 and the SHA-256 of each platform build from the release's checksums.txt. To upgrade, change GITLEAKS_VERSION and all four hashes together, then run self-test. Set GITLEAKS_BIN to use a binary you installed another way. It must still report the pinned version.