Secret scanning in 2026: revisit Gitleaks, TruffleHog and detect-secrets before another API key lands in a commit

Picture a software company in Bhubaneswar that builds cold chain tracking and export paperwork software for seafood processors and exporters along the Odisha and north Andhra coast (an invented example, not a real client). About 70 processing plants and exporters use it to log reefer container temperatures, track lots from pond to packing, and prepare the documents a shipment needs. The company has about 40 developers: a Django backend team, a small Go team for the sensor gateway, and a mobile team. Code lives on a self-managed GitLab CE server (the self-hosted Git forges post compared it with Gitea and Forgejo), CI runs on GitLab runners, and application secrets are supposed to live in OpenBao.

In August, an intern wanted to try a free CI service for a side project and pushed a copy of the sensor gateway repository to his personal public account on a public code hosting site. The current code was clean. The history was not: in 2023, a developer had committed an .env file with an object storage access key for the bucket that holds shipment documents, and deleted it in the next commit. Everyone had forgotten about it, and nobody had rotated the key. Ten days later, the operations team noticed requests from unfamiliar addresses listing that bucket. The key was revoked the same evening, the public copy was taken down, and the team spent a week working out what had been read.

The post-incident meeting ended with one action item: “add a secret scanner”. Three proposals arrived. The DevOps lead wanted Gitleaks, because GitLab’s own pipeline secret detection already runs it underneath. A security consultant the company works with suggested TruffleHog, because it checks whether a found key is still live. The Django team lead had used Yelp’s detect-secrets at a previous job and liked its baseline file in the repository.

This post compares those three as they stand on 25 October 2026. It covers what each one is, how secret detection actually works (patterns, entropy, keywords, baselines and live verification), where to run it (on the laptop, in CI, and over the full history), and what to do when it finds something. GitHub push protection, GitLab’s built-in secret detection and GitGuardian’s ggshield come up briefly as context, but the focus is the three open source tools you can run anywhere.

The facts come from primary material: the projects’ READMEs, licence files, release notes and security advisories, fetched through the GitHub API and raw files on 25 October 2026, plus GitLab and GitHub documentation and the NVD record for one CVE. I also ran a small local drill on the box with fake, randomly generated secrets and no network access; the results appear where they are useful and in the drill note near the end. Earlier Revisit.Tech posts covered Vault and OpenBao, Kubernetes secrets delivery and vulnerability scanners. This one is about the step before all of them: catching the secret that should never have been typed into a file in the first place.

The short version

  • Git never forgets by itself. Deleting a key in the next commit hides it from the working tree, not from history, clones, forks or mirrors. A scanner that only looks at the current files would have found nothing in the Bhubaneswar repository. In my drill, a Gitleaks scan of the working directory found nothing, and a scan of history in the same repository found three secrets.
  • Rotate first, rewrite later. Once a live secret has been pushed, treat it as known to others. Revoke or rotate it, check the provider’s logs, and only then decide whether to clean history. History rewriting is housekeeping, not the fix.
  • Gitleaks 8.30.1 is the simple, fast default, MIT licensed, with a clear TOML rule format, baselines, fingerprints and SARIF output. But its README now says it is feature complete and future releases will be security patches only; its author has moved to a new MIT project, Betterleaks, which is at v1.9.0 with v2 in release candidates. Gitleaks still works well; just know that the road ahead is elsewhere.
  • TruffleHog 3.99.7 adds verification. For many detectors it logs in to the provider to check whether a key is live, and it scans far more than Git (S3, Docker images, GitHub and GitLab metadata, and more). It is AGPL-3.0, releases very often (44 releases so far in 2026), and verification means the secret is sent to the provider’s API, by default.
  • detect-secrets 1.5.0 is built around a baseline that lives in the repository and is audited by people. It is good at keyword based catches like password = "..." that pattern tools miss. But its last release was in May 2024, it scans files rather than Git history, and it also tries to verify secrets over the network by default unless you pass --no-verify.
  • For the Bhubaneswar company: Gitleaks as a pre-commit hook and as a merge request job (GitLab CE’s free pipeline secret detection is a fine start), TruffleHog for a one-time full-history sweep and a nightly scan on one runner with controlled egress, and detect-secrets only in the Django repositories if its keyword catches prove useful during a trial.

Where things stand on 25 October 2026

All times are Indian Standard Time; GitHub gives release stamps in UTC, and I converted them.

Gitleaks

  • Latest release: 8.30.1 (21 March 2026, 7:47 AM). The notes list a goreleaser update, a report template clean-up and a switch to Go 1.24.
  • Before that: 8.30.0 (26 November 2025, 10:01 PM) added rules for Looker client credentials and Airtable personal access tokens, and has a changelog entry titled “0 to 5 - notes on recursive decoding”. 8.29.0 (5 November 2025, 3:12 AM) added an Amazon Bedrock API key rule, a minimum config version setting and validation of rules when [extend] is used. 8.28.0 (20 July 2025, 9:48 PM) introduced composite rules (a primary rule that only reports when auxiliary rules match nearby) and an Anthropic API key rule.
  • Project status: the README carries a warning that Gitleaks is feature complete, that new features will not be merged, that future releases will be security patches only, and that the author is shifting focus to Betterleaks. The repository is not archived; commits since June 2026 are mostly automated dependency updates for its CI workflows, the latest on 24 October 2026.
  • Licence: MIT (checked in the LICENSE file, copyright Zachary Rice). The separate gitleaks-action for GitHub Actions is not MIT since its v2.0.0; its README says organisations need a free licence key from gitleaks.io, while personal accounts do not. Its latest release is v3.0.0 (31 May 2026, 3:12 AM).
  • Where else you meet it: GitLab’s pipeline secret detection, available in the Free tier on self-managed GitLab, shows Couldn't run the gitleaks command in its documented error messages, and excludes files named gitleaks.toml from scans. That tells you what is running underneath.

