21 Oct 2026 Two critical RCE bugs in PHP core (CVE-2026-109618, CVE-2026-109619). Fixed in 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12. Is my PHP patched? →
Checklamp
Exposed file · .git

An exposed .git directory hands over the whole project

Published · updated

Two URLs decide whether your source code is public: /.git/HEAD and /.git/config. When they answer, the entire repository — including secrets that were committed and “removed” later — can be downloaded by anyone.

Passive. Public responses only, nothing is attacked. No account. The result is on the page. Honest: if a value is hidden, we say so instead of guessing.
Reading public responses — usually 5–20 seconds

Confirm it in two requests

curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.git/HEAD
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.git/config

A 200 with a body like ref: refs/heads/main means the repository is being served. A 403 or 404 means the web server refuses .git — the normal, correct state. A 200 that returns your homepage HTML is a catch-all route, not an exposed repository: check the body, not only the code.

What an attacker gets

  • Every file, including ones never meant to be served — configuration, scripts, database migrations, test data.
  • The full history. A password committed in 2023 and deleted in 2024 is still in the pack file. “Removed from the file” is not “removed from the repository”.
  • Branches and stashes — often the half-finished feature with debug credentials.
  • Deploy hints — hostnames, CI files, the shape of your infrastructure.

Rebuilding a working copy from a served .git is a solved problem with public tooling, and the requests for it are automated: scanners try /.git/HEAD on every new domain they find.

Why it happens

Deploys by clone or FTP. The project directory is copied whole into the document root instead of only its public part.

Missing deny rule. nginx and Apache serve dot-directories like any other path unless told otherwise; nothing in a default configuration protects .git.

Build artefacts. A CI job that runs git archive wrongly, or a developer's git pull inside public_html.

Close it properly

  1. Deny dot-paths at the web server before removing anything, so the fix survives the next deploy:
    location ~ /\.(?!well-known/) { deny all; access_log off; }
  2. Move the repository out of the served directory — deploy with git --work-tree=/var/www/site checkout -f from a separate clone, or through CI. Deleting .git by hand only lasts until the next pull.
  3. Rotate what the history contains: database passwords, API keys, JWT secrets, SSH keys that were ever committed.
  4. Treat the code as read. An exposed repository is a disclosure of your logic, including which endpoints skip authentication — look for probing of those endpoints in the access log.
  5. Verify: /.git/HEAD, /.git/config, /.git/index and /.git/objects/info/packs must all return 403 or 404.

Rewriting history so the secrets are not in the pack file is worth doing, but it is a second step: rotation is what makes the leaked credentials useless.

The same class of mistake

Where .git is served, a .env usually is too, and often a phpinfo leftover — they share the same root cause: everything was deployed into the directory the web server serves. Fix the deployment, then the class disappears; patch one file and it comes back with the next release.

Questions

The .git directory returns 403 — is that enough?

Yes for that path, if the denial is at the server level. Confirm that .git/config, .git/index and .svn are also refused, and that the rule comes from the server config rather than a file an attacker could reach.

Does deleting .git break the site?

The running site does not need it. Your deployment process might: if it pulls into the live directory, deleting the repository breaks the next update — which is why the fix is moving the repository out, not just removing the folder.

Can I publish the repository on purpose?

Many projects are public on GitHub, and that is fine. The problem is the deployment copy being served from the web root — the same files, but with history that includes secrets and with no review process.

How do I know if someone already downloaded it?

Search the access log for /.git/ requests: a handful of HEAD and config probes are scanners, a long run of objects/pack requests is a download. Rotate the secrets either way.