Security basics
Is your .env file exposed? The twelve paths to check
Is your .env file exposed on your own web server? Twelve addresses tell you in a minute, and a hit means everything in the file is already public.

In short
- Two different problems get called a .env file exposed. This one is the file itself sitting on your web server, downloadable by anyone who types the address.
- A host that only serves your built front end cannot do this. A real server can, which is why seven of the eight apps we found were Replit apps.
- Of 30,761 live apps where our check got an answer, 8 were handing out a private file. Twelve addresses tell you whether you are the ninth.
Somebody has told you your .env file is exposed, and you cannot tell from the
sentence whether that is serious. The phrase covers two completely different
situations. One of them is the tool working as designed. The other means a file
you believed was private has been downloadable from your live site, by anyone,
for as long as it has been there.
Here is the part guide after guide runs together: a value from your .env
ending up inside your JavaScript is not the same event as the .env file itself
being served by your web server. The first is the build doing what you asked
it to do when you named the variable VITE_SOMETHING. The second is a filing
mistake, and it is the one to check first, because you can check it yourself from
outside in about a minute.
Think of your live site as a shop counter. Everything on the counter is there to
be taken: the page, the images, the JavaScript, the logo. The back office behind
it holds the things that run the shop, and a visitor has no route to it. A value
compiled into your JavaScript is a line printed in the leaflet on the counter.
The .env file answering at a public address is the folder from the back office,
left out on the counter with everything else.
Is my .env file exposed?
Open your live site in a browser, put /.env on the end of the address, and
press enter.
Three things can come back, and only one of them is a problem.
A page from your app. Most front ends answer every unknown address with their own index page, because that is how a single-page app routes. You get your homepage, or your "not found" screen. Nothing is being served at that path.
An error. A 403 or a 404 means the server was asked and declined. That is the answer you want.
Plain text, with lines in it. Something like SUPABASE_URL=https://… and
SUPABASE_SERVICE_ROLE_KEY=eyJ…, in a monospaced wall with no styling. The file
is being handed to anyone who asks for it, and it has been since the day it was
first served.
Two different things get called an exposed .env file
One is about your bundle. The other is about your server.
| What happened | Where the value is | What it takes to fix |
|---|---|---|
You named a variable VITE_, NEXT_PUBLIC_ or EXPO_PUBLIC_ | Compiled into the JavaScript every visitor downloads | Move the work to a server, then rotate the key. The file was never served. |
| Your web server publishes your project directory | In the file, at https://yoursite/.env | Move the file out of what the server publishes, then rotate everything in it. |
The first row is the common one and it has its own article: the prefix is an instruction to publish, and the build followed it. Everything below is the second row.
Why a Lovable app cannot do this and a Replit app can
Because the two hosts publish different things.
A builder that deploys a static front end hands its host a folder of built files:
some HTML, some JavaScript, some images. Your .env was read during the build
and is not in that folder, so there is nothing at /.env for anyone to fetch.
The host could not serve the file if it wanted to.
A Replit app usually runs its own server, in its own project directory, with your files beside your code. A server told to serve its directory serves every file in it, and it has no way to know that one of them holds your keys. The same is true of anything you deployed to a box of your own.
The numbers follow the architecture. Of 30,761 live apps where this check got an answer, 8 were handing out at least one private file. Seven of the eight were Replit apps, and Replit apps were 3,025 of the 30,761. The eighth was on a domain of its own, which says the same thing a different way: somebody was running their own server.
That is the rarest finding we print, and the only one of the nine with no innocent explanation. If you want the wider reading of what Replit apps actually ship, we scanned 3,042 of them.
The twelve paths to check
Twelve addresses, in the order worth typing. Put each one after your domain.
| Address | What it holds | If it answers with text |
|---|---|---|
/.env | Every key your app was built with | Treat all of it as public and rotate |
/.env.local | The same, from a local run | Same |
/.env.production | The same, from your live deploy | Same |
/.git/config | Your repository's remote address | The whole .git directory is usually readable |
/.git/HEAD | Which branch you are on | Same, and it is the quietest of the twelve |
/.aws/credentials | Long-lived Amazon keys | Rotate at Amazon, then check the bill |
/database.sql | Schema and rows | Every row of every table is public |
/dump.sql | Same | Same |
/backup.sql | Same | Same |
/config.json | Whatever you put in it | Read it and see; tokens live here more often than people expect |
/docker-compose.yml | Service definitions, often with passwords in them | Rotate anything written into it |
/.npmrc | A registry token | Revoke the token |
Our own scan reads all twelve from outside and names the ones that answered. It takes about 20 seconds and needs no account: scan your app.
What a hit actually means
Everything in that file is public right now, and has been since the day it was first served.
You will not find out who read it. A builder gives you no log of a file being fetched that you can go and read, and the request looks like any other request for any other file. What you can be sure of is that somebody tried: automated crawlers walk exactly these twelve paths across the whole internet, all the time, and they do not need to know who you are to find yours.
Five of the eight apps we found were handing out one of the four that are
immediately costly: .env, a .git directory, .aws/credentials, or a .sql
dump. The other three were handing out config.json, docker-compose.yml or
.npmrc. Those three are the ones people assume are harmless, which is exactly
why tokens and database passwords end up written into them.
What this is not is a verdict on your app. An external check reads what your site hands to a stranger, and twelve paths answering with nothing is twelve paths answering with nothing. What else leaks out of a vibe-coded app is the longer list.
The one that ruins a week: a .git directory
A .git directory is not one file. It is your whole history.
Every commit is in there, including the one where you pasted a key in and the
later one where you took it out. That is the part people get wrong about a
.git leak: deleting a secret from the code you have today does nothing about
the version where it was still there, and the version where it was still there is
in the same directory your server is publishing.
The check reads /.git/config and /.git/HEAD because those two are small,
their contents are unmistakable, and either one answering means the directory
itself is being served. From there a reader does not need any special tool. The
format is documented and ordinary software clones it.
If a key was ever in a commit, rotating it is the only thing that helps. Which to do first depends on whether the copy is already out, and that order is worth getting right.
The dump somebody left in the folder
Three of the twelve paths are database dumps: /database.sql, /dump.sql and
/backup.sql.
A dump is every row of every table in one file, with the schema above it. Email addresses, hashed passwords, orders, messages, whatever your app holds. It answers at a public address for the dullest reason in this whole article: somebody did the responsible thing, took a copy of their database, and saved it in the project folder they happened to be standing in.
So the file made to protect the data became the fastest way to read all of it. Where a copy lives is as much of the decision as whether you take one. Three ways to back up a Supabase database covers the options, and Reeve Care keeps a copy of your Supabase database off your own server, verified before it counts as a backup.
How to fix it, and why the order matters
Move the file out of what your server publishes. Then rotate every secret that was in it. In that order.
Rotating first feels like the urgent half, and it is the half that wastes the work. Your new keys go into the same file, the file is still answering at the same address, and you have rotated straight back into the leak. Nothing is safer than it was ten minutes ago.
Where to put it instead, depending on what you are running:
- A host with its own secrets store. Replit Secrets, a platform's environment variables, your hosting dashboard. The value is read by your server at run time and never sits in a file under the published directory.
- Outside the served directory. If you control the server config, point it at a build output folder rather than at the project root. Then a file in the project root has no address at all.
- Not in the repository either, which is a separate habit and a good one. It does nothing about today's problem.
That last point is worth saying plainly, because .gitignore is the answer
everyone reaches for. It keeps the file out of your repository. The file on your
server got there because the server is sitting in your project directory, and git
has no opinion about that.
What to do right now
What to do
- Type all twelve addresses after your own domain, or run the free scan and let it do the typing. Read what comes back rather than the status code.
- If one answers with your own settings, move the file out of the directory your server publishes, and redeploy. That is the step that stops it.
- Then rotate every key, password and token the file held, at each provider. Rotating before the file moves puts the new values back where the old ones were.
- Check billing and provider logs afterwards. Rotation stops what happens next and does nothing about what already happened.
- If a
.gitdirectory was readable, rotate anything that was ever in a commit, not only what is in the code today.
If you would rather work through your app as a list, the 10-minute security checklist covers this alongside the other things worth closing in a newly launched app.
FAQ
How do I know if my .env file is public?
Open your live site in a browser, put /.env on the end of the address, and press enter. If what comes back is a page from your app, nothing is being served at that path. If what comes back is plain text with lines like SUPABASE_URL=https://abcdefghij.supabase.co, the file is being handed to anyone who asks. Do the same for /.env.local and /.env.production, because a server that publishes one usually publishes all three.
Is a .env file in my bundle the same thing?
No, and the fix is different. A value that ends up inside your JavaScript got there because the build was told to put it there, by a variable named VITE_ or NEXT_PUBLIC_ or EXPO_PUBLIC_. The file never left your machine; the value did. That is a separate problem with its own article. This one is about the file itself being served by your web server, which means every value in it is public, prefixed or not.
Why can someone download my .git folder?
Because your server was pointed at your project directory, and .git is a directory inside it like any other. A server has no idea that one of them is your version history. If /.git/config or /.git/HEAD answers with text, the whole directory is usually readable, and that includes every commit you ever made rather than only the code you have now.
I found a backup.sql on my own site, what now?
Move it out of the directory your server publishes before you do anything else, because the file is being handed out while you read this. Then treat every password, key and personal record in it as public: a dump holds the schema and every row of every table. Then work out how the file got there, which is usually that somebody took a backup in the project folder and never moved it.
Do I rotate keys or delete the file first?
Move the file first, then rotate. Rotating while the file is still being served writes the new values into something anyone can download, so you spend the effort and end up where you started. Once nothing answers at that path, rotate every secret the file held, at each provider, and check billing and logs afterwards.