Exposed .env File: How Secrets Leak and How to Recover

Exposed .env File: How Secrets Leak and How to Recover

Written by

in

An exposed .env file is one of the fastest ways a working app turns into a breached one. That single file holds the secrets your code needs to run: the database URL, your cloud keys, the API key for your model provider, the signing secret behind your sessions. When a stranger can read it, they do not have to break anything clever. They log in with your own keys. This post walks through how a .env ends up reachable, what an attacker gets from it, how to check your own app, and how to recover once one has leaked.

The examples use an invented app called Acme Notes, so nothing here points at a real target.

What is a .env file and why does it matter?

A .env file is a plain text list of name and value pairs that your app reads at startup. It exists so that secrets live outside your code instead of being pasted into it. A typical one looks like this.

DATABASE_URL=postgres://acme:s3cr3t@db.internal:5432/acme
STRIPE_SECRET_KEY=sk_live_51Hxxxxxxxxxxxxxxxxxx
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxx
SESSION_SECRET=9f2b7c1a4e8d
SMTP_PASSWORD=hunter2hunter2

Every line is a key to something real. The database URL opens your data. The payment key moves money. The model provider key spends your budget. The session secret lets an attacker forge a signed cookie and become any user. This is why the file is meant to stay on the server and never reach a browser, a public repository, or a public bucket. The moment it does, all of those doors open at once.

How does a .env file get exposed?

There are two paths that account for most cases, plus a few quieter ones.

The first is serving it over the web. If the file gets deployed into a public web root, or the web server is willing to hand back dotfiles, then a plain request returns it. An attacker does not guess a password. They ask for the file by name.

GET /.env HTTP/1.1
Host: acme-notes.example.com

If the response is your key list, the app is already compromised and you will not see it in any login log, because nobody logged in. They read a file.

The second path is git. Someone commits the .env before adding it to .gitignore, then pushes to a repository that is public, or private now and public later. Deleting the file in a later commit does not help, because git keeps history. The secret still sits in an old commit that anyone can check out. A repository that went public for one hour is a repository whose entire history is public forever.

The quieter paths matter too. A .env can leak through a storage bucket that was set to public, through a JavaScript source map that bundles server config by mistake, through a Docker image layer where the file was copied in and never removed, or through a backup archive left in a reachable folder. Same file, different door.

Deleting a leaked secret does not un leak it. Once a value has left your control, the only safe assumption is that a stranger has a copy.

Why does an exposed .env file happen so often in AI built apps?

Because the fast path skips the safe step. When you build with an AI coding tool or an app generator, the generator writes your secrets straight into a .env for you, which is correct. What it usually does not do is wire up the deployment so that file stays private. Tutorials say “just deploy” and move on. The step where you add .env to .gitignore before the first commit gets skipped, because the first commit felt like a formality. The result is an exposed .env file sitting one request or one git clone away from anyone who looks.

This is the same shape as other secret leaks in quickly built apps. It is a cousin of hardcoded API keys in the frontend, where the secret ships inside the browser bundle instead. Both come from the same habit: treating a secret like configuration instead of like a live credential. For the wider picture, the vibe coded app security hub maps the five failure shapes that keep showing up, and this is one of them. It also sits squarely in the access control category, because a leaked key is an access control failure that skipped the front door entirely.

The stakes are not abstract. A leaked model provider key lets an attacker run their own traffic on your account until the bill arrives, which is the same money drain covered in denial of wallet. A leaked database URL hands over every row. A leaked cloud key can spin up servers in your name.

How do you check your own app?

Three checks, all read only, all on an app you own.

  • Request the file over the web. From a browser or with curl, ask for /.env on your own domain, then try the common variants: /.env.local, /.env.production, /.env.bak, and /env. Anything other than a clean not found is a problem.
  • Grep your git history. The file can be gone from your working tree and still live in an old commit. Search the whole history, not just the current files.
  • Scan the repository with a secret scanner. A tool that walks every commit will flag keys you forgot were ever there, including ones in files that are not named .env.

The git history check is the one people miss. A single command reads the past.

git log --all --full-history -- .env
git log -p --all -S 'sk_live_'

If either returns a commit, that secret has been in your history and must be treated as leaked, even if the file is deleted today.

How do you fix it and recover?

Fixing the leak and recovering from it are two different jobs. Do both.

Stop serving the file. The .env should never sit in the web root in the first place. Keep it outside the folder your web server publishes, and configure the server to deny dotfiles so a request for /.env returns nothing. Better still, move secrets into your platform’s secret manager and stop shipping a file at all.

Keep it out of git from the first commit. Add these lines to .gitignore before you commit anything, and commit an example file with blank values so teammates know which keys exist.

.env
.env.*
!.env.example

Rotate every secret that was ever exposed. This is the part that gets skipped, and it is the part that matters most. Deleting the file, making the repository private, or force pushing a cleaner history does not help you, because a copy may already be gone. Every key that appeared in an exposed .env file has to be regenerated at its source: new database password, new payment key, new model provider key, new session secret, new SMTP password. Until you rotate, the old keys still work for whoever grabbed them.

Rotation is uncomfortable because it means touching live services, but a deleted secret that still works is not fixed. It is just hidden from you.

What should you take away?

A .env leak is a quiet failure. No alarm fires, the app keeps working, and the only sign is a request for a file that should never have answered. That is exactly the kind of assumption an autonomous researcher is built to test: does the server hand back what it should keep, and does an old key still open a door. In our own early work, a frontier model drove the full methodology on its own and identified and verified real access control and injection issues in test applications it had not seen before. If checking your own assumptions before an attacker does sounds useful, you can read more about UnboundCompute.

Frequently asked questions

What is an exposed .env file?

It is a .env file holding your app’s secrets that has become reachable by someone who should not see it. The two common paths are the file being served over the web, so a request for /.env returns it, and the file being committed to a git repository that is or becomes public. Either way an attacker reads your keys without breaking in.

What can an attacker do with a leaked .env?

Whatever the keys allow. A database URL opens your data, a payment key can move money, a model provider key spends your budget, and a session secret lets an attacker forge signed cookies and act as any user. Because these are live credentials, the attacker skips the login screen entirely and there is no failed login to alert you.

I deleted my .env from git. Am I safe?

No. Git keeps history, so the secret still lives in the old commit even after you delete the file. Anyone who cloned the repository, or who reads a public history, still has the values. Deletion does not un leak a secret. You have to rotate every key that was ever committed.

How do I check if my .env is reachable over the web?

From a browser or with curl, request /.env on your own domain and try the common variants like /.env.local, /.env.production, and /.env.bak. Only test an app you own. Anything other than a clean not found means the file is being served and the app is already exposed.

How do I keep a .env out of git?

Add the file to .gitignore before your first commit, using lines like .env and .env.* while keeping an .env.example with blank values so teammates know which keys exist. If a secret has already been committed, scan the full history with a secret scanner and treat every value it finds as leaked.

What is the single most important recovery step?

Rotate every secret that was ever exposed. Regenerate the database password, payment key, model provider key, session secret, and any other value at its source. Until you rotate, the old keys still work for whoever grabbed a copy, so a deleted or hidden secret is not a fixed one.


Put an autonomous researcher on your own systems

UnboundCompute is an autonomous security researcher that reasons about how an application fits together and proves the access control and injection bugs it finds. We are opening a small number of founding design partner seats: private early access pointed at a staging target you choose, and a say in what it looks for. If your team ships software worth pressure testing, apply to the design partner program.