Exposed .env Files and What Attackers Do With Them

By · Updated · 4 min read

Modern applications keep their secrets in an environment file rather than in code — database credentials, mail credentials, payment API keys, cloud storage tokens, the application encryption key. It is good practice, with one catastrophic failure mode: if that file sits inside your web root and the server serves it as plain text, every one of those secrets is available to anybody who requests the URL.

What is in the file

On a typical Laravel or similar application, an environment file holds the application key used to encrypt and sign data, full database credentials, mail server credentials, and API keys for whatever services the application integrates — payment processors, cloud storage, mapping, analytics, messaging.

That is not a partial compromise. Someone holding that file can connect to your database directly if it accepts remote connections, send mail as you, use your paid API accounts, read your cloud storage, and in many frameworks forge signed session data using the application key. It is closer to handing over the application than to a typical website hack.

How they get exposed

The most common cause is a document root pointed at the project directory rather than the public subdirectory. Frameworks like Laravel are built so that only a public folder is web-accessible and everything else sits above it — but if the site was deployed by copying files up and pointing the domain at the project root, every file becomes reachable by URL.

The other causes are a web server that has not been told to deny dotfiles, a backup or editor copy of the file left with a different extension that gets served as plain text, and the file being committed to a public repository. Automated scanners request these paths constantly on every domain they encounter, so an exposed file is typically found within hours rather than eventually.

Checking whether yours is exposed

Request the path directly in a browser. If you get a download or a page of readable configuration, it is exposed and you should treat every value in it as public. If you get a 404 or a 403, it is not reachable by that route.

Check the obvious variants too, since a single denial rule often misses them: backup copies with an added extension, editor swap files, and copies with alternative names. Also check whether your repository is public and whether the file was ever committed, since history persists even after deletion.

If it was exposed

Rotate everything in it, and assume the worst about timing — if you cannot prove when exposure began, assume it was long enough. Change the database password, regenerate the application key, change mail credentials, and revoke and reissue every API key and token listed.

Regenerating the application key deserves a note, because it invalidates anything encrypted or signed with the old one, which may include session data and certain stored values. That is disruptive, and it is still the right call, because leaving it in place means anyone with the old key can forge signed data. Check your billing on every integrated service too — abuse of a stolen API key shows up there first, and sometimes expensively.

Fixing the exposure itself

The correct fix is to point your document root at the application's public directory so nothing above it is web-accessible. That is the design the framework expects and it solves the whole category of problem rather than one file.

If you cannot change the document root, deny access to dotfiles and to the environment file explicitly in your server configuration, and verify the denial actually works by requesting the path afterwards. Verifying matters — a rule that looks right and does not apply is worse than no rule, because you will stop checking.

Then look for what was done with it

An exposed environment file is often the beginning rather than the end. Check your database for unexpected admin accounts and injected content, check your cloud storage for files you did not put there, check your mail service for messages sent, and check application logs for unfamiliar access patterns.

Then work through the ordinary incident questions — my guides on reading access logs and rotating credentials cover establishing what happened and closing it out, and breach notification obligations covers what follows if customer data was reachable. If you would rather have the whole exposure assessed properly, that is work my cleanup service takes on.

Related reading: hacked Laravel applications, Node and Next.js sites. See also every platform I clean.

Common questions

How do I check if my .env file is exposed?

Request the path directly in a browser. A download or a page of readable configuration means it is exposed. Also test common variants — backup copies with added extensions, editor swap files, and alternative names — because a denial rule covering only the exact filename frequently misses those.

What should I rotate if it was exposed?

Everything in the file. Database password, application key, mail credentials, and every API key and token listed. Also check billing on each integrated service, since abuse of a stolen key often shows up as unexpected usage before it shows up anywhere else.

Why does regenerating the application key matter?

Because the framework uses it to encrypt and sign data. Anyone holding the old key can potentially forge signed values, including session data. Regenerating invalidates existing encrypted and signed data, which is disruptive — and leaving a known-compromised key in place is worse.

How did attackers find the file?

Automated scanning. Bots request common sensitive paths on every domain they come across, continuously. There is no targeting involved and no need for anyone to know your site exists beforehand — an exposed file is typically discovered within hours of becoming reachable.

What is the correct way to prevent this?

Point your web server's document root at the application's public directory, so nothing above it is reachable by URL at all. That is how the framework is designed to be deployed and it eliminates the entire class of problem rather than blocking one filename at a time.