← Git Exposure Checker

How to fix a publicly exposed .git directory

If Git Exposure Checker (or any other check) has just told you a site's .git directory is publicly readable, the order below limits damage first and cleans up second -- doing it in the other order leaves a window where the directory is still readable while you're deciding what else to do.

1. Block web access to .git immediately

This is the fastest step and it stops the bleeding regardless of what caused it. Add a server-level rule that refuses any request path containing /.git/ before it reaches your application, and ship it as its own change first:

# nginx
location ~ /\.git { deny all; return 404; }

# Apache (.htaccess or vhost)
RedirectMatch 404 /\.git

# Caddy
@dotgit path_regexp \.git
respond @dotgit 404

A CDN or reverse proxy in front of the origin needs the same rule, or a request that bypasses it still reaches an unprotected origin.

2. Treat this as a full-history exposure, not a current-code exposure

A working .git directory lets a free tool such as git-dumper or GitTools reconstruct the entire repository, not just the files currently deployed. That includes every commit -- files and secrets that were added and later removed, branches that were merged and deleted, anything squashed away in a rebase. Assume anyone who found the exposure took a full clone before you fixed step 1, and plan the rest of this around that assumption rather than around what the current checkout contains.

3. Rotate every credential the history ever held

Search the full history for anything that looks like a key, password, or token -- not a grep of the current tree, which only shows what's still there:

git log -p --all | grep -iE "api[_-]?key|secret|password|token" 

Rotate each match at the service that issued it (revoke and reissue, don't just change a config value locally). A credential that was ever committed and later deleted from the code is still valid at its issuer until you rotate it there -- removing it from the repository does nothing to a copy someone already has.

4. Fix the deploy step, not just the server rule

The rule in step 1 is a stopgap that depends on staying in every server config forever. Find out how .git ended up inside what gets served -- the common cause is a deploy script that copies or syncs the whole repository checkout (rsync, a Dockerfile COPY ., an FTP push of the working directory) instead of a build output that never contained .git in the first place. Publish a built artifact, an export, or an explicit file list instead, and the exposure class stops being possible rather than merely blocked.

Once the fix ships, re-run the checker to confirm .git/HEAD and .git/config are no longer reachable: Git Exposure Checker.