Vulnerability

Secrets in Version Control
detect, rotate, revoke, verify

Secrets in version control is the exposure of live credentials (API keys, cloud access keys, database passwords, OAuth client secrets, signing keys, session tokens) through the repository history, public visibility, downstream forks, or pull request diffs. The credential is not just present in source; it is recoverable by anyone with read access to the history, indexed by automated scanners scraping public hosts, and may have been mirrored, cached, or built into container images long before the developer noticed.

No credit card required. Free plan available forever.

Severity

Critical

CWE ID

CWE-540

OWASP Top 10

A07:2021 - Identification and Authentication Failures

CVSS 3.1 Score

9.8

What are secrets in version control?

Secrets in version control is the exposure of credentials through the repository as a system, not through a single line of source code. The credential may have been committed once and reverted, embedded in a configuration file deleted three years ago, captured in a pull request diff that lives on the provider forever, or pushed by mistake to a fork that the developer never owned in the first place. The canonical CWE references are CWE-540 (Inclusion of Sensitive Information in Source Code), CWE-538 (Insertion of Sensitive Information into Externally-Accessible File or Directory), and CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor). OWASP catalogues the category under A07:2021 Identification and Authentication Failures because the operational consequence is the same as a stolen password: an attacker has a working credential the application accepts.

The distinction between this page and hardcoded secrets (CWE-798) is the surface of exposure. Hardcoded secrets is a build-time SAST finding: the secret is present in the current working tree and a code scanner sees it on the next run. Secrets in version control is a repository-system exposure: the secret has been written to durable history (the commit log, the pull request diff, the cached fork, the build artefact, the container image, the search index of a public host) and the next code scan against the working tree may report nothing while the credential is still recoverable. The two often co-occur, but the remediation paths diverge sharply. A working-tree fix closes hardcoded secrets; only rotation at the issuing system plus revocation of the old value closes the version-control exposure.

Treat every detection in version control as a live credential under attack until the rotation, revocation, and verification have completed. Automated scanners scrape public commits on GitHub, GitLab, Bitbucket, and Gitea in near real time, often picking up leaked credentials within minutes of the push. Even private repositories are not safe: a compromised developer account, a leaked backup, an unintended fork, an over-permissioned CI runner, or a misconfigured public mirror are all routine paths from private commit to attacker-controlled access.

How secrets reach version control

The committed-and-pushed path is the obvious one, but most enterprise programmes find that the harder leaks come from history-resident artefacts and downstream replicas that survive a working-tree cleanup. The pattern is always the same: a credential becomes part of a durable artefact the development workflow keeps around, and the original developer no longer has unilateral control over where the artefact lives.

1

Committed to the working tree

A developer hardcodes a credential into source or a configuration file and pushes it. The secret is now in the repository on the remote, in every clone, and in the next CI build. This is the only path a working-tree code scan can see by itself.

2

Embedded in the commit history

The developer notices the mistake and removes the secret in a follow-up commit. The working tree is clean; the history still carries the original commit, the value is recoverable via git log, git show, and any clone made after the original push, and the next code scan reports nothing.

3

Captured in pull request diffs and provider caches

Even after a history rewrite, the credential lives on inside the pull request diff on the hosting provider, in code-review comments, in CI build logs, in the provider search index, and in any third-party tool (issue tracker, code-review bot, AI assistant) that ingested the diff.

4

Mirrored to forks, clones, and downstream consumers

Public repositories are forked at unpredictable rates, private repositories are cloned to laptops the central team does not control, and any internal mirror, archive, backup, or container image built off the affected commit carries the credential indefinitely.

Common causes

Missing or misconfigured .gitignore

A .env file, .pem key, .aws/credentials, kubeconfig, or local override is added to the repository because .gitignore is absent, incomplete, or only added after the file was already tracked. The provider continues to version it on every subsequent commit until it is removed and the tracking history is corrected.

Public-history rewrite skipped

A developer deletes a secret in a new commit and stops there. The cleanup leaves the value in the prior commit, the merge commit, and any pull request diff still on the provider, all of which remain discoverable by anyone who can read history.

Public repository or accidental visibility flip

A repository starts private, gets flipped to public for a demo or open-source release, and exposes the entire history. Even a brief window of public visibility is long enough for automated scrapers to capture committed credentials and archive them.

CI runner with over-broad token scope