Betterleaks, briefly

  • What it is: a newer scanner whose README says it is “maintained by the folks who made Gitleaks, including the original author”, with development supported by Aikido Security. MIT licence. The repository was created on 3 February 2026; the earliest release in the GitHub list is v1.0.1 (20 February 2026).
  • Versions: v1.9.0 (30 September 2026, 1:38 AM) is the latest stable. v2.0.0-rc.1 (30 September 2026, 7:12 PM) through rc.4 (20 October 2026, 8:39 PM) are release candidates; the rc.1 notes warn that breaking changes to the CLI, configuration, report formats and Go API may still land before v2.0.0.
  • Why it matters here: it adds things Gitleaks never had, such as opt-in validation (-v), credential analysis (-a), explicit revocation as a separate command, and filters written as Expr expressions. Validation is off by default, according to its README. I did not test it for this post.

TruffleHog

  • Latest release: 3.99.7 (24 October 2026, 11:34 AM). It adds detectors (Fireworks AI, Infisical service tokens), an xAI analyser, a fix so an HTTP 422 from one provider is no longer treated as verified, and fixes for panics in the 7z and RAR archive handlers.
  • Just before: 3.99.6 (24 October 2026, 9:57 AM) says 3.99.5 was tagged but never published because its release build failed; 3.99.6 also bumps a compression library for a CVE. 3.99.4 (22 October 2026, 8:32 PM) marks results whose verification was skipped and hides sensitive data in debug logs. 3.99.0 (6 October 2026, 11:14 PM) added OAuth2 password grant support for custom verification requests. 3.98.0 (5 October 2026, 11:09 PM) fixed the GitHub Actions output advisory described in the security section.
  • Release rhythm: 44 stable releases between 1 January and 25 October 2026 by my count from the GitHub releases API. Pin a version in CI and update on purpose.
  • Scope, in the vendor’s words: the README says TruffleHog “classifies over 800 secret types” and can verify every type it classifies; it also says deeper permission analysis is available for “20 some” of the most commonly leaked credential types. These are the vendor’s own claims.
  • Licence: AGPL-3.0 (checked in the LICENSE file). The README says v3 was a complete rewrite in Go released under AGPL 3, that earlier work remains GPL 2.0 in history, and that a CLA is required for contributions. Truffle Security also sells an enterprise product.

detect-secrets

  • Latest release: 1.5.0 (6 May 2024, 11:35 PM), which added Python 3.10 to 3.12 support, OS-agnostic baselines, and detectors for IP addresses, GitLab, Telegram, PyPI and OpenAI tokens. PyPI also shows 1.5.0 as the latest.
  • Since then: commits on the master branch added Python 3.13 support (6 January 2025) and a secret count in the pre-commit hook output (December 2024), but no release has been cut. The most recent commit is a security review workflow added on 2 April 2026.
  • Plugins: the README lists 27 built-in plugins, including AWS, Azure storage, GitHub, GitLab, Slack, Stripe, private keys, JWTs, two high entropy string detectors and a keyword detector.
  • Licence: Apache 2.0 (checked in the LICENSE file).

Context: the forge and the SaaS options

  • GitHub push protection blocks pushes that contain supported secrets. GitHub’s documentation describes two kinds: push protection for users, on by default on GitHub.com and only for pushes to public repositories, and push protection for repositories, which requires GitHub Secret Protection and is off by default. By default, anyone with write access can bypass a block by giving a reason, which creates an alert.
  • GitLab pipeline secret detection runs in a CI job after a push and is available in all tiers; merge request widgets, the vulnerability report and custom rulesets are Ultimate features. GitLab’s docs say a historic scan must be run separately, once, to cover old commits.
  • GitGuardian ggshield is an MIT licensed CLI at 1.56.0 (15 October 2026, 7:14 PM), but it scans through GitGuardian’s API, so content goes to that service. Its README says only metadata is stored and files and secrets are not. That is a different trust model from the three local tools.

First question: what must never leave a laptop, and what must you find that already left?

Before picking a tool, separate the problem into two jobs, because they have different answers.

  1. Prevent. Stop a new secret from being committed or pushed. This runs on every commit or push, so it must be fast, quiet, and work without network access. False positives here cost developer goodwill quickly.
  2. Discover. Find secrets that are already in history, in other branches, in forks, in container images, in CI logs and in object storage. This runs rarely or on a schedule, can be slow, and benefits from knowing which findings are live.

