How to tell a real exposed .git directory from a soft-404 false positive

Requesting /.git/HEAD and getting back HTTP 200 is not, on its own, evidence your .git directory is exposed. Plenty of webservers and CDNs answer every unknown path with 200 and a custom "not found" page -- a pattern usually called a soft 404. A checker that only looks at the status code flags every site behind one of those as exposed, whether or not a real .git directory is actually there. The fix is to look at what the body contains, not just the status it arrived with.

What the two response bodies actually look like

PathStatusReal exposure bodySoft-404 body
.git/HEAD 200 (both cases) ref: refs/heads/main\n <html><body>404 Not Found</body></html>
.git/config 200 (both cases) [core]\n repositoryformatversion = 0\n <html><body>404 Not Found</body></html>

Both rows return the same status code. A checker that stops at "was it 200?" cannot distinguish the left case from the right one -- it would call both "exposed." The difference only shows up in the body.

The exact shapes this tool matches

The checker on this site parses each body against the narrow shape a real git file actually has, not a loose "contains the word ref" guess:

A generic error page -- even one written to look plausible -- essentially never happens to produce either shape by coincidence.

Worked through on both cases

Case 1: a real exposure. Both probes return git-internal content:

.git/HEAD   -> status 200, body: "ref: refs/heads/main"
.git/config -> status 200, body: "[core]\n\trepositoryformatversion = 0\n"

Verdict: 1 danger-level finding -- the .git directory is publicly readable.

Case 2: a soft-404, same status code on both.

.git/HEAD   -> status 200, body: "<html><body>404 Not Found</body></html>"
.git/config -> status 200, body: "<html><body>404 Not Found</body></html>"

Verdict: no finding -- neither body matched real git-internal content, despite both requests returning 200.

A status-only check sees two identical-looking "200 on .git/HEAD" results and has no way to tell these two cases apart. Checking the body is what makes the difference visible.

Check your own site

The git exposure checker runs a small Node CLI against a site you name, then renders a plain-English report from the JSON it prints -- nothing you paste is uploaded anywhere. Free and local.

← Back to the checker

Common questions

What does a real .git/HEAD file actually contain?

One line, almost always ref: refs/heads/main (or another branch name) with a trailing newline. On a detached HEAD it is instead a bare 40-character hex commit id, such as da39a3ee5e6b4b0d3255bfef95601890afd80709. Nothing else a webserver would plausibly serve at that path looks like either shape.

What does a soft-404 false positive look like on the same path?

A server or CDN configured to answer every unknown path with a 200-status custom error page returns the same status code, 200, for .git/HEAD as a genuinely exposed one -- but the body is generic error-page content such as <html><body>404 Not Found</body></html>, not a git ref or a commit id. A status-only check cannot tell these two 200 responses apart and flags both as exposed.

I got a 200 status on .git/HEAD -- is my site exposed?

Not necessarily. Status 200 only means the server answered; it does not mean the body is real git content. Check the body against the two shapes above: ref: refs/heads/<name> or a bare 40-character hex string. If the body is an error page, a redirect target, or anything else, the path is not exposed even though the status code says 200.