ShipWatchFree scan

Is your .git folder public? Check /.git/HEAD and block it

If your web server hands out files from a folder that still contains .git, anyone can download your repository: every file and every old commit, including secrets you deleted months ago. This page is a .git-only deep dive: how the attack works, how to test it, and how to block it on common hosts.

Short answer: Run curl -s https://yourapp.com/.git/HEAD. If it prints ref: refs/heads/main or similar, your repository is downloadable. Publish only your build folder, add a server rule that blocks /.git, and rotate every secret that was ever committed.

For a quick check of both /.env and /.git together, see Is your .env file exposed?. This page is just about the repository folder.

Why a public .git folder is worse than it sounds

The .git directory is your whole repository: HEAD points at the current branch, refs/ holds commit IDs, objects/ holds every version of every file, and config can contain the remote URL. Directory listing doesn't need to be on. An attacker starts from HEAD, follows refs to commit IDs, and requests each object by its hash-derived path. Free tools automate this, and the result is a working clone of your project.

What they get: server code, config files and any secret that was ever committed, even one you removed later. That's why the fix includes rotation, not just blocking.

How it ends up public

Hosts like Vercel and Netlify publish only the output directory you configure, so the usual cause there is a publish directory set to the repo root.

Test it with curl [manual]

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

Read the content, not just the status code. ref: refs/heads/main (or a 40-character commit hash) means exposed. HTML means your host is serving its fallback page, which is fine. A config response starting with [core] confirms it.

How to block it

1. Publish only the build folder

This is the real fix. Set your host's publish or output directory to the build folder, and make sure your Dockerfile copies only that folder into the web root.

2. Add a deny rule anyway

Nginx, inside the server block:

location ~ /\.git {
  deny all;
}

Apache (.htaccess or vhost):

RedirectMatch 404 /\.git

Cloudflare in front of any host: add a WAF custom rule that blocks requests where the URI path starts with /.git.

Netlify, Vercel, Cloudflare Pages: fix the publish directory and redeploy. If .git isn't in the published folder, there's nothing to serve.

My site serves the /.git folder. Change the deploy so only the build output folder is published, never the repository root. Add a server rule for my host that returns 403 or 404 for any path starting with /.git, and show me how to confirm with curl that /.git/HEAD no longer returns "ref: refs/heads/...".

3. Treat the history as leaked [manual]

List every secret that ever appeared in the repo:

gitleaks git -v .

Rotate each one using the rotate-first playbook. Rewriting history afterwards keeps it out of future copies, but it can't recall the copy someone already downloaded.

Make it stick

What the free scan checks here (and what it doesn't)

The scan requests /.git/HEAD without following redirects and flags it only when the response is a 200 whose body starts with ref: refs/. It also checks /.env, scans your HTML and up to 25 linked scripts for Stripe, OpenAI, Anthropic, AWS, Google and Supabase secret keys, and checks HTTPS, its redirect, and four security headers.

It doesn't try to download objects or rebuild your repository, and a detached HEAD that holds a bare commit hash won't match its check. Use the curl test above to cover that [manual].

FAQ

My .git/HEAD returns 200 but the page looks like my homepage. Am I exposed?
Probably not. Single-page apps often return index.html for every path. Look at the content: an exposed HEAD file is one short line like ref: refs/heads/main. The free scan checks for that line, not just the status code.
I deleted the secret in a later commit. Is it still exposed?
Yes, if .git was public. Old commits are stored in the objects folder, so anyone who downloaded the repository can check out the commit that had the secret. Rotate it.
How is this different from the .env check?
The .env check covers both files briefly. This guide goes deeper on .git: how a repository is rebuilt from a web server, host-specific block rules, and dealing with history.

Scan your app free in 30 seconds

Paste your live URL for a plain-English report with fix prompts. If you keep shipping, weekly monitoring re-checks it: Solo $19/mo or Pro $49/mo.

Scan my app free