Secrets in Git history: what one stolen credential can reach

Secrets in Git history: what one stolen credential can reach

SecurityWeek reported on Tuesday, 13 October 2026, that Meta is notifying roughly 1,500 people, most of them employees in the United States, that their personal information was impacted. According to the report, an outside group gained access to some of Meta's GitHub repositories using an employee's stolen credentials. Meta says the intrusion was detected a few weeks ago, and that its security team shut down the attackers' access and brought in GitHub and law enforcement. Notifications started going out the same day.

Meta told SecurityWeek that 99% of the data the attackers took was business data, in the form of software code related to some of its products. The remaining 1% was personal data, which Meta says had been added to the repositories by mistake. For anyone who runs their own repositories, the intrusion is the least useful part of this story. The useful part is that the data was there at all.

Why secrets in Git history are the real exposure

Here is Meta's statement to SecurityWeek, in its own words:

This was not a vulnerability in any Meta product, and Meta's internal systems were not breached. Meta business data held in a GitHub repository was improperly accessed using an employee's stolen credentials - this included a small amount of personal data for a limited number of people, most of whom were Meta employees.

Read that as a timeline. The entry was one credential. What that credential could reach was decided much earlier, on the day somebody committed something that should never have been committed.

Meta also says it rotated secrets it found in the affected repositories, which means the repositories held secrets as well as code.

This matters because of how Git works. A clone is a full copy of every commit ever made, and that includes far more than the files you see today. Whoever gets one working login to your Gitea, Forgejo or GitHub account gets all of it, including the file you deleted two years ago. That is as true for a mini PC in a cupboard as it is for a company with a security team.

What actually ends up in repositories

Nobody plans to commit a secret. It happens during a late-night fix, and it tends to be one of these:

  • API keys and tokens pasted into a config file "just to test".
  • .env files committed before anyone added them to .gitignore.
  • Database dumps such as backup.sql, left in the project folder after a migration.
  • CI credentials: deploy keys, registry logins and tokens written straight into a workflow file.
  • Logs with request headers, email addresses or session tokens in them.
  • Test fixtures built from real records instead of made-up ones.

The last one is easy to miss. Meta says a significant portion of what looked like personal data in its repositories was actually synthetic or anonymized test data, and it has not said what types of personal information were involved. Synthetic data is the right way to build fixtures. In any project, the trouble starts when a real export gets used because it was quicker.

The danger is usually in history rather than in the files you see today. Deleting .env and committing again only removes it from the working tree. The older commit still holds the file, and git show <commit>:.env will print it for anyone with a clone.

How to look on your own box

Running grep -r "API_KEY" . only searches the files that exist right now. It will tell you the repository is clean when the key is sitting in a commit from last year. You need a tool that walks every commit.

Two free tools do this well. gitleaks scans the full history by default:

cd /path/to/your/repo
gitleaks git --redact -v

Older releases use gitleaks detect --source . --redact -v for the same job. The --redact flag keeps the secret itself out of your terminal scrollback.

trufflehog also walks the history, and for many key types it checks whether the credential still works. It does that by trying the key against the provider, so know that before you run it.

trufflehog git file://.

To check for one file you already suspect, plain Git is enough:

git log --all --full-history --oneline -- .env

Any output means a file at that path was committed at some point.

Treat the results honestly. Both tools match patterns, so they can flag example keys in documentation, and they miss secrets that do not look like secrets, such as a database dump full of real rows. I covered this in the earlier post on why an automated scan is a lead, not a verdict. Open each finding, look at the commit, and decide for yourself. A clean report means the tool found nothing, which is different from nothing being there.

What to do once you find one

The order matters.

  1. Rotate first. Revoke the key and issue a new one before you touch the history. The secret is burned the moment the repository is shared or public, because you cannot know who cloned it. Meta says it rotated the secrets it found in the affected repositories, and that is the correct first move at any size.
  2. Then rewrite history. Work in a fresh clone. git filter-repo --invert-paths --path .env removes that file from every commit, and --replace-text scrubs a string while keeping the file. BFG Repo-Cleaner does the same job with bfg --delete-files .env. Afterwards, add the remote back, force-push, and have every collaborator clone again.
  3. Stop the next one. Add a pre-commit hook so a secret is refused before it becomes a commit, and move the secrets into a proper store.

Rewriting history does not un-leak a key you already published. Old clones, forks, CI caches and backups still contain it. The rewrite is cleanup. Rotation is the fix.

For the hook, the pre-commit framework and gitleaks work together. Save this as .pre-commit-config.yaml, set rev to the current gitleaks release, and run pre-commit install:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks

Then give the secrets somewhere else to live. SOPS with age encrypts the values in a file, so the encrypted version can be committed and only machines holding the age key can read it. Vaultwarden is the simpler choice if you want a password manager you host yourself and copy values out of at deploy time. Either one removes the reason the .env file was in the repository to begin with.

The part a big company got wrong that you can get right

Meta says storing personal data on GitHub went against company policy. It happened anyway. For a small operator that is the most useful detail in the report, because it shows that a written policy on its own is not a control. A control is something that runs without anyone remembering it.

Meta says it is now adding controls for proactive scanning to detect any user data uploaded by mistake. You can have the equivalent today: a pre-commit hook, a scheduled gitleaks run over every repository, and a .gitignore that lists .env, *.sql and *.log from the first commit.

The cheaper habit is data minimisation. If something is not needed in the repository, it does not belong there. Code needs to know that a variable called DATABASE_URL exists, and it works fine without the value. Tests need records shaped like customers, and made-up ones do that job.

The other half is ownership. Meta told SecurityWeek that 99% of what was taken was business data in the form of software code. Your repositories are business data too: compose files, network layout, hostnames, the scripts that run your backups. Where that repository lives decides who can reach it. A hosted account can be tried from anywhere on the internet with one stolen login, while a Forgejo or Gitea instance that only answers over WireGuard or Tailscale needs the attacker to get onto your network first.

Self-hosting does not make a repository safe on its own. A Gitea login page open to the internet falls to a stolen login the same way. Turn on two-factor authentication, use scoped tokens that expire, and keep the instance off the public internet unless it has to be there.

If you would rather hand this off

None of this is difficult, but it is the kind of job that gets put off until after something goes wrong. If you would rather have someone scan your repositories, clean up the history, and set up the hooks and the secret store for you, that is work I take on. The details are on the services page.

Fiverr: hiteshsaini459 · Upwork: hiteshsaini25