Then write down where each check runs and who acts on a finding. For the Bhubaneswar company the list looks like this:

  • On the laptop: a pre-commit hook in every repository, installed by a bootstrap script, that blocks obvious secrets in staged changes.
  • On the merge request: a CI job that scans every commit in the merge request range and fails the pipeline. This is the real gate, because a hook can be skipped with git commit --no-verify and a laptop can lack the hook entirely.
  • Over the whole estate, once: a full-history scan of every repository on the GitLab server, including all branches and tags, to find what is already there. The 2023 key would have shown up here.
  • On a schedule: a nightly or weekly full-history scan that can verify findings, run by the security owner, with results sent to a private place, not a public issue.
  • After a finding: a written runbook that starts with rotation and ends, maybe, with history clean-up.

Two conclusions fall out. First, no single tool mode does all of this well: the fast local hook and the slow verifying sweep are different jobs. Second, the hardest part is not the scanner; it is the response. A scanner that finds a key nobody rotates is only a more detailed incident report.

What each one actually is

What a secret scanner does

A secret scanner reads text (files, Git patches, or other sources), looks for strings that look like credentials, and reports where they are. “Looks like” is the whole difficulty. Some secrets have a fixed shape: a GitHub classic personal access token starts with ghp_ followed by 36 characters, an AWS access key ID starts with AKIA. Others have no shape at all: DB_PASSWORD = "Odisha2026!prawnsCold" is just a string a person chose. Scanners combine three ideas to cope with this: patterns for known shapes, entropy for random-looking strings, and keywords for context such as variable names. TruffleHog and detect-secrets add a fourth: asking the provider whether the secret works.

Gitleaks

Gitleaks is a single Go binary with three scanning modes: git (which reads patches from git log -p, so it sees every added line in history), dir (files and folders), and stdin. Detection is driven by a TOML configuration with one table per rule: an id, a Go regular expression, an optional entropy threshold for the captured group, keywords used as a cheap pre-filter, optional path patterns, and allowlists. The default configuration ships inside the binary, and your own .gitleaks.toml can extend it with useDefault = true, disable rules, or add rules for your internal token formats.

Findings carry a fingerprint (commit, file, rule and line) which you can put in a .gitleaksignore file to silence one specific finding. Any earlier Gitleaks report can serve as a baseline, so only new findings are reported. A gitleaks:allow comment on a line skips it. Reports come out as JSON, CSV, JUnit, SARIF or a custom Go template. Gitleaks does not verify secrets against providers; it only finds them.

Two details are worth knowing. The README says decoding of base64, hex and percent-encoded text is off by default, but in my drill Gitleaks 8.30.1 found a GitHub token inside a base64 value without any flag, tagged decoded:base64 decode-depth:1, and gitleaks --help on 8.30.1 shows the decode depth default as 5. That matches the “0 to 5” changelog entry in 8.30.0. Archive scanning (--max-archive-depth) is still off by default. Trust --help on the binary you run over the README.

TruffleHog

TruffleHog is also a Go binary, but it is organised around sources and detectors. Sources include Git (local or remote), GitHub and GitLab (including issues and pull request comments for some sources), file systems, S3 and GCS buckets, Docker images, CI systems, Hugging Face, syslog and stdin. Each detector knows one credential type: a keyword list for a fast pre-check, a pattern for the shape, and in most cases a verification function that calls the provider. For AWS, the README says it calls GetCallerIdentity; for private keys, the README says a verified result means the key can be used live for SSH or TLS authentication, checked with a technique it calls Driftwood against public SSH keys of GitHub users and TLS certificates.

Each finding gets one of three statuses: verified (the provider accepted the credential), unverified (detected, but not confirmed, or verification was off) and unknown (verification was attempted and failed with an error, such as a network failure). The --results flag picks which ones to output, and --fail returns exit code 183 when results are found. A trufflehog:ignore comment on a line skips it. There is no baseline file in the Gitleaks or detect-secrets sense; you filter by result type, by detector, and with comments. Custom regex detectors (marked alpha) can also call your own verification webhook.

TruffleHog also has an analyze command that, for supported credential types, makes many API calls to learn what a key can access. That is useful during an incident, and it is a deliberate, noisy action against a live account, so treat it that way.

detect-secrets

detect-secrets is a Python package with three commands that are easy to mix up. detect-secrets scan scans files (by default only files tracked by Git, or everything with --all-files) and writes a JSON baseline of what it found. detect-secrets-hook checks given files, normally the staged ones in a pre-commit hook, and fails only for secrets that are not in the baseline. detect-secrets audit walks a person through the baseline to label each entry as a real secret or a false positive.

Its plugins follow the three strategies described in its README: regex rules for structured secrets, entropy detectors for random-looking strings (base64 with a default limit of 4.5, hex with 3.0), and a keyword detector that ignores the value and looks for names like password or secret being assigned a literal. Filters then remove likely false positives, and you can add your own filters in Python. Inline pragma: allowlist secret comments skip a line.

Two points surprise people. First, detect-secrets does not read Git history; it scans the files you give it. The README is explicit that it avoids digging through history by design. Second, its plugin documentation says that by default it “will attempt to verify all secrets”, for plugins that have a verify function (Slack, Stripe and AWS, among others), unless you pass -n or --no-verify.

Where the tools differ in philosophy

