← Back to the Git Exposure Checker

Git Exposure Checker vs. checking the URL by hand

Typing https://example.com/.git/HEAD into a browser and reading what comes back is a real, fast way to spot the obvious case: a plain 404 means nothing is there, and a line reading ref: refs/heads/main is unmistakably a real, exposed repository. It is also the method most people reach for first, and it genuinely works for that one shape of response. This page names no product or company being compared against — "checking by hand" is a generic manual habit, not a competing tool.

CaseChecking the URL by handThis tool
A plain 404, or a login page, at .git/HEADCorrectly reads as "not exposed"Correctly reads as "not exposed"
The obvious case: body reads ref: refs/heads/mainCorrectly reads as "exposed"Correctly reads as "exposed"
A site that answers every unknown path with HTTP 200 and a custom error page (a "soft 404")Sees status 200, easy to misread as exposedReads the body; a generic error page does not match real git content, so it is reported clean
A detached HEAD — a bare 40-character commit hash instead of a ref: lineEasy to see an unfamiliar hex string and move on, missing a real exposureTreats a bare 40-character hex string as real git-internal content, the same as a ref: line
Corroborating with .git/config tooAn extra request most people skip once HEAD looks clean or looks exposedAlways checks both, and reports when only one corroborates the other
Repeatable in CI on every deployManual, point-in-timeCLI returns a real exit code

Two worked examples

False positive. A site answers every unknown path, including .git/HEAD and .git/config, with HTTP 200 and a generic <html><body>404 - Page Not Found</body></html> page — a "soft 404", common on shared hosting and several static-site setups. Reading the status code alone says "exposed"; neither body matches a real .git/HEAD or .git/config file, so this checker correctly reports the site clean.

False negative. A site's .git/HEAD returns HTTP 200 with the body 4b825dc642cb6eb9a060e54bf8d69288fbee4904 — a bare 40-character commit hash (a detached HEAD), not the ref: refs/heads/main line most people know to look for, and .git/config happens to 404. A person scanning for the familiar ref: format can see an unrecognised string of hex digits and conclude nothing is there. This checker's classification treats a bare 40-character hex string as real git-internal content on its own, so it still reports the directory exposed.

When to still just look

If you only need a one-off sanity check on a site you already suspect, and you know to read the body rather than the status code, looking is fine — it costs nothing and you are already there. The gap this tool closes is correctness on the two cases above, and repeatability: the same body check, run again on every deploy, with an exit code a CI pipeline can act on.

Common questions

What does typing .git/HEAD into a browser by hand actually show you?

Visiting https://example.com/.git/HEAD directly is a real, fast way to spot the obvious case: a plain 404 or a login page means nothing is there, and a line reading "ref: refs/heads/main" is unmistakably a real, exposed git repository. That single glance genuinely works for that one shape of response.

Where does checking by hand produce a false positive?

On a site configured to answer every unknown path with HTTP 200 and a custom error page -- common on shared hosting, several static-site setups, and single-page apps that route unknown paths back to index.html. A person checking only the status code sees 200 at .git/HEAD and concludes the repository is exposed, when the body is actually a generic "page not found" message that happens to carry a 200 status. This checker reads the response body and requires it to actually match a real .git/HEAD file (a ref line or a bare 40-character commit hash) before reporting a finding, so a soft-404 page is correctly reported clean.

Where does checking by hand produce a false negative?

When the repository's .git/HEAD is a detached HEAD -- a bare 40-character hex commit SHA such as 4b825dc642cb6eb9a060e54bf8d69288fbee4904, rather than the familiar "ref: refs/heads/main" line most people know to look for. A person expecting the ref: line can glance at a bare hex string, not recognise it as anything in particular, and move on -- missing a real, exposed .git directory. This checker's classification treats a bare 40-character hex string as real git-internal content on its own, the same as the ref: form, so a detached-HEAD exposure is still reported as a finding.

Why does this matter for CI, not just a one-time check?

A browser check is a point-in-time, manual look at one URL. A deploy pipeline, a new static-site host, or a webserver config change can expose (or stop exposing) a .git directory at any point after the last time someone happened to check. The CLI here returns a real exit code from the same body classification, so it can gate a deploy the same way a linter or test suite does, rather than depending on someone remembering to look again.