A continuous integration runner has a Git token that can read every repository in the organisation. A compromise of the runner, a leaked workflow log, or an exposed environment variable becomes a leak of every repository the token can reach, including any credentials those repositories happen to contain in history.

Committed test fixtures and seed data

Test fixtures with sample credentials, seed data for local development, demo configuration files, and tutorial snippets all enter the repository as legitimate-looking content. Some are inert; some carry credentials that work against shared staging environments, third-party sandboxes, or production by mistake.

Force-push without coordinated cleanup

A team rewrites history with git filter-repo or BFG Repo-Cleaner and force-pushes, assuming the secret is gone. Developers with stale clones still hold the credential, CI caches still carry it, and any fork or mirror that pulled before the rewrite remains compromised.

IDE plugins, AI assistants, and code-review bots

Third-party tools that ingest the diff (AI code-review assistants, static-analysis SaaS, vulnerability bots, license scanners) cache the content. Once a secret is in their pipeline, removing it from the repository does not remove it from their store, and the leak is now governed by their retention policy rather than yours.

Long-lived credentials with no rotation discipline

Static credentials that have lived for months or years acquire too many touch points to inventory. A leak of one of these credentials has a wide blast radius because the credential authorises a wide surface area and the team cannot enumerate every system that depends on it.

How to detect it

Detection has to cover the working tree, the commit history, the pull request diffs, and the downstream replicas. A single scanner against the working tree will miss the most common enterprise pattern: a secret that was committed, deleted, and is still recoverable from history. Pair the working-tree code scan with a history sweep, provider-side scanning where it is available, and pre-commit controls that stop the next leak before it lands.

Automated and tooling signals

  • SecPortal code scanning runs Semgrep with the p/secrets ruleset against the connected repository on every scan, capturing matches with the file path, line number, secret category, and the engagement record they belong to.
  • Repository connections via GitHub, GitLab, and Bitbucket OAuth give the scanner read access to the repository so the working-tree scan happens against the same code the team ships rather than a stale snapshot.
  • History-aware tooling (gitleaks, trufflehog, detect-secrets, and the equivalent provider-side scanner) walks the commit log and flags secrets that the working tree no longer contains. Pair the history scan with the working-tree scan because they cover different exposures.
  • Pre-commit hooks (gitleaks, detect-secrets, trufflehog) refuse to accept commits that contain candidate credentials, moving the detection earlier than the push, the pull request, or the CI pipeline.
  • Provider-native secret scanning (GitHub Advanced Security secret scanning, GitLab secret detection, Bitbucket security tooling) covers the public-host detection surface and partners with issuers to revoke certain credential classes automatically. Use it alongside SecPortal scanning rather than instead of it; the two see different vendor coverage.
  • Findings management tags every detection with CWE-540, CWE-538, or CWE-200 plus the OWASP A07:2021 category, applies a CVSS 3.1 vector, and routes the finding through the engagement record so triage, rotation, and verification are state events rather than chat messages.

Manual and audit-driven detection

  • Search the history with `git log -p -S` against vendor-prefix substrings such as AKIA (AWS), or `git log -p -G` against entropy-based regex patterns for AWS, Azure, GCP, Stripe, Slack, GitHub, OAuth client, and PEM private-key markers.
  • Audit `.env.example`, `config.sample`, `docker-compose.yml`, `Dockerfile`, Kubernetes manifests, Terraform state, Ansible vaults, and CI workflow files for placeholder values that were never replaced with environment-variable references.
  • Review every repository that was once public or that is accessible to a developer audience wider than the team that owns it. Brief public exposure is functionally equivalent to permanent public exposure for any credential that touched the repository in that window.
  • Cross-check issuing-system access logs (the cloud IAM audit log, the third-party API request log, the OAuth token activity feed) for use of credentials from unexpected source IPs, regions, user agents, or hours. A leaked secret often shows itself in the access log before the developer notices the leak.
  • Periodically audit build-artefact registries (container registry, package registry, artifact server) for secrets baked into images. A history rewrite cleans the source repository but does not rebuild the artefacts that already include the value.

Detection is the first state event in the workflow, not the end of it. For the operating discipline that turns a Semgrep detection into a verified closure (triage, rotate at the issuing system, revoke the old value, clean the working tree, decide on a history rewrite, and re-scan the cleaned branch), read the secret scanning remediation workflow.