Gitleaks is a fast pattern engine for Git. TruffleHog is a discovery and verification engine for many sources. detect-secrets is a change control system: accept what exists, record it in a reviewed baseline, and stop it growing. Each philosophy is reasonable. Problems start when a team expects one to behave like another.

Side by side

Licence and governance

  • Gitleaks: MIT, a personal project of its author with community contributors and sponsors. Now in security-patch-only mode. The GitHub Action has its own non-MIT licence and needs a free key for organisations.
  • TruffleHog: AGPL-3.0, run by Truffle Security, a company with a commercial enterprise product. Contributions need a CLA. For internal use as a command line tool, AGPL rarely changes anything in practice, but if you plan to modify it and offer it to others over a network, read the licence with your legal adviser. This is not legal advice.
  • detect-secrets: Apache 2.0, maintained under Yelp’s GitHub organisation. Slow release cadence; no release since May 2024.

What gets scanned

  • Gitleaks: Git history (added lines in patches, through git log -p with any --log-opts you pass), directories, stdin, and archives if you turn that on. It does not fetch from forges or buckets.
  • TruffleHog: Git, GitHub and GitLab, file systems, S3, GCS, Docker images, CI systems and more. In my drill, a filesystem scan of a repository folder found secrets inside .git/objects, which means it read the compressed Git objects too. Useful for discovery; surprising if you expected “current files only”.
  • detect-secrets: files only. Tracked files by default, or all files with --all-files. No history.

Detection method

  • Gitleaks: regular expressions with keywords as a pre-filter, optional Shannon entropy thresholds per rule, path rules, stopwords and allowlists, composite rules since 8.28.0, and decoding of encoded text.
  • TruffleHog: keyword pre-filter and per-detector patterns, decoders for encoded data, optional entropy filtering of unverified results (--filter-entropy), and verification.
  • detect-secrets: regex plugins, two entropy plugins, a keyword plugin, heuristic filters, an optional word list and an optional gibberish model.

Human-chosen passwords

  • In my drill, a line DB_PASSWORD = "Odisha2026!prawnsCold" was not reported by Gitleaks 8.30.1 with its default rules, and TruffleHog 3.99.7 reported only the AWS and GitHub values in the same file. detect-secrets 1.5.0 flagged it as a “Secret Keyword”. A later staged file with SMS_API_PASSWORD = "Bhubaneswar@123" was flagged by detect-secrets as a keyword, while Gitleaks and TruffleHog reported only the Slack webhook next to it.
  • This is the main practical reason to keep detect-secrets in mind: its keyword detector catches the weak, typed passwords that pattern tools skip. The price is more false positives.

Verification of live credentials

  • Gitleaks: none. Every finding is a candidate.
  • TruffleHog: on by default; --no-verification turns it off; --results filters by status. Custom verifier endpoints are supported.
  • detect-secrets: on by default for plugins that implement it; -n or --no-verify turns it off; --only-verified shows only verified ones.
  • Betterleaks: off by default; -v turns validation on.

Baselines and allowlists

  • Gitleaks: any earlier report as a baseline (--baseline-path), fingerprints in .gitleaksignore, gitleaks:allow comments, and allowlists in the config by commit, path, regex or stopword.
  • TruffleHog: no baseline file; filter by result type, include or exclude detectors, trufflehog:ignore comments (and --no-ignore-tag to review them).
  • detect-secrets: the baseline is central, lives in the repository as .secrets.baseline, and is audited by people; pragma: allowlist secret comments; exclusion regexes for lines, files and secrets.

Pre-commit and CI integration

  • Gitleaks: an official pre-commit hook that runs gitleaks git --pre-commit --redact --staged --verbose, Docker images on Docker Hub and GHCR, and SARIF output for code scanning views.
  • TruffleHog: documented hooks for pre-commit, Husky and Git’s core.hooksPath. Setting TRUFFLEHOG_PRE_COMMIT=1 applies its pre-commit settings; in my drill that mode exited with 183 on a staged fake Slack webhook even without --fail. Official GitHub Action and a GitLab CI example in the README.
  • detect-secrets: an official pre-commit hook that takes --baseline; needs a Python environment.

How detection actually works, and where it goes wrong

Patterns

A pattern rule is the most precise tool when a provider uses a fixed prefix and length. Many providers now design tokens to be detectable: a recognisable prefix, sometimes a checksum. Patterns fail in two ways. They miss secrets with no shape (passwords, internal tokens without a prefix), and they fire on test fixtures, documentation examples and look-alike strings. Keywords make patterns cheap: a rule only runs its expensive regex when a literal such as ghp_ appears in the chunk.

Entropy

Shannon entropy measures how unpredictable a string’s characters are. Random keys score high; English words score low. Entropy is good at catching unknown random tokens and bad at everything else: hashes, UUIDs, checksums in lock files and base64-encoded images all score high too. That is why entropy is usually a filter on a pattern’s captured group (Gitleaks’ entropy field), or a tunable threshold (detect-secrets’ --base64-limit and --hex-limit), rather than a detector on its own. In my drill, detect-secrets reported a base64 value only as a “Base64 High Entropy String” and did not see the GitHub token inside it; Gitleaks and TruffleHog decoded it and named it.

Keywords and context

A keyword detector looks at the name, not the value: password = "...", api_key: .... It finds what patterns cannot, and it produces the most noise, because code is full of words like password that are not secrets (form field names, test helpers, documentation). detect-secrets mitigates this with filters; you will still spend time on audits.

