ShipWatchFree scan

Is your .env file exposed? How to check (and what to do)

Your .env file holds the passwords and keys your app runs on. It's meant to stay on your machine and your server. If the wrong folder gets deployed, it can end up one URL away from anyone who guesses the address.

Want the quick answer first? The free ShipWatch scan requests /.env and /.git/HEAD on your live domain and tells you if either one is publicly downloadable. It's read-only and needs no signup.

Run the free scan

The problem: anything in the deployed folder can be downloaded

A web host serves the files in one folder. If a file is in that folder, anyone who asks for its path gets it. There's no "private" setting for a file that's been published. Two files cause the most damage when that happens:

Neither file should ever be deployed. They get there in a few common ways:

Publishing files that shouldn't be public is a classic case of what OWASP calls security misconfiguration. It's not a clever attack. Someone only has to type the URL.

How to check your app yourself

Run these against your live domain, not localhost. Only check sites you own or are allowed to test.

1. Ask for the file directly

curl -s -o /dev/null -w "%{http_code} %{content_type}\n" https://yourapp.com/.env
curl -s https://yourapp.com/.env

Read the content, not just the status code. Many hosts (especially for single-page apps) answer any unknown path with your homepage and a 200. If the second command prints HTML, that's your homepage, not a leak. If it prints lines like DATABASE_URL=... or OPENAI_API_KEY=..., the file is public.

2. Try the common variants

Frameworks load several env files, such as .env.local and .env.production (see Vite's list). Check each one:

for f in .env .env.local .env.production .env.production.local .env.development; do
  printf "%-24s " "$f"
  curl -s -o /dev/null -w "%{http_code} %{content_type}\n" "https://yourapp.com/$f"
done

Anything that returns 200 with a text/plain or application/octet-stream type deserves a closer look with curl -s.

3. Check for a public .git folder

curl -s https://yourapp.com/.git/HEAD
curl -s https://yourapp.com/.git/config

If the first command prints ref: refs/heads/main (or another branch name), or a long hexadecimal commit ID, your .git folder is being served. If the second prints a block starting with [core], same result. An HTML page or a 404 is what you want to see.

4. Look at what you actually deploy

Run your build locally and list the hidden files in the output folder (dist, build or out, depending on your tool):

npm run build
find dist -name ".*"

If this prints .env, .git or any other dotfile you didn't intend to publish, it will be deployed. Also open your host's build settings and confirm the output/publish directory is the build folder, not the project root.

5. Check whether .env was ever committed

git ls-files | grep -i "\.env"
git log --all --oneline -- .env

If either prints anything other than an example file like .env.example, the real file is (or was) in your repository. Adding it to .gitignore now won't remove it: gitignore doesn't affect files Git already tracks.

How to fix it

1. Stop publishing the files

Point your host at the build output only, remove any .env from public, and redeploy. Then paste this into your AI tool:

My .env file (or .git folder) is publicly downloadable on my live site. Fix the deploy so only the build output folder is published (dist, build or out, not the project root). Remove any .env file from the public folder. Add .env, .env.* and *.local to .gitignore, keep a .env.example with blank values, and if .env is already tracked by git, untrack it with git rm --cached. Tell me exactly what to change in my host's build settings, then list every variable that was in the file so I can rotate them.

2. Block dotfiles at the server too

Fixing the deploy is the real fix. A server rule is a second layer in case a dotfile slips into the output again. Keep /.well-known/ reachable, since standard services use that path (RFC 8615).

# nginx (inside the server block)
location ~ /\.(?!well-known) { deny all; }

# Apache (.htaccess or vhost)
RedirectMatch 404 "/\.(?!well-known)"

# Cloudflare Workers static assets: .assetsignore in the assets directory
.env*
.git

References: nginx deny, Apache RedirectMatch, Cloudflare .assetsignore.

Add a server-level rule for my host that returns 404 (or 403) for any request to a path starting with a dot, such as /.env, /.env.local and /.git/HEAD, while still allowing /.well-known/. Show me the exact config file and where it goes, and give me curl commands to confirm it works after I deploy.

3. Rotate every key that was in the file. This step isn't optional.

Once a file has been public, assume someone has a copy. Removing it from the server doesn't revoke anything in it. For each value in the exposed .env, create a new key or password in the provider's dashboard, put the new value in your host's environment settings, deploy, then revoke the old one. The OWASP Secrets Management Cheat Sheet covers rotation and revocation in more depth.

My .env file was publicly downloadable, so every value in it must be treated as leaked. Make a checklist of every variable in it with the provider it belongs to and where in that provider's dashboard I create a new key or password and revoke the old one. Mark which ones are the most dangerous (database passwords, service_role or secret keys, payment keys) so I rotate those first. Don't print any secret values.

4. If .git was public, treat your history as public

For a .git-only deep dive (how a repository is rebuilt from a web server and per-host block rules), see .git folder exposed.

Any secret that was ever committed, even one deleted later, should be rotated. GitHub's guide on removing sensitive data makes the same point: revoke or rotate the secret first. Rewriting history is a separate, optional cleanup with real side effects.

5. Look for misuse and confirm the fix

Check each provider's usage and billing pages for activity you don't recognize, and your host's access logs (if it keeps them) for requests to /.env or /.git. Then re-run the curl commands above. You want HTML or a 404 for every one.

Make it hard to happen again

What the free ShipWatch scan checks here (and what it doesn't)

The scan requests /.env and /.git/HEAD at the root of your live domain without following redirects. It reports a public .env as critical only when the response is a 200 containing KEY=value lines and not an HTML page, so a homepage fallback doesn't count. It reports a public .git folder when /.git/HEAD returns a ref: refs/... line. Each finding comes with a fix prompt.

It doesn't check variants like .env.local, files in subfolders, a detached-HEAD .git folder, or your repository's history, so use the steps above for those. The same scan also looks for leaked keys in your page and JavaScript, missing security headers and a missing HTTPS redirect. For the rest of the pre-launch list, see the vibe coding security checklist.

Scan your app free in 30 seconds

Paste your live URL. ShipWatch checks your page and JavaScript for leaked keys (including Supabase service_role), exposed .env and .git, missing security headers and HTTPS redirects, and gives you a plain-English report with fix prompts. Passive and read-only, no signup.

Run the free scan