A single misconfigured deploy script can hand an attacker your entire commit history, including files you deleted years ago. A recent internet-wide scan found roughly 4.96 million IPs exposing .git metadata over HTTP, and about 252,733 of those servers — roughly 5% — leak a .git/config file containing live credentials such as API keys, database passwords, or access tokens, according to reporting aggregated by cyberpress.org and mysteriumvpn.com. US-hosted servers accounted for the largest share, around 34.7% (roughly 1.72 million IPs), followed by Germany, France, India, and Singapore. This is not a new problem: a 2018 internet scan already found on the order of 390,000 to 400,000 sites exposing .git publicly, meaning the bug class has persisted, largely unchanged, for the better part of a decade. The mechanism is simple and the tooling to exploit it is trivial — public utilities like git-dumper and the GitTools suite can reconstruct a full working tree from directory listings or, if listing is disabled, from blind enumeration of git's own object hashes. This post explains why .git/ exposure is so damaging, what refs, packed-refs, and reflogs actually leak, and how to close the hole and verify it stays closed.
Why is an exposed .git directory worse than an exposed source file?
Because .git/ is not a snapshot — it's the full object database, meaning every commit ever made, including branches and files never merged to the version currently live on the server. When git init runs in a directory later served by Apache or nginx with a default configuration, .git/ sits alongside index.html and is fetchable like any other path unless explicitly denied. An attacker who finds /.git/HEAD returning ref: refs/heads/main knows the repo is exposed and can start pulling loose objects and pack files directly, reconstructing the entire repository offline. This differs fundamentally from leaking a single config.php or .env file: it hands over source code, commit messages, author emails, and — critically — anything ever committed and later "removed," since deleting a file in a new commit does not erase it from history. A single exposed .git/ directory is effectively a leaked source control export, not a leaked file.
What specifically can attackers extract from git internals?
Three artifacts do most of the damage. .git/config stores remote URLs and, in poorly hygienic setups, credentials embedded directly in the remote URL (https://user:token@host/repo.git) — this is the file responsible for most of the ~252,733 credential leaks found in the 2024–2026 scan data cited above. .git/packed-refs and the files under .git/refs/ point to every branch and tag the repository has ever had locally, including private or abandoned feature branches that were committed but never pushed to the visible production branch — these can contain work-in-progress code, hardcoded test credentials, or debug endpoints never meant to ship. Finally, .git/logs/HEAD, the reflog, records every commit a local checkout has ever pointed to, including commits that a force-push or history rewrite was supposed to erase. Reflog entries are not deleted by git push --force; they persist locally under git's default retention windows (90 days for reachable entries, 30 days for entries no longer reachable from any branch tip) until an explicit git reflog expire and git gc --prune=now run. An attacker who fetches the reflog can retrieve commits the team believed were gone.
Why doesn't rewriting history actually fix a leaked secret?
Because tools like git filter-repo and BFG Repo-Cleaner rewrite the commit graph and refs, but they do not automatically expire the reflog or run garbage collection — so the "removed" commit, and the secret inside it, can remain fetchable as a dangling object indefinitely if the operator skips the cleanup step. This is a common and dangerous assumption: a team discovers a hardcoded AWS key in config.py, runs a history-rewrite tool, force-pushes the cleaned branch, and considers the incident closed. If the .git directory itself was ever web-exposed, or if a clone was made before the rewrite, the original commit is still reachable through refs, packed objects, or the reflog on any copy that wasn't also garbage-collected. The only actually safe remediation for a leaked secret is rotation — treat the credential as compromised and reissue it — regardless of what happens to the git history afterward. History rewriting reduces future exposure; it does not undo past exposure.
How does this connect to the broader secrets-in-git problem?
Web-exposed .git/ directories are a distinct vector from secrets committed to git history in general, but they compound the same underlying problem: source control was never designed to be a secrets store, and once a credential is committed, its blast radius is hard to bound. GitHub reported detecting over 39 million leaked secrets across public repositories in 2024 alone, a 67% year-over-year increase, spanning API keys, cloud credentials, and database connection strings committed directly into tracked files. Most of those leaks don't require a misconfigured web server at all — they're simply pushed to a public GitHub repo. But an exposed .git/ directory turns even a private, unpublished repository into an equivalent exposure, because the entire object database becomes fetchable to anyone who finds the path. The lesson from both data points together: secrets belong in a vault or environment-injected at runtime, never in a commit, because you cannot reliably guarantee a commit stays private forever.
How do you detect and prevent .git exposure in practice?
Prevention starts at the server and deploy-pipeline layer, not in git itself. Configure your web server or reverse proxy to explicitly deny access to any dot-prefixed directory (an nginx location ~ /\.git deny-all block, or the Apache equivalent) so .git/ is never served even if it exists on disk. More fundamentally, stop deploying by copying a working tree that still contains .git — use git archive to export a clean tree, or build deployable artifacts through CI so the .git metadata never ships to production filesystems in the first place. For detection, run periodic external attack-surface scans that specifically request /.git/HEAD and /.git/config against every production hostname and subdomain, since a 200 response on either path is an unambiguous, high-confidence finding rather than a heuristic guess. Any credential ever found in a reachable .git/config, or in any commit reachable via refs or reflog, should be treated as compromised and rotated immediately — full stop, independent of whether the exposure window was hours or years.
How Safeguard helps
Safeguard's continuous external attack-surface scanning is built to treat internet-facing misconfigurations as first-class findings rather than an afterthought bolted onto vulnerability management, surfacing exposed infrastructure — unprotected admin panels, open storage buckets, and similarly reachable paths that should never be public — with severity tied to what's actually exposed, including whether live credentials are present. Once a credential surfaces anywhere in your supply chain — a dependency manifest, an SBOM component, or another scanned artifact — Safeguard's correlation model applies the same CWE-mapped risk logic and reachability context it uses for code vulnerabilities, so a leaked credential is prioritized and routed for rotation instead of sitting in a separate, disconnected report.