Baselines

A baseline says “these findings already existed and were reviewed; only tell me about new ones”. It is how you switch on a scanner in a ten-year-old repository without blocking every merge on day one. The danger is that the baseline becomes a graveyard of real secrets that nobody rotated. A baseline entry should mean “reviewed and harmless” or “real, rotated, and recorded”, never “real, and we will get to it”.

Verification

Verification answers the question a security team actually cares about: does this key still work? A verified AWS key in a public repository is an emergency; a dead one is a clean-up task. That is a big reduction in triage work, and it is the main reason people choose TruffleHog.

It has consequences you must accept on purpose:

  • The secret is sent to the provider. To check a key, the scanner uses it against the provider’s API. For a key that was only ever in your private repository, verification is the first time it travels outside your network. The provider sees the request; the provider is also the party that issued the key, so this is usually acceptable, but it should be a decision, not a default you never noticed.
  • It needs network egress. A CI runner without internet access cannot verify. In my drill, with networking removed, TruffleHog tried DNS lookups for api.github.com and sts.us-east-1.amazonaws.com, failed, and marked both results with a verification error. With --results=verified --fail, the exit code was 0, a clean pass. With --results=verified,unknown --fail, it was 183. If you filter to verified results only, a firewall change can silently turn your gate off.
  • Verified means “works now”, not “was never dangerous”. An unverified result may be a key for a provider without a detector, a key for an internal service, or a key that was revoked last week after being misused. Do not close unverified findings automatically.
  • It touches live accounts. Verification calls appear in the provider’s logs under the leaked identity. During an incident, tell the responders that a scanner made those calls, so nobody mistakes them for the attacker.
  • It can be abused if the scanner is careless. TruffleHog’s GHSA-3r74-v83p-f4f4 (CVE-2024-43379, low severity, fixed in 3.81.9) was a blind SSRF in some detectors: crafted data could make a detector send a request to an endpoint of the attacker’s choosing.

Worked example for the Bhubaneswar scenario

The plan has five parts: a local hook, a merge request gate, a one-time sweep, a scheduled verifying scan, and a response runbook. Versions are pinned to what was current on 25 October 2026.

Step 1: a pre-commit hook in every repository

Use the pre-commit framework so the hook is versioned in the repository. Gitleaks goes into every repository. detect-secrets goes into the Django repositories only, after a two-week trial, with verification turned off because a laptop hook should not send anything over the network.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks
  # Django repositories only, after the trial
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.5.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline', '--no-verify']
        exclude: package-lock\.json

The bootstrap script that every developer runs after cloning includes pre-commit install. Accept that some people will skip it, and some will bypass it with git commit --no-verify or SKIP=gitleaks. The hook is a seatbelt reminder, not the gate.

Step 2: a gate on every merge request

GitLab CE’s included template is the easiest start:

include:
  - template: Jobs/Secret-Detection.gitlab-ci.yml

GitLab’s docs say that on merge request pipelines it scans all commits on the branch, and that results in the merge request widget need Ultimate; on CE you get the JSON report artifact. If you want a hard failure and control over the Gitleaks version and config, run Gitleaks directly:

secret-scan:
  stage: test
  image:
    name: ghcr.io/gitleaks/gitleaks:v8.30.1
    entrypoint: [""]
  variables:
    GIT_DEPTH: 0
  script:
    - gitleaks git . --redact --no-banner
        --log-opts="$CI_MERGE_REQUEST_DIFF_BASE_SHA..$CI_COMMIT_SHA"
        --report-format sarif --report-path gitleaks.sarif
  artifacts:
    when: always
    paths: [gitleaks.sarif]
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

Three choices matter here. --log-opts limits the scan to the commits in the merge request, so a secret added in one commit and removed in the next still fails the pipeline. --redact keeps the secret out of the job log, because CI logs are read by many more people than the code. And the job fails closed: a non-zero exit stops the merge.

For the company’s own API tokens, add a rule. Give internal tokens a recognisable prefix first; that one design change makes every scanner better.

# .gitleaks.toml
[extend]
useDefault = true

[[rules]]
id = "coldchain-api-token"
description = "Internal cold chain API token"
regex = '''\b(ccx_live_[A-Za-z0-9]{32})\b'''
keywords = ["ccx_live_"]

Step 3: a one-time sweep of all history

Before the gate goes live, find what is already there. Clone every repository as a mirror (all branches and tags) onto one machine and scan history. Gitleaks is fine for this; TruffleHog is better if you want to know which findings are live.

# one repository, all refs, Gitleaks
git clone --mirror https://git.example.internal/gateway/sensor-gateway.git
gitleaks git sensor-gateway.git --redact \
  --log-opts="--all" \
  --report-format json --report-path sweep-sensor-gateway.json

# the same with TruffleHog, from a host allowed to reach provider APIs
trufflehog git file://sensor-gateway.git \
  --results=verified,unknown,unverified --json --no-update \
  > sweep-sensor-gateway.trufflehog.jsonl

