Is an exposed .git folder a security risk?
A request for https://example.com/.git/config should return a 404. If it returns text instead, your repository is downloadable. The .git directory holds the object store, the references that point at your commits, and the repository configuration, and a server that treats it as ordinary static files will hand the whole project history to anyone who asks.
Why this matters
Git is a content-addressable store: commits, trees, and blobs are written under .git/objects and keyed by a hash of their own contents, so the objects alone are enough to rebuild the working tree. The .git/HEAD file is normally a symbolic reference such as ref: refs/heads/master, and the files under .git/refs record the hash at the tip of each branch, which means a crawler can walk from HEAD down to every commit ever made. The repository configuration in .git/config is readable too, and it usually contains a [remote "origin"] section whose URL can embed a username, an access token, or an internal hostname. OWASP’s Web Security Testing Guide lists .git/ among the special paths worth probing for exactly this reason. Once the repository is reconstructed, the attacker gets source code, comments, internal endpoints, and any secret that was ever committed and later deleted, because deletion does not remove history.
Two details make an exposed .git folder worse than it first sounds. First, the crawl is scriptable, so no human needs to understand Git internals to exploit it: a mirroring tool copies the directory and a checkout rebuilds the tree. Second, the leak is silent. Nothing in your logs looks unusual when a scanner fetches a handful of object files, so the exposure can sit unnoticed long after it was first indexed.
How to fix it
The most reliable fix is to publish only build output. If the web root receives compiled or generated files and the repository directory is never copied there, there is no path to block. When the repository does sit under the document root, deny the dotfile before the request reaches any static handler. Nginx can match the path and return a denial, Apache 2.4 can refuse the directory with its authorization module, and an application server such as Express can reject the route before static middleware runs. Managed edge platforms can strip the path in a small Worker.
# Nginx: refuse any path beginning with /.git
location ~ /\.git {
deny all;
return 404;
}
# Apache 2.4: refuse the directory with mod_authz_core
<DirectoryMatch "/\.git">
Require all denied
</DirectoryMatch>
# Express: guard before static middleware
app.use(function (req, res, next) {
if (req.path.startsWith('/.git')) return res.status(404).end();
next();
});
// Cloudflare Worker: block before proxying
export default {
async fetch(request) {
const url = new URL(request.url);
if (url.pathname.startsWith('/.git')) {
return new Response('Not found', { status: 404 });
}
return fetch(request);
},
};
How to verify the fix
Request the paths a scanner tries first and confirm they no longer return content. A 403 or 404 is correct; a 200 with a body is not. Test both a file inside the directory and the directory itself, and check from outside your own network so you are not looking at a cached or local response.
# Expect 403 or 404, not 200
curl -s -o NUL -w "%{http_code}\n" https://example.com/.git/config
curl -s -o NUL -w "%{http_code}\n" https://example.com/.git/HEAD
curl -s -o NUL -w "%{http_code}\n" https://example.com/.git/
Automated check
The Auditor does not check for an exposed .git folder. Its scan fetches a single page and reads the response headers, cookies, and markup, and it never crawls for repository paths, so a reachable /.git is outside what it can see. Run the curl checks above yourself, then use the scan for the header, cookie, CORS, and performance problems it does cover.