How to fix it

Rotate the credential at the issuing system before touching the repository

Rotation produces the new value the application will use; revocation of the old value is a separate step. Do the rotation first because every minute the old value still authenticates is a minute the attacker can use it. Capture the rotation as a state event on the engagement record with the issuing-system ticket reference, the named operator, the new credential identifier (never the value itself), and the timestamp.

Revoke the old value and verify it no longer authenticates

Revocation is the step that turns the leaked credential from a live attack tool into an inert string. Explicitly revoke the previous value at the issuing system, then run a verification check that confirms it no longer authenticates: an authentication request that should fail returns the expected denial, the cloud provider returns the key as deactivated, the third-party API rejects requests signed with the old token. Revocation without verification leaves the closure uncertain.

Remove the secret from the working tree

Once the credential is rotated and revoked, remove the literal value from the current code or configuration and replace it with a reference to a secrets manager, an environment variable, or a runtime-injected configuration source. Push the cleanup commit so the next code scan no longer flags the working tree.

Decide whether a history rewrite is in scope

A rotated and revoked credential is no longer a live attack tool, so leaving it in history is sometimes acceptable. A history rewrite (git filter-repo, BFG Repo-Cleaner, or the equivalent provider tool) is appropriate when the credential is irreplaceable (a long-lived signing key, a certificate that cannot be rotated, a customer-supplied key), when the repository is public and a residual value is unacceptable on principle, or when a compliance obligation requires the artefact to be expunged. Capture the decision on the finding record so the remediation reflects the chosen scope.

Coordinate with forks, clones, and downstream consumers

If you force-push a rewrite, the team needs to re-clone, CI caches need to be invalidated, build artefacts need to be rebuilt, and any internal mirror or backup needs to be updated. A history rewrite without coordinated cleanup leaves the credential alive in every stale clone and downstream copy.

Move to short-lived credentials and a secrets manager

Replace long-lived static credentials with short-lived tokens issued through OIDC federation, workload identity, or a secrets manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, Google Secret Manager). The shorter the lifetime, the smaller the consequence of the next leak. The closer the issuing system is to a managed audit trail, the easier the next investigation.

Install pre-commit detection on every developer machine

Run gitleaks, detect-secrets, or trufflehog as a pre-commit hook configured at organisation level so the detection happens before the push rather than after. Pre-commit detection is the cheapest and earliest line of defence and removes most candidate leaks from ever reaching the remote at all.

Document the incident and feed it back into the threat model

Every leak that reached production should produce a brief retrospective: what credential leaked, how it leaked, how long it was exposed, who used it (if anyone), what the rotation and revocation looked like, what changed in the controls. Capture the artefact through document management against the engagement record so the same gap does not recur on the next service.

How SecPortal records and tracks the leak

SecPortal is the workspace where the secret-leak finding lands as a structured record alongside scanner findings, pentest findings, and audit findings. The platform does not rotate cloud IAM credentials for you, does not revoke third-party API keys, does not ship a secrets manager, and does not push a force-rewrite to your repository. What it does is hold the operating record so the rotation, revocation, cleanup, and verification become state events the next auditor and the next CISO can read end to end.

Record the finding with CWE-540 and CVSS 3.1

Capture the leak as a finding on the engagement record, tagged against CWE-540 (or CWE-538, CWE-200 where the pattern fits), the OWASP A07:2021 category, and a CVSS 3.1 vector calibrated against what the credential authorises (cloud admin, database, third-party API).

Scan the connected repository with Semgrep p/secrets

Repository connections via GitHub, GitLab, and Bitbucket OAuth give the code scanner read access. Each run captures every working-tree match with file path, line number, secret category, and an attachment to the engagement record.

Route to the named owner with team RBAC

Findings management assigns the leak to the engineer or AppSec lead with the responsibility to coordinate rotation and revocation, scoped through team management RBAC so the assignment respects role boundaries.

Track rotation and revocation as state events

Move the finding through the lifecycle states as the rotation, revocation, cleanup, and re-scan complete. Activity log records the actor, timestamp, and rationale for every transition so the audit trail back to the leak is intact.

Suppress documentation samples through finding overrides

Use finding overrides to mark documentation samples and known-safe placeholders as false positives with a written rationale so the next scan does not re-litigate the same call. The override carries a (workspace, finding, target) uniqueness key so the suppression is not silently lost.

