You Pasted the Token Into config.js at 2 AM
It’s 2 AM. Your self-hosted Renovate runner refuses to authenticate. You have been staring at RENOVATE_TOKEN and RENOVATE_GITHUB_COM_TOKEN for an hour. You paste both values straight into config.js “just to test”, the runner springs to life, and you run git add -A && git commit -m "fix renovate" before your brain catches up.
Then you push. The CI runner clones the repo. A mirror syncs it. Someone’s fork pulls the new branch. A scraper that watches public commit feeds grabs it in under a minute. You notice at 9 AM and delete the file in a new commit, which feels like closing the barn door while admiring the horse’s new life elsewhere.
Renovate is not the villain here. Any tool that needs tokens invites this mistake, and a self-hosted bot needs two of them. The point is that a secret which reaches git push is already gone. The only place a leak is still free to fix is the pre-commit hook on your own machine. My default pick for that hook is gitleaks. TruffleHog is the upgrade when you want proof that a credential is live. detect-secrets is the third option for people already living in Python-land.
Full example: Clone the working files at github.com/KingPin/sumguy-examples/devops/pre-commit-secret-scanning
Why “I’ll delete it in the next commit” Does Not Work
Git keeps history. A later commit that removes the token leaves the old commit intact, and every clone carries every commit. Renovate opening PRs, CI runners printing logs, Dependabot, mirrors, and forks all take full copies.
GitHub’s server-side secret scanning fires alerts after the push lands. Push protection blocks at the push boundary, but only for the token patterns the provider supports. A hook on your machine runs before the commit object exists, so there is nothing to clean up.
If it already happened, rotate the credential. Do it now. Rewriting history with git filter-repo makes the repo look clean, but it does not un-leak anything a bot or fork already cloned. Rotate first, scrub later (if you scrub at all).
The 3-Minute Setup: pre-commit Framework
All three scanners plug into the pre-commit framework (v4.6.2 as of October 2026). Install it, then wire it into the repo.
pipx install pre-commit # or: pip install pre-commitpre-commit install # writes .git/hooks/pre-commitpre-commit autoupdate # bumps rev: pins to the latest tagspre-commit run --all-files # scan the whole tree onceHere is a .pre-commit-config.yaml with gitleaks, TruffleHog, and a few basics from pre-commit-hooks v6.0.0. In practice you run one scanner, not both. I list both so you can compare them on your own repo.
repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-added-large-files - id: detect-private-key - id: end-of-file-fixer
- repo: https://github.com/gitleaks/gitleaks rev: v8.30.1 hooks: - id: gitleaks
- repo: https://github.com/trufflesecurity/trufflehog rev: v3.99.0 hooks: - id: trufflehogdetect-private-key is a cheap tripwire for PEM headers. It overlaps with the scanners, and that is fine. Cheap and redundant beats expensive and missing.
Gitleaks: The Fast Offline Gate
Gitleaks is a Go binary that matches your staged changes against a set of regex rules. No network calls, no accounts. Version 8.30.1 came out in March 2026.
The pre-commit repo ships three hook ids:
gitleaksbuilds from source withgo install(language: golang). pre-commit bootstraps its own Go if none is installed, so the first setup needs network access and a minute of patience, not a toolchain.gitleaks-dockeruses thezricethezav/gitleaksimage.gitleaks-systemuses a gitleaks that is already on yourPATH.
The hook runs gitleaks git --pre-commit --redact --staged --verbose with pass_filenames: false. It looks only at what you staged, and --redact keeps the secret out of your terminal scrollback. A failing run looks like this:
Detect hardcoded secrets.................................................FailedTuning gitleaks without breaking a sweat
False positives are the tax you pay for any scanner. Gitleaks gives you four ways to quiet them, from narrow to wide.
- Inline: add a
gitleaks:allowcomment on the line. Pass--ignore-gitleaks-allowif you want CI to ignore those comments and report everything. - Fingerprint: list a finding in
.gitleaksignore. A fingerprint looks like<commit>:<path>:<rule-id>:<line>, and for staged changes that have no commit yet it drops the first field (config.env:github-pat:1). The-i, --gitleaks-ignore-pathflag (default.) points at the file. - Config: a
.gitleaks.tomlthat extends the defaults and adds an allowlist. - Skip once:
SKIP=gitleaks git commit -m "...". Use this rarely and on purpose.
Here is a config that keeps the built-in rules and quiets one noisy rule for docs and a marker string. Since v8.25.0 the global [allowlist] became [[allowlists]], and since v8.21.0 a rule’s [rules.allowlist] became [[rules.allowlists]].
[extend]useDefault = true
[[rules]]id = "generic-api-key"
[[rules.allowlists]] regexTarget = "line" regexes = ['''EXAMPLE_TOKEN_DO_NOT_USE'''] paths = ['''docs/.*\.md$''']Allowlist fields you can use: description, condition (OR by default, or AND), commits, paths, regexes, regexTarget (secret by default, or match, or line), and stopwords, which target the extracted secret. Note the AND condition: with it, a finding must satisfy every filter in the block before it is ignored. With the default OR, any single match is enough.
The exit code is 1 on a finding by default. Change it with --exit-code if a pipeline needs something else.
Is gitleaks dead?
No. As of October 2026 the README carries a warning: gitleaks is feature complete, no new features will be merged, and future releases are security patches only. The author’s attention has moved to Betterleaks (github.com/betterleaks/betterleaks), created in February 2026, with v1.9.0 out on 2026-09-29 and about 2,100 GitHub stars.
That sounds like a reason to panic and is a reason to calendar a look in six months. A feature-complete regex scanner with security patches is a fine thing to have in a commit hook. Nobody needs a new feature in their hook at 2 AM. You need it to run fast and stay quiet. Pin the rev, let Renovate bump it (more on that below), and move when Betterleaks earns it.
TruffleHog: The One That Checks If the Key Works
TruffleHog v3 is a Go rewrite with 700+ credential detectors, and its party trick is active verification. When it finds something that looks like an AWS key, it calls the vendor API to check whether the credential is live. A match that works is a verified finding. A match that is a dead test key from 2019 is noise, and TruffleHog can drop it.
That means fewer false positives. It also means your commit makes outbound network calls.
The hook (v3.99.0 as of October 2026) runs this:
trufflehog git file://. --since-commit HEAD --results=verified --fail --trust-local-git-configWhat each flag does:
--results=verifiedreports only credentials confirmed live. Use--results=verified,unknownto also include ones it could not verify (a failed verification call, for example). With no--resultsflag you get unverified pattern matches too. TruffleHog’s own pre-commit guide recommendsverified,unknownfor a hook, so a failed verification call still blocks the commit. The hook id in the repo ships withverifiedonly. If you want the stricter form, define arepo: localhook with that entry, which is what the guide shows.--failexits non-zero when it finds something, which is what blocks the commit.--trust-local-git-configskips a safety clone. Since CVE-2025-41390, TruffleHog clones a local repo to a temp directory before scanning, to guard against malicious git configs. That is the right default for a repo you downloaded from a stranger. For your own repo, scanning in place is fine, which is why the hook sets it. Use--clone-pathif you want a custom clone location.- A verified private key means TruffleHog confirmed it works for live SSH or SSL authentication.
Ignoring a line works with a trufflehog:ignore comment. --no-ignore-tag reports results even when the comment is present, which is handy in CI.
The honest trade-offs
Three gotchas to know before you adopt it.
- Offline commits report nothing. With
--results=verified, no network means nothing can be verified, so nothing is reported. On a plane, your hook passes everything. That is the worst failure mode: a green check that means “I could not look”. - Commits are slower. Verification calls take time. I will not give you a number, because the time scales with how many candidates you stage and how fast the vendors answer. Expect “slower than a regex”, not “instant”.
- Stage, then commit. TruffleHog’s pre-commit guide says to run
git addandgit commitas separate steps and to avoidgit commit -am, because unstaged modifications can slip past the hook. Test with a staged fake secret before you trust it (the script below does exactly that).
There is also a privacy angle. A live verification call sends your candidate credential to the vendor’s API. For a real key, you are telling the vendor that a key is in your commit. Most teams accept that, and you should decide on purpose.
detect-secrets: The Baseline Workflow
Yelp’s detect-secrets (v1.5.0, last released 2024-05-06) works differently. It uses entropy checks and plugins, so it flags high-entropy strings that are not credentials. The answer to the noise is a baseline file: you scan once, audit the results, and the hook then only complains about new findings.
repos: - repo: https://github.com/Yelp/detect-secrets rev: v1.5.0 hooks: - id: detect-secrets args: ['--baseline', '.secrets.baseline']The workflow:
detect-secrets scan > .secrets.baselinedetect-secrets audit .secrets.baselinegit ls-files -z | xargs -0 detect-secrets-hook --baseline .secrets.baselineThe first command writes the baseline. The second walks you through each finding interactively, marking real secrets and false positives. The third scans every tracked file against the baseline. For a single line, add # pragma: allowlist secret.
Tuning flags exist: --base64-limit and --hex-limit (entropy thresholds), --disable-plugin, --exclude-files, --exclude-lines, --exclude-secrets, --word-list, and --list-all-plugins to see what is available.
The release cadence is slow. The last release was in May 2024, which is a long gap in a field where vendors keep changing token formats. I would pick it only if your shop already runs on Python tooling and you like the audit-and-baseline ritual.
The Comparison Table
| gitleaks | TruffleHog | detect-secrets | |
|---|---|---|---|
| Works offline | Yes | Runs, but verified-only reports nothing | Yes |
| Verification | No (regex rules) | Yes, calls vendor APIs | No (entropy + plugins) |
| Inline allow | gitleaks:allow | trufflehog:ignore | # pragma: allowlist secret |
| Ignore mechanism | .gitleaksignore fingerprints, .gitleaks.toml allowlists | Inline tag, plus exclude flags | .secrets.baseline |
| Maintenance (Oct 2026) | v8.30.1, security patches only | v3.99.0, released 2026-10-06 | v1.5.0, last release 2024-05-06 |
| Pick when | You want a fast, quiet gate on every commit | You want proof a key is live and accept network calls | You live in the Yelp/Python world and like baselines |
Prove It Blocks: leak-test.sh
Never trust a hook you have not watched fail. This script makes a throwaway repo, copies in the configs, stages a fake token, and tries to commit. The token is fake. It matches the shape of a GitHub token and does not authenticate anywhere.
#!/usr/bin/env bashset -euo pipefail
here="$(cd "$(dirname "$0")" && pwd)"tmp="$(mktemp -d)"trap 'rm -rf "$tmp"' EXIT
cd "$tmp"git init -qgit config user.name "Leak Test"cp "$here/.pre-commit-config.yaml" "$here/.gitleaks.toml" .pre-commit install
# Fake token: right shape, not a real credential.echo 'RENOVATE_GITHUB_COM_TOKEN=ghp_aB3dE5gH7jK9mN1pQ3sT5vW7yZ9bC1dE3fG5' > config.envgit add config.env
if git commit -m "add config" ; then echo "FAIL: the commit went through, no hook caught the fake token" exit 1else echo "OK: the hook blocked the commit"fiRun it with bash leak-test.sh. You should see the Failed line from gitleaks and the final “OK” message. If you see “FAIL”, your hook is not wired up, and you just saved yourself a very bad morning.
Heads up: with the --results=verified default, TruffleHog does not flag this fake token. In my run it reported Passed while gitleaks reported Failed on rule github-pat, which is the verification working as designed. Also expect the first run to be slow: pre-commit builds both scanners from source before the hooks fire.
The Gotcha: Hooks Only Run Where You Installed Them
pre-commit install writes a file into your local .git/hooks. Hooks do not travel with the repo. A teammate who clones and never runs pre-commit install has no protection, and neither does a bot that commits through an API.
The backstop is CI. Run the same scan in the pipeline so a missed install still gets caught before merge. A generic job needs only the binary and one command:
# Option 1: reuse the exact same hookspre-commit run --all-files
# Option 2: scan the history range of the pull requestgitleaks git --redact --verbose --log-opts="$BASE_SHA..HEAD"trufflehog git file://. --since-commit "$BASE_SHA" --results=verified --failCI is the net under the trapeze. It catches the fall, but the secret has already been pushed to a branch by then. It limits who can see it, and it does not replace the hook. Rotate anything CI catches.
Close the Loop With Renovate
Back to the bot from the war story. Renovate can manage the rev: pins in your .pre-commit-config.yaml through its pre-commit manager. Renovate’s docs mark the manager as beta and it is disabled by default, so turn it on in renovate.json:
{ "pre-commit": { "enabled": true }}Now the bot that nearly leaked your token keeps your secret scanner on the latest release (Renovate is at 44.145.0 as of October 2026). Keep the Renovate tokens in the runner’s environment or a secrets store, never in config.js or a compose file you commit. For a compose file, use an env_file that is git-ignored, or Docker secrets.
The SumGuy Take
Run gitleaks. Pin it, add the .gitleaks.toml above, and keep detect-private-key next to it. It works on a plane, it scanned the 67-byte test file below in 21 ms, and it blocks the commit before there is a commit. Feature complete is a fine state for a regex gate.
Reach for TruffleHog when false positives are costing you more than slower commits, or when you want to know whether a leaked key was alive. Treat the network dependency as a real cost, not a footnote. Pick detect-secrets if your team already runs on Yelp-style Python tooling and wants the audit workflow.
Add the CI backstop either way. Then rotate anything that ever escaped, because history rewrites do not un-leak. Your 2 AM self will thank you.
Common Questions
What should I do after I already pushed a secret to GitHub?
Rotate the credential immediately, then check the provider’s logs for use. Rewriting history with git filter-repo does not help against anything that cloned the repo already, including bots, mirrors, and forks. Treat the secret as public. Scrub history afterward only to stop new readers from seeing a dead token.
Does gitleaks work offline?
Yes, gitleaks works fully offline. Gitleaks matches rules against your staged changes locally and never calls an external service. The gitleaks hook id builds from source on first use, so pre-commit needs network access once to fetch and build it. After that, every commit scans without a connection.
Does GitHub push protection make a local pre-commit hook redundant?
No, a local hook still earns its place. Push protection acts at the push boundary and only covers the token patterns the provider supports. A pre-commit hook blocks the secret before a commit object exists, works on any host, and can use your own custom rules.
Is gitleaks still safe to use now that it is feature complete?
Yes. As of October 2026, gitleaks v8.30.1 still receives security patches, and the README says future releases are security patches only. The author now focuses on Betterleaks. Gitleaks has no new features coming, so pin a version and plan a review of Betterleaks rather than an urgent migration.
Will the hook protect teammates who never ran pre-commit install?
No. The pre-commit framework writes the hook into each clone’s local .git/hooks, so a teammate who skips pre-commit install commits unscanned. Add a CI job that runs pre-commit run --all-files or the scanner directly, so a missed install gets caught before merge.