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.
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.
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.
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.
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.