21 Oct 2026 Two critical RCE bugs in PHP core (CVE-2026-109618, CVE-2026-109619). Fixed in 8.2.35 / 8.3.36 / 8.4.27 / 8.5.12. Is my PHP patched? →
Checklamp
Exposed file · .env

An exposed .env is a database password in a URL

Published · updated

Framework projects keep their secrets in a dot-file next to the code. When that file lands in the web root, one plain GET returns the database password, the mail credentials and every API key — and bots request it on every site they find.

Passive. Public responses only, nothing is attacked. No account. The result is on the page. Honest: if a value is hidden, we say so instead of guessing.
Reading public responses — usually 5–20 seconds

What is inside, and why it matters

A typical Laravel, Symfony or Node project keeps APP_KEY, DB_PASSWORD, mail credentials, payment keys and third-party tokens in .env. It is not a configuration detail — it is the complete set of credentials the application uses, in plain text, with no authentication in front of it.

With DB_PASSWORD and a reachable database port an attacker copies the customer table. With APP_KEY they can forge session cookies and log in as an administrator without a password. With mail credentials they send invoices from your domain.

How it becomes public

  • Deployed to the web root. The project is copied whole (git clone, FTP, rsync) into the directory the web server serves, instead of into a parent with only public/ exposed.
  • Copies with guessable names. .env.local, .env.production, .env.backup, .env.bak — created during a migration and never removed.
  • Editor and archive leftovers. .env.swp, site.zip, backup.tar.gz that contains the project folder.
  • Wrong server root. An nginx root pointing one level too high exposes the whole project, including .git and .env.

Test for it without copying secrets into a chat

# только код ответа: 200 — файл отдаётся, 403/404 — закрыт
for p in .env .env.local .env.production .env.bak; do
  printf '%-18s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' "https://example.com/$p")"
done

Do not print the body to a terminal you share, do not paste it into a ticket, and do not attach it to a chat with a contractor: the first line is usually a working password. If you need to confirm the content is real, pipe it into head -c 0 and look only at the status code and content-length.

Our checker does the same four requests among its exposed-file checks and reports whether the file is downloadable — without storing or displaying the contents.

If it is exposed: remove, rotate, then watch

  1. Delete the file and every copy — including archives that contain it.
  2. Rotate every secret it held. Database password, mail credentials, API keys, payment keys. Changing the password matters only after the file is gone; doing it in the other order leaves the window open.
  3. Rotate APP_KEY last, and expect consequences. All existing sessions and encrypted values become invalid — users are logged out, and encrypted columns may need re-encryption.
  4. Search the access log for GET /.env. If it was requested from an address that is not yours, treat the credentials as used, not merely exposed.
  5. Look for follow-up activity: new admin users, changed mail rules, exports of customer data, new cron entries.

Bots request /.env on new domains within hours of the first certificate being issued, so “nobody knows about it” is not a working assumption.

Close the class of problem, not one file

# nginx: все dotfiles недоступны, до location-ов приложения
location ~ /\.(?!well-known/) { deny all; access_log off; }

# Apache 2.4
<FilesMatch "^\.">
  Require all denied
</FilesMatch>

Keep .env one level above the served directory, deploy through a repository or CI with an exclude list, and add a check to the release pipeline that requests /.env and fails the deploy on 200. The patch checklist covers the same principle for PHP itself: the fix belongs where the whole class is closed.

Questions

The file returned 403 — is that enough?

It means the server refused the request. Check the same rule covers .env.local, .env.bak and archives; a rule written for one filename is not a rule for dotfiles.

How long was it exposed?

Access logs answer that: search for requests to /.env and look at the first and last date. If the log rotates weekly and the file was deployed months ago, assume the earlier period is unknown.

Do I need to rotate the database password if nobody downloaded it?

Treat any 200 response in the log as a download. If there is no such entry and the file was only briefly live, rotate anyway — the cost is a five-minute restart, the alternative is an incident review.

Is hiding dotfiles in nginx enough for a Node or Go app?

It closes the server-level path. Also confirm the application does not serve the file itself (a static handler or a misconfigured public directory), because that path bypasses the server rule.