Credentials committed to Git stay readable in every clone, fork and history rewrite that missed them, long after someone deletes the line from the latest commit. Secret scanning finds API keys, tokens, private keys and connection strings in repositories and blocks new ones before they land, but most programmes scan only one layer and treat a closed alert as a fixed problem. This guide covers where to scan (IDE, pre-commit, push, CI, history and the places code travels to), how the main detection approaches differ, how to tune verification and baselines, and how to run remediation so a leaked secret is actually dead. It is written for application security, platform and identity teams who own developer tooling and the non-human identities those secrets belong to.
Key takeaways
- Revoke before you rewrite. A leaked secret is fixed when the credential no longer works; removing it from Git history is secondary and never sufficient on its own.
- Scan in layers. Pre-commit hooks can be skipped, push protection only covers supported patterns, and CI scanning sees code after it has left the laptop, so each layer catches what the others miss.
- Verification changes triage. Tools that test whether a key is live (TruffleHog verification, GitHub validity checks) let you fix active credentials first, but an unverified result is not proof the secret is dead.
- Code goes beyond the repository. Container images, build logs, issue comments, wikis and exposed
.gitfolders on web servers all carry secrets that repository scanning alone does not see. - Every finding is an identity problem. Each key belongs to a service account, app or workload; remediation needs an owner and a rotation path, which is why scanning belongs alongside NHI governance.
What git secret scanning finds and why history matters
Git stores every version of every file. A key added in one commit and deleted in the next is still present in the object store, in every existing clone and in any fork made in between. Scanners therefore work on two targets: the change being made now (a diff, a push, a merge request) and the full history of all branches.
- Provider-specific secrets: tokens with a recognisable prefix or structure, such as cloud access keys, Git platform tokens, payment API keys and chat bot tokens. These give high precision and are what push protection usually blocks.
- Generic secrets: private keys (RSA, EC, OpenSSH, PGP), database connection strings with embedded credentials and HTTP Basic or Bearer authentication headers. GitHub lists these as non-provider patterns such as
generic_private_key,postgres_connection_stringandhttp_bearer_authentication_header, with no validity checks available. - Unstructured secrets: human-chosen passwords in configuration files. GitHub uses an AI-detected
passwordpattern for these and states that push protection and validity checks are not supported for it. Regex and entropy tools struggle here too. - Custom secrets: internal token formats, signing keys and service passwords that only your organisation recognises. These need custom patterns written and tested by you.
Attackers search public code for exactly these patterns (MITRE ATT&CK T1593.003 Search Open Websites/Domains: Code Repositories) and harvest credentials from files once inside (T1552.001 Credentials In Files, T1552.004 Private Keys). The GitLab public repositories study shows the scale: a verified TruffleHog scan of about 5.6 million public projects surfaced 17,430 live secrets, the oldest committed in 2009.
Where to scan: the layers and what each catches
| Layer | When it runs | Strengths | Gaps |
|---|---|---|---|
| IDE scanning | As the developer types or saves | Earliest feedback; nothing has been committed | Depends on extension adoption; no central enforcement |
| Pre-commit hook | Before the commit is created locally | Stops secrets entering history at all | Opt-in per clone; skipped with git commit --no-verify or tool-specific skip variables |
| Push protection (server side) | When the push reaches the Git platform | Central, cannot be skipped without a recorded bypass | Limited to supported, high-precision patterns; GitHub supports only the most recent token versions |
| CI/CD pipeline scanning | On merge or pull request pipelines | Covers repositories without push protection; can fail the build; outputs SARIF | Secret is already pushed; must scan the diff range, not just the head |
| Full history and continuous scanning | Scheduled, and when new detectors are added | Finds old leaks; GitHub rescans history when it adds new secret types | Large backlog; needs a triage process |
| Non-code surfaces | Issues, pull request comments, wikis, discussions | GitHub scans issues, PRs, discussions, wikis and secret gists; GitLab has client-side detection for issue and merge request text | Chat, ticketing and documentation tools outside the Git platform need separate tooling |
| Build outputs | Container images, packages, artefacts | Catches secrets copied in at build time | Usually a separate scanner and owner |
Platform features differ in naming but follow the same idea. GitHub offers secret scanning, push protection for repositories and users, custom patterns, generic patterns, validity checks and a partner programme that notifies the issuer when a partner token is found in public code. On private and internal organisation repositories these need GitHub Secret Protection; public repositories are scanned at no cost. GitLab provides secret push protection, pipeline secret detection and client-side secret detection, and records findings in the vulnerability report. See the GitHub and GitHub Actions Security Guide.
Detection approaches compared
| Approach | How it works | Precision | Best use |
|---|---|---|---|
| Pattern (regex) rules | Match known token formats and prefixes | High for structured tokens | Push protection, blocking in CI |
| Entropy detection | Flags random-looking strings above a threshold | Low to medium; noisy on hashes and test data | Finding unknown key formats during history audits |
| Keyword and context rules | Look for names such as password= near a value | Medium | Configuration files, generic credentials |
| Verified scanning | Calls the issuing API to test whether the credential works | Very high for live findings | Prioritising remediation; proving impact |
| AI or model-based detection | Classifies unstructured strings such as passwords | Varies; vendor-specific | Passwords that regex cannot express |
| Decoding and archive traversal | Decodes base64, hex or percent encoding and opens archives before matching | Depends on underlying rules | Secrets hidden in encoded config or zipped fixtures |
Open-source tools and their controls
- Gitleaks: commands
git(usesgit log -p, narrowed with--log-opts),dirandstdin; the olderdetectandprotectcommands were deprecated in v8.19.0. Rules live in.gitleaks.tomlwith[extend] useDefault = trueto inherit defaults, allowlists by commit, path, regex or stopword, a--baseline-pathto report only new findings,--max-decode-depthand--max-archive-depth(both off by default) and--redactto keep secrets out of logs. Reports are JSON, CSV, JUnit or SARIF. Its README now says the project is feature complete and will receive security patches only, which matters for long-term tool choice. - TruffleHog: classifies over 800 secret types with over 700 detectors that support verification. Results are
verified,unverifiedorunknown(verification attempted but failed), filtered with--results. Sources go well past Git:github,gitlab,docker,s3,gcs,filesystem,postman,jenkins,elasticsearch,huggingface,circleciandtravisci. Theanalyzecommand reports what a credential can access for around 20 common types. - detect-secrets: built around a
.secrets.baselinefile that accepts existing findings so the hook blocks only new ones, withdetect-secrets auditfor labelling true and false positives andpragma: allowlist secretfor inline exceptions. Its documentation notes it does not catch multi-line secrets.
OWASP's Secrets Management Cheat Sheet recommends running more than one detection tool and de-duplicating results to expose coverage gaps, and agreeing standard test secrets so fixtures stop generating noise.
How to implement: a phased rollout
- Inventory and baseline (weeks 1 to 2): list every Git organisation, group and self-hosted server, including mirrors and archived repositories. Run a full-history scan with verification enabled and store the results as a baseline. On GitHub, the free organisation assessment (Security and quality, Assessments, Scan your organization) gives a first view.
- Revoke live findings first: sort the baseline by verified or active status and rotate those credentials before touching anything else. Assign each one to an owning team; unowned keys go to the platform team by default.
- Enable server-side push protection in audit posture: turn it on for a pilot group of repositories, watch bypass volume and reasons for two to four weeks, then extend organisation-wide. Keep the default list of supported patterns before adding custom ones.
- Add CI scanning for diffs: scan only the commit range of each pull request, for example TruffleHog with
--since-commit main --branch <feature> --results=verified,unknown --fail, or Gitleaks with--baseline-path. Upload SARIF where your platform supports it so findings appear next to the code. - Distribute pre-commit hooks: ship a shared
.pre-commit-config.yaml(hook idsgitleaksordetect-secrets) through repository templates and developer onboarding. Treat hooks as a convenience for developers, not as the control you rely on. - Add custom patterns: write rules for internal token formats, test them against the baseline and against known-good fixtures, then promote them to blocking only once the false positive rate is acceptable.
- Extend beyond Git: scan container registries, artefact stores, CI logs and the wiki and ticketing systems that sit next to the code. Exposed
.gitdirectories on web servers should be checked from outside. - Roll back safely: if push protection blocks a release-critical pattern wrongly, use delegated bypass for named people rather than disabling the control, and fix the pattern. Custom patterns can be disabled individually without affecting the default set.
Push protection bypasses and delegated bypass
On GitHub, anyone with write access can bypass repository push protection by default if they give a reason: "it's used in tests" and "it's a false positive" close the resulting alert, while "I'll fix it later" leaves it open. Every bypass creates an alert, an audit log entry and an email to people watching the repository. Delegated bypass lets you restrict this so that only named reviewers can bypass or approve bypass requests, and exemptions let specific actors push without triggering the check.
- Small organisations: default bypass with weekly review of bypass alerts is usually workable; the audit trail is the control.
- Large or regulated organisations: use delegated bypass with a security reviewer group, and require a ticket reference in the bypass comment.
- Watch the "used in tests" reason: it closes the alert automatically. Sample these regularly, because real credentials are often pasted into test fixtures.
- User push protection is not organisation coverage: GitHub enables it by default for personal accounts pushing to public repositories, but it does not raise bypass alerts unless repository push protection is also on. A developer can also switch it off, as the CISA Private-CISA GitHub leak showed when commit history revealed the default protection had been disabled.
Remediating a leaked secret
- Revoke or rotate the credential at the issuer. GitHub's own guidance says that once the secret can no longer be used, rewriting history may not be necessary. See the Secrets Rotation Runbook Template.
- Check for use during the exposure window in the issuer's logs (cloud audit trails, API access logs). OWASP lists who had access, when it was used and when it was last rotated as the lifecycle information responders need.
- Replace the secret with a reference to a secrets manager or, better, a short-lived federated credential so the code no longer needs a static key. See the Secrets Management Guide.
- Decide whether to rewrite history. For sensitive data that cannot be rotated, GitHub documents
git-filter-repo --sensitive-data-removal(version 2.47 or later) followed by a mirror force push. The side effects are real: commit hashes change, signatures are removed, open pull requests lose their diffs, forks keep the data, and anyone who merges an old clone brings it back. Cached views andrefs/pull/references need GitHub Support. - Close the alert with the right resolution (for GitHub,
revokedrather thanwont_fix) so metrics reflect reality, and record the root cause.
Where identity fits
Each finding is the credential of a non-human identity: a cloud service account, a CI deploy key, a GitHub App, a bot token. Scanning tells you a secret exists; only identity governance tells you who owns it, what it can reach and whether it should exist at all. Map each verified finding to its identity, check its permissions and retire identities that no longer have a purpose. The leaked GitHub App private keys research found 474 of 4,802 leaked keys still authenticated, because App private keys do not expire and many of the Apps were small, internal or abandoned. A scanner can flag such a key; only an owner can delete it. See the CI/CD Pipeline Identity Security Guide.
Detection and measurement
- What to log: push protection blocks and bypasses with reasons, alert state changes, validity status changes, and attempts to use revoked credentials at the issuer. OWASP advises never logging plaintext secrets, so use redaction options such as Gitleaks
--redact. - What to alert on: any new verified or active finding, any bypass on a default-branch protected repository, and any use of a credential from an unexpected source after a leak.
| Metric | Definition | Healthy direction |
|---|---|---|
| Coverage | Share of active repositories with push protection and CI scanning enabled | Towards 100% |
| Time to revoke | Median hours from first detection to credential revoked (not alert closed) | Falling; under 24 hours for verified findings |
| Live backlog | Open findings with verified or active status | Falling to near zero |
| Bypass rate | Bypasses as a share of push protection blocks, by reason | Low and stable, with "fix later" bypasses closed promptly |
| Recurrence | Repositories or teams with repeat leaks of the same secret type in 90 days | Falling |
| False positive rate | Findings resolved as false positive per rule | Low enough that developers trust blocks |
For deeper programme measurement, see the Secret Sprawl Measurement Guide.
Common failure patterns
- Deleting instead of revoking: developers remove the key from the file and consider it fixed. In the Docker Hub secrets leak, about a quarter of developers removed exposed secrets within a day or two, but in most cases the credential was not revoked.
- Scanning the head commit only: CI jobs that check the current tree miss secrets added and removed within the same branch, which remain in history once merged.
- Trusting pre-commit hooks as a control: they are local, optional and easy to skip. Server-side push protection and CI must back them up.
- Ignoring where the repository is served: deploying a working copy to a web server can publish the whole
.gitfolder. The misconfigured Git servers study counted 4,964,815 IP addresses serving.gitdirectories, with 252,733.git/configfiles holding deployment credentials, by the researchers' own unverified count. - Treating unverified as safe: verification failures can mean a network error (TruffleHog's
unknown), and generic secrets such as private keys and connection strings often have no validity check at all. - Allowlisting by path wholesale: excluding
test/ordocs/folders hides real credentials copied into fixtures and examples.
Framework mapping
| Framework | Reference | How scanning supports it |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-5(7) No Embedded Unencrypted Static Authenticators | Detects and blocks static credentials in code |
| NIST SP 800-53 Rev. 5 | SA-11 Developer Testing and Evaluation; SA-15 Development Process, Standards, and Tools | Scanning as part of developer testing and toolchain |
| NIST SP 800-53 Rev. 5 | RA-5 Vulnerability Monitoring and Scanning; IR-4 Incident Handling | Continuous history scanning; leak response |
| NIST CSF 2.0 | PR.PS-06; PR.AA-01; DE.CM-09 | Secure development practice; credential management; monitoring software |
| CIS Controls v8.1 | Control 16 Application Software Security | Secure development life cycle for in-house software |
| ISO/IEC 27001:2022 Annex A | 5.17 Authentication information; 8.28 Secure coding | Protecting credentials; secure coding practice |
| MITRE ATT&CK | T1593.003; T1552.001; T1552.004; T1213.003 | Techniques that harvest secrets from code and repositories |
Practitioner checklist
- Run a full-history, verified scan of every Git organisation, mirror and self-hosted server, and keep it as a baseline.
- Revoke every verified or active finding and record time to revoke, not time to close.
- Enable server-side push protection organisation-wide after a pilot, and decide on default or delegated bypass.
- Scan the pull request commit range in CI and fail on verified or unknown results.
- Distribute pre-commit hooks through templates, but do not count them as coverage.
- Write and test custom patterns for internal token formats before making them blocking.
- Extend scanning to container images, CI logs, wikis, issues and externally exposed
.gitpaths. - Map each finding to its owning non-human identity and remove credentials that have no owner.
- Review "used in tests" and "false positive" bypasses monthly.
- Replace static keys in code with secrets manager references or federated, short-lived credentials.
Standards and references
- GitHub Docs: About secret scanning
- GitHub Docs: About push protection
- GitHub Docs: Removing sensitive data from a repository
- GitLab Docs: Secret detection
- OWASP: Secrets Management Cheat Sheet
- NIST: SP 800-53 Rev. 5
- Truffle Security: TruffleHog documentation
- Gitleaks: Project documentation
Related NHI Mgmt Group resources: Secrets Management Guide · Top 10 Secrets Management Mistakes · CI/CD Security Guide · Secrets Scanning Buyer's Guide · Secret Sprawl Measurement Guide