Keep the output files somewhere private and short-lived. A sweep report is a list of secrets with their locations: exactly what an attacker wants. Sort the results into three piles: verified (rotate today), unknown or unverified for a provider you use (check by hand, rotate if in doubt), and clearly harmless (test fixtures, documentation examples). For a repository that people still work in, the harmless pile becomes the starting baseline:

# Gitleaks: a reviewed report becomes the baseline
gitleaks git . --report-path .gitleaks-baseline.json
gitleaks git . --baseline-path .gitleaks-baseline.json

# detect-secrets: create, then audit by hand
detect-secrets scan --no-verify > .secrets.baseline
detect-secrets audit .secrets.baseline

In my drill, re-running Gitleaks with its own earlier report as the baseline returned “no leaks found” and exit code 0, as expected. Do not baseline a real secret that you have not rotated. Rotate it, then baseline the dead value with a note, or rewrite it out of history (step 5).

Step 4: a scheduled verifying scan

Run TruffleHog nightly from one dedicated runner that is allowed outbound HTTPS, with the results going to the security owner only:

trufflehog git file://. --results=verified,unknown --fail --json --no-update

Include unknown, as the drill showed, so a network problem fails loudly instead of passing quietly. Pin the TruffleHog version in the runner image and update it monthly after reading the release notes, rather than pulling latest every night. If the security owner is uneasy about sending discovered keys to providers from CI, run with --no-verification and verify by hand only the findings that look real. That is slower but fully under your control.

Step 5: when a real key is found

This is the runbook the Bhubaneswar team wrote after August. The order matters.

  1. Revoke or rotate the credential at the provider. Do this first, before any Git work. Create the replacement in OpenBao (or your secrets manager) and update the consuming services. The Vault and OpenBao post covers dynamic credentials that make this step routine, and the Kubernetes secrets delivery post covers getting the new value into pods without a YAML file in Git.
  2. Check the provider’s logs for use of the old credential since the commit date, not just since today. If you ran a verifying scanner or trufflehog analyze, separate those calls from real misuse.
  3. Find every copy. Search all repositories, branches, forks, mirrors, CI job logs, container images and wikis for the same value. A key committed once tends to be copied.
  4. Fix the code so the value comes from the environment or a secrets manager, and add a rule or test so that the same mistake fails CI.
  5. Decide whether to rewrite history. After rotation, the old value is dead; rewriting is about hygiene and about not training people to ignore findings. If you do it, use git filter-repo with --replace-text on a fresh mirror clone, coordinate a force push with everyone, and ask every developer to re-clone. GitLab’s docs note that purging history does not remove the secret from existing forks or clones, and GitLab has a separate “redact text from repository” feature. Rewriting a shared main branch is disruptive; for a dead key in an old commit, recording it in the baseline with a note is often the better trade.
  6. Write it down. Date, credential, scope, how it was found, when it was rotated, and what changed. The next audit will ask.

The expressions file for git filter-repo --replace-text takes one literal value per line (with optional regex: or glob: prefixes), and ==> sets the replacement text instead of the default ***REMOVED***:

<old-key-value-here>==>REMOVED_ROTATED_2026_10

What changes for the intern

Nothing in this plan stops a person from copying a repository to a public account. What changes is that the 2023 key would have been found by the sweep and rotated long before, so the copy would have carried a dead value. Add a written rule that company code is not pushed to personal accounts, and make the onboarding talk say why. Tools reduce the damage; people decide whether there is any.

Security and privacy notes

The scanner sees everything it scans

A secret scanner reads your code and history and writes out the most sensitive lines it finds. Treat its reports, artifacts and logs as secrets. Use redaction in CI (--redact in Gitleaks; TruffleHog 3.99.4 added hiding of sensitive data in debug logs). Keep sweep reports out of shared drives and ticket systems, or store only fingerprints and locations, not values.

Verification sends the secret out

As described above, TruffleHog and detect-secrets both verify by default. For detect-secrets this surprises people most, because it is usually thought of as an offline pre-commit tool. On laptops and in pre-commit hooks, pass --no-verify to detect-secrets and use Gitleaks or TruffleHog with --no-verification. Do verification in one controlled place, on purpose. SaaS scanners such as ggshield send the scanned content itself to the vendor’s API for detection; that is a separate decision for your security owner, whatever the vendor’s storage promises.

Scanning untrusted repositories

A scanner that clones or reads a repository can be attacked through that repository. The NVD record for CVE-2025-41390 (published 20 October 2025) describes an arbitrary code execution issue in the Git functionality of TruffleHog 3.90.2, triggered by a specially crafted repository, reported through Cisco Talos. TruffleHog’s README now says it clones local Git repositories to a temporary directory before scanning to guard against malicious Git configs, and that --trust-local-git-config skips this only for trusted repositories. Do not pass that flag when scanning repositories you did not create, such as contractor deliveries or acquired code. Run sweeps of outside code in a throwaway container.

