Skip to content
StackSiren

Is my /.git folder exposed on my website?

By StackSiren · Published

If https://your-domain/.git/config returns a readable Git config (not your SPA's index.html), your repository metadata — and often the full object history — may be downloadable. Block .git on the host and rotate every secret that ever sat in that history.

This is the same family of mistake as a public .env file: the deploy uploaded more than the build output. Attackers have automated dumpers that turn an open .git into a working clone. Only test domains you own.

Why it's serious

A bare or working .git directory holds:

  • Commit history and file contents (including deleted files)
  • Local config (sometimes with url credentials — rare but ugly)
  • Branch names and remotes that reveal private tooling

If the objects are reachable, tools can reconstruct source even when directory listing is off. That source often contains old API keys, .env copies, and internal URLs. Treat a public .git like a full source leak until proven otherwise. Related guides: exposed env files and exposed API keys.

curl check (you own the site)

# 1) Config file — the usual canary
curl -sS -D- -o /tmp/git-config "https://yourapp.com/.git/config"

# 2) HEAD and a well-known object path
curl -sS -D- -o /tmp/git-head "https://yourapp.com/.git/HEAD"
curl -sS -o /dev/null -w "%{http_code}\n" "https://yourapp.com/.git/objects/info/packs"

How to read the results:

Response Meaning
Body starts with [core] or contains repositoryformatversion Exposed. Act now.
200 but body is your index.html / React shell SPA fallback — not a Git config. Still confirm /.git/HEAD is not a real ref: refs/… line.
403 / 404 with empty or error body Usually fine. Re-check after each deploy.
401 from basic auth on the whole site Not a substitute for blocking .git on the public app hostname.

Never run dumpers against other people's sites. On yours, once config is confirmed readable, assume history is next.

Block per host

Goal: the web root should be the build output (dist, build, .next export, etc.), not the repo root that contains .git.

Vercel

Deploy from Git so Vercel builds and serves artefacts — do not upload a folder that includes .git. If you must use a static output directory, set it to dist (Vite) or the framework equivalent. Add a belt-and-braces rewrite in vercel.json:

{
  "rewrites": [],
  "redirects": [],
  "headers": [
    {
      "source": "/.git/(.*)",
      "headers": [{ "key": "X-Robots-Tag", "value": "noindex" }]
    }
  ]
}

Rewrites alone are easy to get wrong; the durable fix is not uploading .git. Confirm with curl after deploy. Framework defaults usually omit .git from the serverless bundle — verify rather than assume.

Netlify (common for Bolt exports)

Publish directory = build output. Add to _redirects or netlify.toml:

# _redirects
/.git/*  /404.html  404

Or in netlify.toml:

[[redirects]]
  from = "/.git/*"
  to = "/404.html"
  status = 404

nginx

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

Same pattern works for other dotfiles (see the .env public check).

Replit / misc static hosts

Serve only the built public folder. If the platform's "always on" web server uses the workspace root, configure the run command or .replit publish path so .git is outside the document root. Confirm with curl on the *.replit.app (or custom) hostname.

Rotate secrets that were in history

Blocking .git stops new downloads. Anyone who already fetched objects still has them.

  1. List secret-shaped paths in history (on your laptop clone):
git log --all --full-history -- .env .env.* '**/secrets.*'
git grep -nE 'sk_live_|sk-proj-|ghp_|xoxb-|service_role' $(git rev-list --all) 2>/dev/null | head
  1. Rotate at the provider (OpenAI, Stripe, Supabase service role, GitHub PATs, webhook secrets). Rotation — not git rm — is what invalidates a stolen copy. See leaked OpenAI key and Stripe secret key exposed for provider-specific first hours.

  2. Put replacements in server-only env vars. Do not recommit .env.

  3. History rewrite (git filter-repo) is optional and disruptive. Do it only after rotation, and only if the repo is or was public and you understand force-push costs. Private repos still need rotation if .git was on a public website.

  4. Re-run the curl canaries. Spot-check JS bundles for keys with the exposed API key checker or the env file checker.

StackSiren's free scan of a site you own probes /.git/config and /.env without displaying secret values. A Deep Scan ($49 one-off or $99/mo add-on on Pro) goes further when you want a wider pass after a confirmed leak.

How dumps usually work (so you know what leaked)

Public write-ups and OWASP's "Git repository publicly accessible" note describe the same pattern: fetch /.git/config and /.git/HEAD, then walk objects/ and pack files until a normal git checkout works. Directory listing is not required. If your canary config is readable, assume a patient attacker can reconstruct trees and blobs — including files you deleted in later commits.

That is why "we removed the key in the last commit" is not enough. History still holds the blob. Rotation at the provider is the fix; rewriting git history is optional cleanup.

CI canary

Add this to post-deploy checks on staging and production hostnames you control:

#!/usr/bin/env bash
set -euo pipefail
url="${1:?usage: check-git.sh https://yourapp.com}"
body=$(curl -fsS "$url/.git/config" || true)
if echo "$body" | grep -q 'repositoryformatversion'; then
  echo "FAIL: readable .git/config on $url" >&2
  exit 1
fi
echo "ok: no git config body on $url"

Fail the pipeline on match. Pair it with the /.env checks from the env-file post so both classes of leak stay closed.

FAQ

curl returns 200 — is that always a leak? No. Many SPAs return 200 + index.html for unknown paths. Open the body. A real Git config starts with [core] / repositoryformatversion.

Is a public GitHub repo the same issue? Public remotes are intentional. An exposed /.git on your production app domain is accidental deployment of VCS metadata, often with extra local commits or env files that never pushed.

Does deleting .git on the server fix stolen clones? No. Rotate secrets first, then ensure new deploys never publish .git.

Can I rely on robots.txt? No. robots.txt does not authenticate. Block at the server.

What about .svn or .hg? Same idea: do not publish VCS metadata directories. Curl the equivalent paths if you used those systems.

How do I stop this recurring? Always set the host's publish directory to build output; add a CI curl canary for /.git/config; keep secrets out of git entirely.

Check your app now

Free passive scan, results in under a minute.

Run a free scan

Fix guides for this topic

Platform checklists: Vercel · Bolt · Replit

Related posts

Sources