Skip to main content
Thought Leadership
9 min read

Your Git History Still Has the Password You Deleted

Deleting a password from a repo doesn't remove it. How attackers pull secrets from git history, self-hosted servers, and reused vault keys, and how to stop it.

Your Git History Still Has the Password You Deleted

On a recent internal penetration test, our initial foothold did not come from an exploit. It came from a git clone. No login, no vulnerability, no alarm. We copied a company’s own source code off its own server and read the passwords out of it.

That engagement ended in a full Active Directory takeover, and the whole story is worth reading. This article is about the first link in that chain, because it is the one we find the most often and the one teams misunderstand the most.

Source code leaks credentials. It has for years. And the single most common mistake we see is a team that thinks it fixed the problem by deleting the secret.

”Public” on an internal server still means “no login”

The server in this case was a self-hosted GitLab instance, the kind a company runs itself rather than paying a vendor to host. Several of its repositories were set to public visibility.

On an internal server, public does not mean on the internet. It means readable by anyone who can reach the server, without authenticating. Teams hear internal and picture a wall around it. On a flat corporate network, there is no wall. Every user, and anyone who has phished a single one of them, can browse those repositories.

This server also allowed open self-registration: its sign-up page was live, so a network user could create their own account and look around with even more freedom. Neither setting is exotic. Both are defaults or conveniences that someone turned on and nobody turned back off.

The lesson is not that GitLab is dangerous. It is worth being precise about that, because GitLab patches real vulnerabilities on a steady cadence and self-hosted operators spend genuine effort keeping up: information disclosure in Workhorse, cross-site scripting in analytics dashboards, unauthenticated denial of service against the CI/CD job API. Those are the flaws teams track, and patching them is the right work.

None of them is what happened here. Nobody exploited GitLab. Two settings were left on, and a network user could read the source code. The client patched diligently and was compromised through a checkbox, which is the more uncomfortable finding, because a patch cadence will never catch it. An internal source-code server is a high-value target that gets treated as trusted infrastructure, and trusted infrastructure is exactly what attackers aim for once they are inside.

Automation repositories are the richest target

Not all source code is equal. Application code can leak secrets, but the motherlode is usually infrastructure automation: the code that builds, configures, and patches the estate.

Automation has to authenticate to everything it manages, which means it has to know a lot of passwords. On this engagement, the automation repositories held a virtualization-platform administrator password, privilege-escalation passwords used to gain root, a configuration entry that granted an account passwordless root, and a spread of database and application credentials. That is not unusual. If you want to find the passwords in a company, read the code that logs in to things for a living.

When you audit your own repositories, start there. The deploy, provisioning, patching, and infrastructure repositories earn attention before anything else.

Deleting a secret does not delete it

Here is the part that trips up good teams.

Git is not a folder. It is a history. Every version of every file is retained so the project’s past can be reconstructed. That is the entire point of the tool. It also means that when someone commits a password, then removes it in a later commit, the password is not gone. It is one command away, sitting in the history where it was first written.

We find secrets in history constantly, precisely because a developer noticed the mistake, deleted the line, committed the fix, and moved on believing it was handled. The current files look clean. The repository remembers everything.

This changes what remediation has to mean. If a secret has ever been committed, editing it out does not remove the exposure. You have to assume it is compromised and rotate it: change the actual password or key so the exposed value stops working. For a value that is already public to everyone on your network, rotation is the only real fix. Purging it from history is worth doing too, but rotation is the part that actually protects you.

A vault is only as strong as the key next to it

One repository on this engagement did the right thing on paper. Instead of a plaintext password, it held an encrypted vault. Encryption is the correct instinct.

It did not help, because the password that unlocked the vault had been reused elsewhere in the same exposed material. We found the key, opened the vault, and pulled a live administrator credential out of it.

Password reuse is usually discussed as a user problem: people reusing one password across accounts. It is just as common with the secrets that protect other secrets. A vault password, an encryption passphrase, a master key that shows up in three different repositories because it was convenient. Encryption buys you nothing if the key is lying in the same drawer as the lock.

It is not carelessness, it is convenience

It is tempting to read all of this as sloppiness. It usually is not. It is convenience winning a series of small, reasonable-looking decisions.

Automation has to authenticate to the systems it manages. The fastest way to make a playbook work at 6 p.m. on a deadline is to put the password in the file, confirm the job runs, and promise to move it into the vault later. The job runs. Later never comes. The secret ships with the code and rides along in every clone and every backup from then on.

Self-hosted git servers get the same treatment. Someone stands one up so the team can move quickly, sets a repository public so a colleague in another group can pull from it without a permissions ticket, and turns on self-registration so onboarding is one less email. Each choice solves a real problem that day. Nobody circles back to ask what the sum of those choices exposes.