Notable advisories

  • TruffleHog GHSA-fg8w-m62h-jvvw (medium, CVSS 3.1 score 4.7, published 9 October 2026, 1:21 AM): in versions 3.31.0 to 3.97.9, the --github-actions output mode wrote file paths into workflow commands without escaping. A file path with a newline could inject workflow commands, for example to forge annotations or hide real findings in the workflow UI. The official GitHub Action always passes that flag. Fixed in 3.98.0. Anyone who can get a commit into the scanned range, such as a pull request author, could use it.
  • TruffleHog GHSA-3r74-v83p-f4f4 (CVE-2024-43379, low): blind SSRF in some detectors, fixed in 3.81.9.
  • TruffleHog CVE-2025-41390: described above; the README’s local clone behaviour is the documented guard.
  • Gitleaks and detect-secrets: the GitHub repository advisory lists for both were empty when I checked on 25 October 2026, and the GitHub advisory database returned nothing for the Gitleaks Go module or the detect-secrets package. Absence of advisories is not proof of absence of bugs, but I found nothing to report.

Habits that matter more than the tool

  • Give your own tokens a prefix and a fixed length, so scanners can find them precisely.
  • Prefer short-lived credentials (dynamic database users, OIDC federation from CI to the cloud) so that a leaked value expires on its own.
  • Never put secrets in issues, merge request comments or CI variables printed with echo. Scanners that read forge metadata exist because people do this.
  • Keep test fixtures obviously fake (for example EXAMPLE, or values that fail a provider checksum) and in a known path you can allowlist by path, not by value.
  • Scan container images too. A secret baked into an image layer travels further than one in Git. The vulnerability scanners post covered Trivy, which can also scan for secrets, and the image signing post covered proving where an image came from.

Operational costs to budget for

  • Triage time. The first full-history sweep of a few dozen repositories produces a pile of findings, most of them harmless. Plan a few days of a security-minded engineer’s time, and a clear owner for each repository.
  • Baseline upkeep. Baselines go stale as files move. Gitleaks fingerprints include the commit, file and line; detect-secrets baselines are rewritten by re-scanning. Someone must review baseline changes in merge requests, or they become a quiet way to accept secrets.
  • False positive tuning. Allowlists by path for fixtures, stopwords for known placeholders, and custom rules for internal tokens. Each one is code that needs review; an allowlist regex that is too wide hides real secrets.
  • Verification egress. A runner with outbound internet access, a firewall rule set that allows provider APIs, and awareness of provider rate limits during large sweeps.
  • Version churn. TruffleHog’s release pace means frequent updates if you want new detectors. Gitleaks and detect-secrets need almost no updates, which also means their rule sets age.
  • Rotation work. The real cost of a finding is rotating the key: finding every consumer, deploying the change, and confirming nothing broke. Systems that already pull secrets from OpenBao or a cloud secrets manager pay this cost in minutes; systems with keys in config files pay it in days.
  • Forge features. GitHub Secret Protection and GitLab Ultimate add push-time blocking, dashboards and partner revocation. Compare that licence cost with the engineering time to run the open source tools well.

Decision guide

Lean towards Gitleaks if

  • You want one small binary for pre-commit hooks and CI gates, with no network access needed.
  • You value a readable rule format and want to add rules for internal tokens yourself.
  • You run GitLab and want the same engine as its free pipeline secret detection, with your own pinned version and config.
  • You accept that Gitleaks will get security fixes only, and you will review Betterleaks once its v2 line is stable.

Lean towards TruffleHog if

  • You need to know which findings are live, to prioritise a large backlog or respond to an incident.
  • You want to scan beyond Git: buckets, container images, forge comments and CI systems.
  • You can give one runner controlled outbound access and accept that verification sends keys to providers.
  • AGPL-3.0 is acceptable for how you use it, and you can keep up with a weekly release pace by pinning and updating on purpose.

Lean towards detect-secrets if

  • Your problem is weak, human-chosen passwords in config files and Python code, which keyword detection catches and pattern tools miss.
  • You want an audited baseline in the repository as a record of accepted findings.
  • You can live with a tool whose last release was in May 2024, and you remember to pass --no-verify where network calls are unwanted.

Consider Betterleaks if

  • You are starting fresh, like the Gitleaks model, and want validation and analysis without AGPL. Wait for a stable v2.0.0, read the migration guide, and run a trial against your own repositories before switching. I have not tested it.

Use the forge’s built-in features as an extra layer if

  • You are on GitHub with Secret Protection, or GitLab Ultimate, and want server-side push blocking. Server-side checks catch the pushes that skipped the laptop hook. They complement a scanner you control; they do not replace a full-history sweep of what is already there.

Combine them when

  • You need both jobs. A common and sensible split: Gitleaks for prevention (hook and gate), TruffleHog for discovery (sweep and schedule), and detect-secrets only where its keyword catches pay for their noise.

Checklist

  1. Every repository has a pre-commit hook with a pinned scanner version, installed by the bootstrap script.
  2. Every merge request runs a secret scan over all commits in its range, with redaction, and fails closed.
  3. A full-history sweep across all branches and tags of every repository has been run once, and every finding has an owner and a status.
  4. A scheduled scan runs with --results=verified,unknown (or equivalent), so verification failures do not pass silently.
  5. Verification is turned off on laptops and in hooks, and turned on only where the security owner has decided it should run.
  6. Internal tokens have a prefix and a custom scanner rule.
  7. Baselines contain only reviewed harmless values or rotated dead values, and baseline changes are reviewed in merge requests.
  8. Scan reports and CI artifacts are treated as secrets and expire.
  9. A written runbook puts revocation and log review before any history rewrite.
  10. Repositories from outside the company are scanned in a throwaway container, without trusting their local Git config.
  11. Scanner versions are reviewed monthly, with advisories read, especially for TruffleHog’s Action and output modes.

