Is your .env or .git folder public? How to check in 30 seconds
Two curl commands show whether your .env or .git folder is public. What a leak looks like, why deleting the file is not enough, and the rotation steps.
LLaunchScaler·Published ·7 min read
To check whether your .env or .git folder is public, request https://yoursite.com/.env and https://yoursite.com/.git/HEAD: both must return 403 or 404. If either returns 200 with the file's real contents (KEY=value lines, or ref: refs/heads/main), treat every secret in it as stolen, rotate them all, and only then fix the server.
It takes 30 seconds, and it is worth doing on every production and staging host you own, because the file that leaks is rarely on the one you remembered to check.
How do you check if your .env or .git folder is public?
Run two commands from any terminal. The first prints only the status code for /.env; the second prints the body of /.git/HEAD, which is a single line on any Git checkout. A 403 or 404 on both is a pass. Anything else needs the body read before you decide.
Run curl -s -o /dev/null -w '%{http_code}' https://yoursite.com/.env. A 403 or 404 is safe. A 200 needs a second look: if the body contains KEY=value lines, your secrets are public; if it is your homepage HTML, a single-page app fallback is answering and nothing leaked.
200 with lines like DATABASE_URL=postgres://... or STRIPE_SECRET_KEY=...
/.env.local, /.env.production, /.env.backup
403 or 404
Same as above; build tools create several variants
/.git/HEAD
403 or 404
200 with ref: refs/heads/main (or another branch name)
/.git/config
403 or 404
200 with a [remote "origin"] section and your repository URL
One false alarm is common. A single-page app often answers every unknown path with index.html and a 200, so /.env returns your homepage. That is not a leak. Check with curl -s https://yoursite.com/.env | head -5: HTML means the fallback answered; KEY=value lines mean the file is public.
Run the same commands against www, any staging or preview hostnames, and old subdomains still on a server you control. OWASP's testing guide lists .env, .pem, .key, .htpasswd and .npmrc among files that should generally never be publicly served, and it lists .git/ among the paths to probe, so those are worth a request too.
Why is a public .git folder so dangerous?
Because it is the whole repository, not a config file. The Pro Git book describes the .git directory as containing almost everything Git stores and manipulates, and says copying it gives you nearly everything you need to back up or clone the repository. A served .git folder lets anyone rebuild your source code and its history.
What that exposes:
Every file in every commit, including a .env or key file that was committed once and deleted in a later commit. Deleting a file removes it from the latest version, not from history.
objects/, which holds the content, and refs/, which points at every branch and tag.
config, which names your remote and sometimes contains a token embedded in the remote URL.
Your server-side code, which shows an attacker your routes, queries and admin paths.
Directory listing does not need to be on for this to work. The file names inside .git are predictable (HEAD, config, refs/heads/main), and each file points to the next, so the repository can be fetched one request at a time.
How do .env and .git files end up public?
They get copied into the folder the web server serves. Static hosts and web servers serve whatever is in that folder, with no idea which files were meant to be private. OWASP's advice is to design applications so they do not create or rely on files stored in the web directory trees the server exposes.
The usual ways it happens:
The whole project folder is the web root. A server pointed at the repository checkout (/var/www/app, with .git and .env inside it) serves both unless told not to.
A deploy copies everything: rsync -a ./ server:/var/www/, scp -r, an FTP upload of the project folder, or a Dockerfile COPY . . into a directory a web server then serves.
A file lands in the build output. Vite copies everything in public/ to the root of dist as-is, so a .env saved there ships to production. Netlify serves the publish directory, and every static host serves its output folder the same way.
A backup is left behind: .env.bak, .env.old, site.zip or a database dump in the web root.
Check the build output itself before deploying: find dist -name ".*" (or .next, build, out, whatever your output folder is) should list nothing sensitive.
Are NEXT_PUBLIC_ and VITE_ variables exposed?
Yes, by design, to every visitor. Next.js inlines any variable prefixed NEXT_PUBLIC_ into the JavaScript bundle at build time, and Vite exposes every VITE_ variable in client-side code after bundling. Vite's docs say these "should not contain sensitive information such as API keys."
The rule is simple: a prefixed variable is public, so only values meant for browsers belong there.
Fine with a public prefix
Never with a public prefix
Analytics or tag IDs
Payment provider secret keys
A payment provider's publishable key
Database URLs and passwords
A Supabase URL and publishable (anon) key, when every table has row-level security
To check what actually shipped, build the app and search the output for key prefixes you know, for example grep -rn "sk_live\|service_role" .next/static dist 2>/dev/null. Next.js also notes that NEXT_PUBLIC_ values are frozen at build time, so a key you remove from the environment stays in the old bundle until you rebuild and redeploy. That makes rotation the real fix here too.
Do source maps leak your code too?
They can. A source map lets anyone rebuild your original, unminified client code, comments and file paths included. It never contains server secrets, but it does hand over the logic of your front end and often the names of internal endpoints. Both major build tools leave them off in production unless you opt in.
Next.js: the docs say production browser source maps are disabled "to prevent you leaking your source on the client" unless you set productionBrowserSourceMaps: true, in which case Next.js serves the .map files next to your JavaScript.
Vite: build.sourcemap defaults to false. true writes a separate .map file with a comment pointing to it, 'inline' embeds the map in the bundle, and 'hidden' writes the file without the comment, for uploading to an error tracker.
To check, open one of your production JavaScript files, look for a //# sourceMappingURL= comment at the end, and request that URL. A 200 means the map is public. If you need maps for error reporting, use hidden maps, upload them privately, and keep them out of the deployed folder.
What do you do if your .env or .git was exposed?
Rotate first, fix second. GitHub's guidance for leaked secrets is that the first step is to revoke or rotate them; removing the file or rewriting history does nothing about copies already made. Work through it in this order:
Block the path right away (see the next section) so no new copies are made.
List every secret the file held, and every secret ever committed to the repository if .git was exposed. Check old commits: git log -p --all -- .env shows every version of a tracked .env.
Rotate each one at its provider: generate a new key, deploy it, then revoke the old one. Include database passwords, payment and email keys, OAuth client secrets, JWT signing secrets and webhook secrets.
Check each provider's logs and dashboards for activity you do not recognise from the moment the file became reachable.
If secrets were committed, rewrite history with git filter-repo (GitHub says you need a version with the --sensitive-data-removal flag, 2.47 or later), and remember GitHub's warning that you cannot remove data from other people's clones.
Purge your CDN cache for the exposed paths, since a cached copy can outlive the fix on the origin.
Add a check to your deploy so it cannot happen again: fail the build if the output folder contains a dotfile, or run the two curl commands after every deploy.
How do you block dotfiles on the server?
Keep them out of the served folder first, and add a server rule as a backstop. The rule should refuse every path beginning with a dot, except /.well-known/, which certificate renewal and security.txt rely on.
For Nginx, inside the server block:
location ~ /\.(?!well-known) {
return 404;
}
On platforms like Vercel and Netlify, which serve a build output rather than your repository, the fix is to keep secrets out of that output: store them in the platform's environment settings, keep .env files out of public/, and make sure *.local is in .gitignore, as Vite's docs advise.
To check your live site for these files without typing each path, run the free scan on LaunchScaler. It needs only your URL and no account, and its free security checks request /.env and its common variants, /.git/HEAD and /.git/config, published JavaScript source maps, database dumps and archives, open directory listings and unauthenticated admin or debug endpoints, and they read the body, so an HTML fallback page is not reported as a leak. They run with 156 checks across 6 of its 7 categories.
The .git directory holds almost everything Git stores: every file version, every commit and the remote configuration. Anyone who can download it can rebuild your source code and history, including secrets that were committed once and deleted later.
03
Is deleting an exposed .env file enough?
No. Assume every value in it was copied the moment it was reachable. Rotate every key, token and password the file ever held, then check each provider's logs for use you do not recognise.
04
Are NEXT_PUBLIC_ and VITE_ variables secret?
No. Next.js inlines NEXT_PUBLIC_ values into the JavaScript sent to the browser at build time, and Vite's docs say VITE_ variables end up in the client bundle and should not contain sensitive information. Only put values there that any visitor may see.