3 files your production site shouldn't be serving: .env, .git and source maps
By Pritesh Ghodge, security engineer · Last updated 29 October 2026
Want to know in a minute whether your site serves .env or .git?
The Abbi Labs quick check requests /.env, /.env.local, /.env.production and /.git/HEAD on your live site, and also looks for secret keys in your JavaScript and Supabase tables readable with your public key. No sign-up.
Weekly AI-app security checklist
One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.
Most leaks in AI-built apps come from code: a secret key in the bundle, a table without Row Level Security. These three come from the deploy: files that were never meant to leave your laptop or your build folder, sitting on your production domain where anyone can download them. They're quick to test and usually quick to fix.
1. The .env file
Why it matters: it usually holds every secret your app has: database URLs and passwords, the Supabase service_role key, Stripe and OpenAI keys, SMTP credentials. One download hands over all of them at once.
How it ends up public:
- It was put in (or copied into) the
public/orstatic/folder. Vite and Next.js serve everything inpublic/as-is, at the root of your site. - The whole project folder was uploaded to a plain web server or shared host (FTP, cPanel, an nginx root pointed at the repo) instead of just the build output.
- A build or copy script copies dotfiles into the output folder.
Test it from any terminal:
curl -i https://yoursite.com/.env
curl -i https://yoursite.com/.env.local
curl -i https://yoursite.com/.env.productionA 404 is what you want. A 200 isn't automatically bad: single-page apps often return their index.html for every path. Look at the body. HTML means you're fine; lines like STRIPE_SECRET_KEY=… mean the file is public.
2. The .git folder
Why it matters: a served .git directory lets someone rebuild your repository, including its full history. Free tools exist that do exactly this from a URL. So a key you "removed" months ago is still there in an old commit, along with your source code.
How it ends up public: almost always the same "uploaded the project folder, not the build" mistake, or a server that pulls the repo straight into its web root with git clone / git pull.
Test it:
curl -i https://yoursite.com/.git/HEADIf the body is something like ref: refs/heads/main, the folder is being served. Anything else (a 404, or your app's HTML) is fine.
3. Production source maps (.js.map)
Why it matters: a source map turns your minified JavaScript back into the original files, with names, comments and folder structure. That's less serious than a .env, because the code already runs in the browser, but it makes it much easier to find hard-coded keys, internal API routes and admin-only logic. If a secret is in your source, it's in the map too.
How it ends up public:
- Someone turned maps on for debugging and left them on:
build.sourcemap: truein Vite, orproductionBrowserSourceMaps: truein Next.js. Both are off by default. build.sourcemap: 'hidden'in Vite. This still writes the .map files; it only drops the comment that points to them. Deploy them and anyone can fetchapp.js.mapby adding.mapto the script URL.- An error-tracking setup (Sentry and similar) generates maps to upload, but the files are also deployed with the site.
Test it: open your site, then DevTools → Network → JS, copy the URL of one of your script files and add .map:
curl -s https://yoursite.com/assets/index-abc123.js.map | head -c 200If the output starts with something like {"version":3,"sources":[, your source maps are public. Also check the last line of the script itself for a //# sourceMappingURL= comment.
How to fix them
Deploy only the build output. This one habit prevents all three of the .env and .git cases. Your host should serve dist/ (Vite) or the framework's own output, never the project root. Managed hosts like Vercel and Netlify do this when they build your app from the repo; manual uploads and self-managed servers are where it goes wrong.
Keep secrets out of public/. Nothing in that folder is private. Make sure .env* is in .gitignore too.
Block dotfiles at the web server if you run one, while still allowing /.well-known/ (used for HTTPS certificates and domain verification):
# nginx
location ~ /\.(?!well-known) { deny all; }
# Apache (.htaccess)
RedirectMatch 404 /\.(?!well-known)Turn production source maps off, or keep them away from the public:
// vite.config.ts
export default defineConfig({ build: { sourcemap: false } });
// next.config.js (false is the default; just don't set it to true)
module.exports = { productionBrowserSourceMaps: false };If you need maps for error tracking, upload them to the tracker during the build and delete them before deploy. Sentry's build plugins have an option for deleting files after upload.
A prompt for Lovable, Bolt or Cursor:
Security fix: make sure production never serves .env files, the .git
folder or JavaScript source maps.
1. Confirm no .env file is inside public/ or copied into the build output.
2. Set build.sourcemap to false for production (not 'hidden').
3. Show me the deploy/output settings so I can confirm only the build
folder is published. Show the diff before applying.If one of them was exposed
- Rotate first, then clean up. For a .env or .git exposure, rotate every secret that was ever in the file or in the repo's history, starting with anything that can spend money or bypass your database rules (Stripe, OpenAI, the Supabase service_role key). Deleting the file doesn't revoke anything. See what to do if your service_role key leaked.
- Remove the file and redeploy, then re-run the curl tests above against the live domain.
- Look for misuse in each provider's logs or dashboard for the period the file was public: unfamiliar API usage, new users, changed data.
- For source maps, search the original source for hard-coded keys. If you find one, treat it as leaked and rotate it, because it was in the bundle as well.
Re-check after deploys
These usually appear because of a one-off change: a new upload method, a debugging flag left on, a build script an AI tool added. So a check before launch isn't enough on its own. Abbi Labs checks for exposed .env and .git files in the free quick check, looks for public source maps on verified domains, and Monitor re-scans weekly and after every deploy, emailing you only when something new appears.
FAQ
How do I check if my .env file is exposed?
Request it from your live domain: curl -i https://yoursite.com/.env (and /.env.local, /.env.production). A 404 is good. If you get a 200 with lines like NAME=value, it's public. Watch out for single-page apps that return their index.html with a 200 for every path: that's not a leak, so read the body, not just the status code.
Are source maps a security risk?
A public source map isn't a breach on its own, but it hands anyone your original, readable source code, including comments, internal API routes and any secret someone hard-coded. If a key is in the source, it's also in the minified bundle, so the map mostly makes it easier to find. Most production builds don't need public maps.
My .env was public for a while. Is deleting it enough?
No. Assume every value in it was copied. Rotate each secret at its provider (database, Stripe, OpenAI, email and so on), then remove the file and block dotfiles at the host. Deleting the file only stops new downloads.
Does 'hidden' source maps in Vite make them private?
No. build.sourcemap: 'hidden' still writes the .map files; it only removes the comment that points to them. If the files are deployed, anyone can fetch app.js.map by guessing the name. Delete them after uploading to your error tracker, or don't deploy them.
Does Abbi Labs check for these?
Yes. The free quick check on the home page (no sign-up) requests /.env, /.env.local, /.env.production and /.git/HEAD and only flags a real hit (variable lines or a git ref, not an HTML error page). Full scans on a domain you've verified you own also look for public source maps next to the JavaScript files your home page loads.
Related guides
- OpenAI, Claude or Gemini API key exposed in your frontend? What to do now
- Lovable app security checklist (from a security engineer)
- NEXT_PUBLIC_ and VITE_ variables are shipped to every visitor
- Supabase service_role key exposed? Do this in the next 15 minutes
- How to check if your Supabase tables are readable by anyone
- Security headers for Lovable, Bolt, v0 and Cursor apps: CSP, HSTS and clickjacking
- Supabase "42501 permission denied for table": fix it with a GRANT, not by turning off RLS