Skip to content
Content
Skill

/private-secret-scanning

Local Gitleaks scans for staged changes, push ranges, and full history in private repos, with redacted reports. Use to block committed secrets without GitHub Secret Protection.

BOOST
From plugin
claude-skills-journalism
40961 skills1 agent22 commands1 hook
Install
$ npx -y skills add jamditis/claude-skills-journalism --skill private-secret-scanning --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/private-secret-scanning

Context preview

The summary Claude sees to decide when to auto-load this skill.

Local Gitleaks scans for staged changes, push ranges, and full history in private repos, with redacted reports. Use to block committed secrets without GitHub Secret Protection.

SKILL.md

private-secret-scanning.SKILL.md
name: private-secret-scanning
description: Local Gitleaks scans for staged changes, push ranges, and full history in private repos, with redacted reports. Use to block committed secrets without GitHub Secret Protection.

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](https://github.com/gitleaks/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 dat

Read more
Ships withclaude-skills-journalism

A collection of Agent Skills for journalists, researchers, academics, media professionals, and communications practitioners. The same repository serves Claude Code and Codex while keeping Claude-only commands, agents, and hooks clearly labeled.

Get the whole plugin

Other skills on claude-skills-journalism.