The files you forgot you deployed

Is your .git folder public? Strangers can rebuild your source from it

A public .git directory is your entire source history, downloadable.

Not just the current code. The history. Every commit, every branch, every file you ever deleted because it had a password in it. If the .git folder ended up under your web root, a stranger can reconstruct the whole repository on their laptop without ever touching your server in a way you'd notice.

Why the folder is the whole repo

When you run git init, Git creates a .git directory and quietly stores everything important in it. The commit graph, the object database, the config, the refs. Your working files are just a checkout of what's in there. The .git folder is the repository. Delete your source files and you can recreate them from .git. That's by design and it's great, right up until that folder is served over HTTP.

The two files that give it away are /.git/config and /.git/HEAD. They're small, predictable, and present in every repo. A scanner requests them first. If either returns a 200, the rest of the directory is almost certainly fetchable too, and the contents are structured enough that automated tools can walk them.

request
GET /.git/config HTTP/1.1\nHost: yoursite.com
response
HTTP/1.1 200 OK
Content-Type: text/plain
[core]
repositoryformatversion = 0
[remote "origin"]
url = [email protected]:acme/app.git
If /.git/config is readable, the object store usually is too. That's enough to rebuild the repo.

How the folder gets deployed

The classic cause is deploying with git pull or git clone straight onto the server, then pointing the web root at the checkout. The working files are exactly what you want to serve. The .git folder rides along with them, sitting one level up from public view but still inside the directory the web server hands out. Nobody links to it. It's just there.

Build pipelines do it too. A docker build that copies the project with COPY . . brings .git into the image. An rsync deploy without an exclude pattern syncs it up. FTP uploads of a whole project folder carry it. In every case the site works perfectly and the folder is silently public. The same root cause that exposes your .env file exposes your .git, because both are dotfiles that live alongside the code you meant to serve.

What they pull out of it

There are off-the-shelf tools, git-dumper is the well-known one, that take a base URL and walk a public .git directory until they've reconstructed the full repository locally. The attacker runs one command and waits.

$ yoursite.com/.git
$ git-dumper https://yoursite.com/.git ./loot
[+] Fetching .git/config
[+] Fetching .git/HEAD
[+] Fetching 1,284 objects
[+] Running git checkout .
$ git -C loot log --oneline | head -1
9c1f2a3 hotfix: hardcode prod db creds (temporary)
One command reconstructs the repo. Then they read the history for the commits you regret.

Now they have your source code, which means they can read your auth logic, find the endpoints you didn't link anywhere, and study exactly how your app validates input. Reading source turns blind guessing into targeted work.

But the history is the real prize. Developers commit secrets, notice, and delete them in a later commit, thinking it's gone. It isn't. It's still in the object store, recoverable with git log -p or git show. A config file with a database password, an .env that got committed once and removed, a private deploy key, an API token in a test fixture. All of it is in the history a git-dumper pull hands over. The deletion only removed it from the current checkout.

Closing it

Confirm it the way an attacker would. Request https://yoursite.com/.git/config and https://yoursite.com/.git/HEAD. A 404 or 403 means you're fine. A 200 means the folder is public and you should assume the full history has already been pulled, because this is one of the first things crawlers check.

The fix is to stop serving the directory. Block any request path containing /.git, and ideally any dotfile, at the web server. Better still, don't deploy the .git folder at all: build artifacts, or exclude it from your image and your sync.

# Apache: .git deployed under the web root,
# served like any other directory
<Directory /var/www/app>
Require all granted
</Directory>
Deny the path at the server, and keep .git out of the deploy in the first place.

If you rotate anything, rotate every secret that ever lived in that history. The deleted ones are the dangerous part, because they're still sitting in the objects long after they left your current code.

Whether your .git folder is reachable is the kind of thing you can know for certain from outside, before someone with worse intentions checks for you. That's what SurfaceCheckr does: it asks your server for /.git/config and /.git/HEAD, exactly the first two requests a git-dumper run makes, and tells you whether either comes back with content. It stops at that question. We don't pull the objects or read a line of your history, because confirming the door is open is enough to act on.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.