Common mistakes

  1. Scanning only the working tree and calling the repository clean, while the secret sits two years back in history.
  2. Rewriting history first and rotating later, or never. The key stays valid in every clone made before the rewrite.
  3. Relying on a pre-commit hook as the only control. --no-verify exists, and new laptops miss hooks.
  4. Filtering CI to verified results only, so a blocked network or a provider outage turns every scan green.
  5. Running detect-secrets in a hook without --no-verify and not realising it may send found secrets to provider APIs.
  6. Putting real, unrotated secrets into a baseline to make the pipeline pass.
  7. Writing an allowlist regex so broad (for example by a common word) that real secrets in that file type are hidden.
  8. Printing full findings in CI logs that the whole company can read.
  9. Assuming Gitleaks will learn next year’s token formats. It is feature complete; new formats need your own rules or another tool.
  10. Scanning a contractor’s repository with --trust-local-git-config on a shared build machine.

Drill note

A small drill was run on the box on 25 October 2026, between about 10:22 PM and 10:25 PM IST. I downloaded Gitleaks 8.30.1 and TruffleHog 3.99.7 release binaries (checksums verified against the release checksum files) and installed detect-secrets 1.5.0 from PyPI in a virtual environment. Every scan ran inside a Linux network namespace with no network access at all, and every “secret” was a fake value generated at random for the drill: a GitHub-style token, an AWS-style key pair, a Slack-style webhook URL and two typed passwords. Nothing was sent to any provider.

What it showed, in brief: a secret committed and then deleted was invisible to directory scans and found by history scans; Gitleaks’ own report worked as a baseline; Gitleaks 8.30.1 decodes base64 by default (decode depth 5 in --help), which the README does not yet say; TruffleHog’s filesystem mode read Git objects inside .git; detect-secrets was the only one of the three to flag typed passwords; and with verification failing for lack of network, TruffleHog’s --results=verified --fail exited 0 while --results=verified,unknown --fail exited 183. All three hooks blocked a staged fake Slack webhook. This was a functional check on a tiny repository, not a benchmark, and it says nothing about speed or detection rates at scale. Before you roll out a scanner, run your own trial on two or three real repositories and count the findings your team has to triage.

Unlearn

  • Unlearn that deleting a file deletes the secret. Git keeps every version until you rewrite history, and clones keep it after that.
  • Unlearn that a clean scan of the working tree means a clean repository.
  • Unlearn that a pre-commit hook is a control. It is a helpful reminder that people can skip.
  • Unlearn that secret scanners are offline tools. Two of the three verify over the network by default.
  • Unlearn that “verified: false” means safe. It means “not confirmed now”.

Re-learn

  • Re-learn how Git stores history: commits, trees, blobs, and why a deleted line still exists in old objects.
  • Re-learn the two jobs, prevention and discovery, and give each its own tool mode and owner.
  • Re-learn entropy, patterns and keywords well enough to read a finding and judge it in a minute.
  • Re-learn credential hygiene at the source: short-lived credentials, prefixed internal tokens, and secrets fetched at runtime from a manager.
  • Re-learn incident order: revoke, review logs, find copies, fix code, then clean history.

Closing

The Bhubaneswar incident was not caused by a missing scanner; it was caused by a key that lived for two years after everyone thought it was gone. Gitleaks 8.30.1 is a fast, readable, MIT licensed pattern engine for hooks and gates, now finished and in security-patch-only mode, with its author’s attention on Betterleaks. TruffleHog 3.99.7 is the strongest discovery tool of the three, scanning many sources and telling you which keys are live, at the cost of AGPL, rapid releases and sending secrets to providers when it verifies. detect-secrets 1.5.0 brings an audited baseline and keyword detection that catches typed passwords, from a project that has not released in a year and a half and that also verifies by default. For the Bhubaneswar company, the sensible path is Gitleaks in every pre-commit hook and merge request pipeline, one TruffleHog sweep of every repository’s full history followed by a nightly scan from a single runner with controlled egress, detect-secrets only in the Django repositories if its keyword catches earn their keep, and a runbook that always starts with rotation. Then the next key that lands in a commit is a ten-minute fix, not a week of forensics.

Sources

Gitleaks and Betterleaks

TruffleHog

detect-secrets

Forge and SaaS context

Security records

Internal

comments powered by Disqus

Releted Posts

Container image signing in 2026: revisit cosign, Notation and in-toto attestations before you trust another image tag

Picture a software company in Rajkot that builds production planning and job work software for foundries, forging units and engineering workshops across Saurashtra (an invented example, not a real client).

Read more

Mesh VPNs in 2026: revisit Headscale, NetBird and plain WireGuard before you open another SSH port to the internet

Take a pump manufacturer in Coimbatore that makes agricultural and domestic pumps at two plants, one near the city and a smaller one at Kaniyur (an invented example, not a real client).

Read more

Self-hosted Git forges in 2026: revisit Gitea, Forgejo and GitLab CE before you move another repository

Picture a software company in Kochi that builds billing and export-documentation software for seafood exporters in Aroor, Alappuzha, Kollam and Thoothukudi (an invented example, not a real client).

Read more