The .env File That Ends Companies
A staging box nobody remembers
Every company has one. A staging, dev, or demo subdomain that someone spun up in a hurry, pointed at a real database "just for testing," and never took down. It still runs the framework dev server. It still holds a copy of .env. And it is one HTTP request away from anyone who looks.
This is a high-impact class of exposure when it occurs: a .env file can contain several credentials, so one leaked file may expand the blast radius quickly. The actual impact depends on which values are present and whether they remain valid.
The one request that opens the door
Modern front-end tooling ships a dev server that can read your filesystem on purpose. Vite exposes a /@fs/ route so the browser can import files outside the project root. There is a guard, server.fs.deny, meant to block sensitive paths like .env. It has been bypassed more than once.
A representative probe against an exposed Vite dev server:
GET /@fs/home/deploy/app/.env?import&raw?? HTTP/1.1
Host: staging.yourco.com:5173Vite has patched versions of a historical server.fs.deny bypass in which ?import&raw could return arbitrary files. Check the affected and patched versions in Vite's advisory, and keep the dev server off the public network. Current Vite documents .env in the default deny list, while warning that alternative paths can defeat a deny rule (server options).
The lower-tech version is even more common: a misconfigured Nginx or Apache root that serves the whole app directory, so GET /.env returns it directly. Same outcome, less cleverness.
What is actually in that file
Here is why one file ends companies. A typical .env on a real app:
| Line | What it unlocks | First move |
|---|---|---|
SUPABASE_SERVICE_ROLE_KEY=... | Full DB read/write, RLS bypassed | Dump every user row |
JWT_SECRET=... | Signs any session token | Forge an admin login |
DATABASE_URL=postgres://... | Direct Postgres connection | Connect from a laptop |
AWS_SECRET_ACCESS_KEY=... | S3, and often more | List and pull every bucket |
STRIPE_SECRET_KEY=sk_live_... | Live payments API | Read customers, refunds, payouts |
A development server is a developer tool, not a production boundary. If one is reachable from the internet, verify its version, configuration, exposed files, and credentials promptly; a 200 on a development port alone does not prove that secrets leaked.
The kill-chain, step by step
The keys do not sit there quietly. They compose.
- Read the secret. One request, as above.
- Forge a login.
JWT_SECRETsigns sessions, so an attacker mints a token withrole: adminand walks into your dashboard as you. We break this down in Forging Admin. - Skip the app entirely.
DATABASE_URLis a direct line to Postgres. No API, no rate limits, no logs in your app tier. - Move to the cloud. AWS keys turn a database breach into an infrastructure breach: buckets, backups, sometimes IAM.
- Follow the money.
sk_live_reads your customer list and payout history.
The gap between step 1 and "attacker is you" is often minutes.
How to check yours in five minutes
Non-destructive. Run these from a network that is not your office.
# 1. Is a dev/staging host reachable at all?
for p in 3000 5173 8080; do
curl -sS -m 5 -o /dev/null -w "%{http_code} :$p\n" http://staging.yourco.com:$p/
done
# 2. Does the web root leak dotfiles?
curl -sS -m 5 -i https://staging.yourco.com/.env | head -20
# 3. Vite fs.deny exposure (read-only probe)
curl -sS -m 5 'https://staging.yourco.com:5173/@fs/home/deploy/app/.env?import&raw??' | head -5If any of these returns file contents, investigate the exposed file and its version/configuration. A 200 on a development port by itself does not prove that a secret was served.
The fix is boring, which is the point
- Dev servers bind to
localhostonly. If you need remote access, tunnel it, do not publish it. - Staging never shares production credentials. Separate keys, separate database, separate blast radius.
.envnever lives inside a web-served directory. Verify with the checks above, do not assume.- Rotate everything the moment a leak is even plausible. A secret that was public for an hour is a public secret forever.
Most teams do not miss this because they are careless. They miss it because a scanner pointed at production comes back clean, and the forgotten box on a different subdomain was never in scope. That blind spot is exactly what we argue about in Why Your Scanner Lies, and it is why breadth of surface matters as much as depth of test.
Point Notra at your domain and it maps the hosts you forgot you had, then verifies whether any of them leak a .env before someone else checks. Run a free scorecard, or read more field notes on the blog.