This is why “just tell developers to be careful” does not work. Careful people under deadline pressure make exactly these trades, because the cost is invisible and deferred while the benefit is immediate. The fix has to be structural: something that makes the safe path the easy path, rather than a memo asking people to be more disciplined than their week allows.

How an attacker does this in an afternoon

None of this requires skill that is hard to find. The workflow is close to mechanical:

Anonymous clone to working credential

Reach the git server
List what is readable without authenticating. Self-hosted platforms will happily return public projects to an anonymous request.
not detected
should have caught this: Repositories private by default, self-registration off, SSO in front of the server
Clone everything readable
No exploit, no credential, no alarm.
not detected
should have caught this: Audit logging on clone operations, and alerting on bulk reads
Scan the working files and the full history
Free tools flag high-entropy strings, known key formats, and password patterns across every commit in minutes.
not detected
should have caught this: Run the same scanners yourself, pre-commit and server-side, before an attacker does
Try the interesting hits against real systems
The defender has to remove every secret from every commit. The attacker needs one that still works.
not detected
should have caught this: Rotate anything that has ever been committed; treat history as already read

The defender has to get every secret out of every repository and every commit. The attacker has to find one that still works. That asymmetry is why this keeps paying off, and public breach data reflects it: the annual Verizon Data Breach Investigations Report continues to rank stolen and misused credentials among the leading ways attackers get in.

The step a tester adds to that list is deciding how far to take the credential once it works. A found password can be validated without being used to its full effect, and on this engagement that distinction mattered, because the credentials in those repositories reached production medical-device infrastructure. Where we chose to stop, and why is a separate story worth reading if you are about to hand someone a foothold in your own environment.

How to actually fix it

The controls are well established and not expensive. They are just easy to skip.

  • Make private the default. Repository visibility should be private unless there is a deliberate reason otherwise, and the platform should not let individual repositories flip to public without review. Turn off open self-registration and put the server behind single sign-on.
  • Scan before the commit lands, not after. Pre-commit hooks catch a secret on the developer’s machine, before it ever reaches the server and its permanent history. Back that with server-side scanning so nothing slips past.
  • Rotate what leaked, and treat history as already read. Every credential that has touched a repository should be considered exposed. Rotate it. Then purge it from history so it stops being handed to the next person who clones the repo.
  • Move secrets into a secrets manager. Automation should pull credentials at runtime from a system built to store them, with access control and rotation, rather than reading them from a file in a repository. OWASP’s Secrets Management Cheat Sheet is a solid, vendor-neutral starting point. For the password practices underneath all of this, NIST SP 800-63B is the reference worth reading.
  • Look past the repositories. Secrets sprawl beyond source code. Continuous-integration logs, wikis, issue trackers, build artifacts, and container images all leak credentials the same way. When you scan, scan those too, and treat every hit as exposed and rotate it.

The uncomfortable truth is that secret scanning will almost always find something the first time you point it at your own history. That is not a reason to avoid looking. It is the reason to look now, on your terms, instead of finding out the way this client did. The same exposed credential is often just the first step toward privileged access nobody thought to count.

If you want someone to look at your source-control estate the way an attacker would, that is part of what we do on an internal penetration test. Start a conversation about where your secrets actually live.

Frequently Asked Questions

Does deleting a secret from a git repository remove it?

No. Git is a history, not a folder. Every version of every file is retained so the project's past can be reconstructed, which is the whole point of the tool. When someone commits a password and then removes it in a later commit, the current files look clean but the password still sits in history, one command away. The only real fix is to rotate the credential so the exposed value stops working, then purge it from history so it stops being handed to the next person who clones the repo. Treat any secret that has ever been committed as compromised.

How do attackers find secrets in source code?

They reach the git server, list what is readable without authenticating, clone everything, and run an automated secret scanner across the working files and the full commit history. Free tools built for this flag high-entropy strings, known key formats, and password patterns across every commit in minutes. Then they try the interesting hits against real systems. The defender has to get every secret out of every repository and every commit; the attacker only has to find one that still works. That asymmetry is why the technique keeps paying off.

Where do secrets leak besides source code?

Secrets sprawl well beyond repositories. Continuous-integration logs, wikis, issue trackers, build artifacts, and container images all leak credentials the same way. Infrastructure automation is usually the richest target, because the code that builds, configures, and patches your estate has to authenticate to everything it manages. When you scan, scan those surfaces too, and treat every hit as exposed and rotate it. Moving credentials into a secrets manager, so automation pulls them at runtime instead of reading them from a file, removes the root cause rather than chasing individual leaks.

Ready to Strengthen Your Defenses?

Schedule a free consultation with our security experts to discuss your organization's needs.

Or call us directly at (445) 273-2873