Skip to content
CWS
CorovaAboutContact
Book a Call
All articles
Service Delivery

The Credential Has Been in the Repo History for Years

A credential was committed. A few commits later it was taken out. The file reads clean today. The credential is still in the history, still in every clone anyone took, and it may still open the system it was issued for.

CWSAugust 14, 20267 min read

The later commit recorded a new state

A commit records a state. The commit that deleted the line recorded a new state in which the line is absent. The earlier state is still there, still reachable, still as readable as it was the day someone pasted the value in. A normal clone pulls both.

That is the tool working as designed. Reconstructing any past state is the reason for keeping history. So a credential written into a repository is published to everyone with read access from the moment it lands, and it stays published until the credential itself changes.

The copies are what make this hard. By the time a finding reaches you the repository has been cloned onto laptops and build agents, forked into personal namespaces, mirrored, and pulled into pipeline caches. Editing the canonical repository changes the canonical repository, and every copy keeps what it already has.

  • Developer clones, including ones on machines that left the company with their owner
  • Forks, which on GitHub share object storage with the upstream repository through a repository network, so an object removed from one side can stay fetchable from the other
  • Read-only mirrors kept for disaster recovery, and backups of the platform itself
  • Build agents and pipeline caches that hold working copies between jobs
  • Anywhere a bundle or an archive of the repository was exported to, including tickets, chat threads, and file shares

The exposure window belongs to the rotation process

A finding gives you a string and a commit. Whether that string still opens anything is a separate question, and it is the one that decides how the next week goes.

Work from what would have forced the value to change. A credential can sit in a history for years because nothing in those years was built to expire it: no scheduled rotation covered its class, no owner was recorded who would have noticed, and no inventory listed it. The commit is old news by the time you see it. Whether the credential is live is a current condition, and it belongs to the rotation process.

That reframing matters because it moves the first hour of work off the repository and onto the credential.

  • What the credential grants, at what scope, in which environment
  • Whether the issuing system still accepts it, confirmed by testing it
  • What is using it today, since a production service holding the same value turns rotation into a change window
  • Whether the issuing system logs use, and what those logs show across the exposure window
  • Who can issue a replacement, and who approves the cutover
A credential stays valid for years because nothing in those years was built to change it.

Rotate, then decide about the history

Rewriting history while the credential still works protects nothing. Every copy already in circulation keeps the old objects, and anyone holding one already has what they need. Rotation is the control. The rewrite is housekeeping, and housekeeping can wait until the thing being cleaned up is inert.

The sequence gets inverted because the rewrite looks like the easier half. The local part of it is a command someone runs in an afternoon, and on a hosted platform that command is the first step of several. Rotating a credential four teams depend on, where the original owner has left and the replacement path was never tested, is a coordination problem with a change window attached. The afternoon task feels like progress while leaving the exposure where it was.

Work it in order, and write down the dates as you go, because the dates are what you will be asked for.

  • Establish what the credential is and what it grants
  • Issue a replacement and cut the dependent systems over to it
  • Revoke the old value at the issuer, then verify the revocation by trying it
  • Pull the issuer's logs for the window between the commit date and the revocation, and hand anything unexplained to whoever runs incident response
  • Record the exposure window as a date range, since that range is what a regulator, a customer questionnaire, or your own board will ask about later
Order of operations, in one line: the credential stops working, then the history gets tidy.

What a rewrite costs, and when it is worth paying

Once the credential is dead, rewriting the history becomes an ordinary engineering decision, and it changes the identifier of the rewritten commit and of every commit after it. The effects propagate. Open branches diverge and pull requests need attention. Tags, release notes, and incident write-ups that quote a commit stop resolving. Pipelines and submodules pinned to a specific commit stop finding it. Everyone with a clone has to reset or start again, and anyone who pushes from a stale clone can put the old objects straight back. Signed commits lose their signatures until someone re-signs them.

On a hosted platform the force push is one step of several. Pull request refs keep their own copies, cached views keep serving, and the old objects stay fetchable by commit identifier until platform support runs a purge, which is a support request with a turnaround of its own. Budget for that step, since the push starts the removal and the purge is what completes it.

Forks are the part that gets forgotten, and the mechanics run against you. On GitHub a fork shares object storage with its upstream through a repository network, so an object removed from the upstream can stay fetchable through any fork in that network until the purge covers the network as a whole. On platforms that give a fork its own storage, the copy sits outside the reach of your rewrite. Either way, a repository that was ever public has copies sitting beyond your reach.

Set against that, a rewrite on a dead credential buys a cleaner artifact and removes a string a future reader might waste a day treating as live. In a few situations that is worth the disruption. Rotation sequencing, and the point at which rewriting history is worth its cost, are CWS positions. The Git documentation describes what a rewrite does, and whether to pay for one is left to you.

  • The repository is about to be made public, open sourced, or transferred to another party
  • The committed material is customer data, personal data, or key material, where rotation leaves the exposure exactly where it was
  • A contractual or regulatory obligation names removal specifically
  • The repository is young and has few consumers, so the disruption stays contained
  • In an old repository with many consumers and a rotated credential, leaving the history alone and recording why is a defensible answer

History is part of the estate

An estate inventory describes current state, because current state is what a repository page shows you. History holds material that current state has stopped displaying.

Discovery across roughly 6,300 repositories is the extent of what CWS can claim here, and the count is only one output of it. The question that decides how bad a single exposure turns out to be is a different one: who can clone this repository, and where do copies of it already sit.

Read access sprawls quietly. A team is granted access for one integration and keeps it after the integration retires. A contractor organization outlives the project it was created for. An automation account is added everywhere because scoping it per repository looked like more work than it was worth. By the time a secret surfaces, the honest answer to who has this is a list nobody has opened in two years.

Scanning history across an estate returns more than scanning current state does, because it covers every state the repository has ever held. Volume is the risk to the exercise, and a triage rule written after the results land is a rule written under pressure. Agree the rule first, then run it, and keep enough per finding to answer questions in month six.

  • The commit date, which sets the start of the exposure window
  • The credential class and the issuing system, which together determine who can rotate it
  • Whether the value was still accepted when tested, with the result and its date
  • The repository's read-access list at the time of the finding, plus whether forks or mirrors exist
  • The rotation decision and the history decision, each with its date and reasoning, in the same record

What makes the next one short

Everything above closes one credential. What shortens the next one is the plumbing around credentials generally, and that work sits outside the repository.

Short lifetime is the property that does the most. A credential that expires on its own turns an exposure measured in years into one measured in hours, whether or not anyone notices the commit. Where a class of credential cannot be made short-lived, the fallback is a rotation path that has been run at least once in anger, an owner recorded against it, and a scope narrow enough to contain a leak to one system.

Access is the other half. A read-access review on the repositories that hold anything sensitive, an expiry on contractor and integration grants, and a stated position on forks and mirrors each shrink the blast radius of the next finding.

Published secure development practice tells you to keep credentials out of source control, and that guidance holds right up to the morning it fails. What to do once you find one that has been sitting in a history for years is a separate body of work. That morning is operating work, and it belongs to whoever owns the estate.

Rotation closes the exposure window. What follows is housekeeping, and housekeeping can be scheduled.

Sources

  • CWS delivery corpus, one application security estate discovery across roughly 6,300 repositories
  • CWS Application Security service description: estate discovery scope, deliverables, and responsibilities
  • Git project documentation on rewriting published history and on the effects of changing commit identifiers
  • NIST Secure Software Development Framework (SP 800-218), practices for protecting source code and securing development environments
  • Rotation sequencing, and when rewriting history is worth its cost: CWS positions.