notra·
2026-08-27·4 min read·Notra·Reviewed 2026-09-09

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:5173

Vite 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:

LineWhat it unlocksFirst move
SUPABASE_SERVICE_ROLE_KEY=...Full DB read/write, RLS bypassedDump every user row
JWT_SECRET=...Signs any session tokenForge an admin login
DATABASE_URL=postgres://...Direct Postgres connectionConnect from a laptop
AWS_SECRET_ACCESS_KEY=...S3, and often moreList and pull every bucket
STRIPE_SECRET_KEY=sk_live_...Live payments APIRead customers, refunds, payouts
Warning

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.

  1. Read the secret. One request, as above.
  2. Forge a login. JWT_SECRET signs sessions, so an attacker mints a token with role: admin and walks into your dashboard as you. We break this down in Forging Admin.
  3. Skip the app entirely. DATABASE_URL is a direct line to Postgres. No API, no rate limits, no logs in your app tier.
  4. Move to the cloud. AWS keys turn a database breach into an infrastructure breach: buckets, backups, sometimes IAM.
  5. 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 -5

If 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 localhost only. If you need remote access, tunnel it, do not publish it.
  • Staging never shares production credentials. Separate keys, separate database, separate blast radius.
  • .env never 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.

ShareX LinkedIn

Related

Want this run against your site?