Attach incident artefacts through document management

Upload the rotation ticket reference, the revocation confirmation, the IAM access-log audit, the post-incident retrospective, and any provider-side secret-scanning report through document management so the closure record is complete and reusable for the next audit.

Generate the leadership report from the same record

AI-generated reports derive the secret-incident narrative from the finding record so leadership reads the closure verification, the timeline, and the named ownership rather than a hand-built status update assembled from chat history.

Map the finding to compliance frameworks

Compliance tracking surfaces the same leak inside ISO 27001 (A.8.5, A.8.24, A.5.16), SOC 2 (CC6.1, CC7.1), PCI DSS (3.6, 8.6), and NIST SSDF (PW.4, PW.6) so the framework evidence reuses the operating record rather than diverging from it.

Carry the closure through retest

Retesting workflows confirm the cleaned branch no longer reports the secret on a follow-up scan, the cloud-provider audit log shows the rotated credential active, the revoked credential is denied, and the closure verification lands on the same finding record.

SecPortal does not ship a secrets manager, does not federate workload identity, does not push history rewrites, does not revoke credentials at the cloud provider, and does not ingest GitHub Advanced Security or GitLab secret-detection findings through a vendor integration. The leak still has to be resolved at the issuing system and in the repository through the team or tools that own those surfaces. SecPortal carries the operating record across those tools so the closure is verifiable rather than implied.

Related vulnerabilities and recommended reading

Version-control credential exposure sits inside the wider authentication and information-disclosure family. The pages below cover the adjacent vulnerability categories, the frameworks that name the controls the leak violates, and the SecPortal workflows that carry the operating record across detection, rotation, revocation, and verification.

  • Hardcoded secrets the build-time SAST companion that catches secrets present in the working tree, distinct from the repository-history exposure this page covers.
  • Sensitive data exposure the wider OWASP category for unintentional exposure of sensitive information, of which credential leakage through a repository is one routine pattern.
  • Information disclosure the umbrella category that captures any unintended leak of information through an externally-accessible artefact.
  • Default credentials the adjacent authentication failure where the credential is recoverable from public documentation rather than from a private repository.
  • Broken authentication the broader category that covers any failure of the authentication mechanism, of which an attacker holding a working credential is the worst-case outcome.
  • Insecure design the upstream design-stage failure when a system was built around static long-lived credentials rather than short-lived federated identity.
  • Code scanning the SecPortal feature that runs Semgrep with the p/secrets ruleset against the connected repository on every scan.
  • Repository connections the SecPortal capability that connects GitHub, GitLab, and Bitbucket via OAuth so the code scanner has read access to the working tree.
  • Findings management the operating record that carries the CWE, CVSS, severity, owner, evidence, and state transitions for every leaked-credential finding.
  • Finding overrides the override mechanism that captures documentation samples and known-safe placeholders as false positives with a written rationale.
  • Secret scanning remediation workflow the operating discipline that turns a Semgrep detection into a verified closure through rotate, revoke, cleanup, and re-scan.
  • Code review the engagement shape that pairs SAST and human review on the same engagement record so leaked-credential findings travel alongside design-level findings.
  • DevSecOps scanning the SDLC-embedded scanning discipline that puts code scanning, including p/secrets, alongside dependency and DAST scanning on the same engagement.
  • Incident response the engagement shape for leaks that crossed the threshold from candidate finding to confirmed compromise and now need full incident handling.
  • OWASP ASVS the verification standard whose Authentication and Secret Management chapters name the controls a leaked-credential finding violates.
  • NIST SSDF the Secure Software Development Framework with PW.4 and PW.6 practices for protecting software, including managing credentials in code and build pipelines.
  • SecPortal for AppSec teams the audience overview for the teams that own secret scanning, rotation governance, and the closure record across the application portfolio.
  • SecPortal for product security teams the audience overview for product security organisations that pair secret-leak response with the engineering and product organisations that issued the credential.

Compliance impact

Close the secret leak on the engagement record

SecPortal runs Semgrep with the p/secrets ruleset against the connected repository, captures the leaked-credential finding with CWE-540 and CVSS 3.1, and carries the rotate, revoke, cleanup, and verification steps through one auditable record. Start scanning for free.

No credit card required. Free plan available forever.