Skip to content
>_ITDITDWeb Security Platform

By Stack

Did you leave a secret file in a public directory? Audit your webroot

Is a token or credential file sitting in your web public directory (webroot)? The classic mistake where anyone can fetch it by URL, the template fan-out trap where every site has the same hole, and how to audit your own server and close it — through this site's operational lens.

Published 2026-06-11 Updated 2026-06-11 6 min read

For: anyone running several sites on shared/rental hosting or a VPS, deploying into public directories (public/, public_html/, htdocs/, etc.). No attack steps — only auditing your own server and closing the holes. For protecting the .env itself, see protecting .env on shared hosting.

This site's view: where you put it IS the access control

Secrets leak more from placement accidents than from clever attacks. The webroot is "a place anyone can come fetch from by URL." Put a secret there and even the strongest server password is meaningless. This site's foundation is to keep secrets out of both the webroot and git (→ storing secrets safely). The idea is simple: the public directory is a public shelf. Put only things you don't mind being seen on it.

public/ = fetchable by anyone via URL

OK to place

images, CSS, JS — public assets

Never place

.env, token.json, .sql, .git, .bak

Above the app root = not served

Put here

secrets, tokens, credentials, backups. Perms 600 (owner-read only). Unreachable by URL.

The public directory is a public shelf. Keep secrets outside it (unserved, perms 600).

Why "leftovers in the public directory" happen

The common pattern is a file placed for convenience, or one that shipped inside a boilerplate, quietly staying public.

one URL
a webroot file is fetchable by anyone
years
it sits unnoticed
every site
template-born holes fan out
revoke now
assume it leaked and rotate

For example, a JSON holding an access token for a third-party integration sits under public/ for a long time — and if it's from a shared template, the same file is copied to the same spot on many sites, so they all share the same hole. Fixing one site is meaningless if the others are open. Equally dangerous: leftover .env, database backups (.sql), a .git directory, editor backups (*.bak), and shortcuts (related: path traversal · an entire .env exposed).

Audit your own server

First, mechanically surface "any secret-looking files in my own public directory." This is inspecting your own assets, not peeking at anyone else's site.

1

Search under public directories for secret-looking files

Across your own home, surface secret candidates that slipped into public/ (token/credential JSON, .env, keys, shortcuts).
2

Assume fan-out — widen to every host and site

Sites built from the same boilerplate have the same thing in the same place; run the same check across all hosts and sites. One hit is the tip of the iceberg.
3

Decide per file whether it truly needs to be public

Sort each result into "safe to publish" vs "secret." Secrets go through the remove/revoke/move steps below.
# audit under your own public directories (inspecting your own assets)
find ~ -path '*/public/*' \( -iname '*token*.json' -o -iname '*credential*.json' \
      -o -iname '*.bak' -o -iname '*.sql' -o -iname '.env*' \) 2>/dev/null
# an exposed .git directory is dangerous too
find ~ -path '*/public/*/.git' -maxdepth 8 2>/dev/null

What to do when you find one

Do three things as a set — remove, revoke, move. Skip any one and the hole isn't fully closed.

1

Remove from the webroot so the URL can't fetch it

Move the file out of the public directory or delete it, and confirm the URL now returns 404.
2

Revoke/reissue any key or token that may have leaked

Don't leave it on "probably fine." Since it was exposed, assume it leaked and refresh or revoke that token/key.
3

Keep secrets outside the webroot at perms 600

From now on, place secrets outside the public directory (e.g. above the app root) at perms 600 (owner-read only). If your config can deny serving sensitive extensions, add that too.

The placement people fall into (dangerous)

  • a JSON token for an integration sits in public/
  • a secret file that shipped in the boilerplate stays public
  • backups (.sql/.bak) or .git left under the public dir
  • fix one site and call it "handled"

The correct placement

  • the public directory holds only publicly-shareable things
  • secrets live outside the webroot at perms 600
  • sensitive extensions are denied from being served
  • find one and audit every host and site

Make 'only public-safe things' a standing rule

Rules beat one-off fixes. Decide that the public directory is a public shelf, and as a rule never put secrets, backups, version-control data (.git), or config files there. When you update a boilerplate, strip the secret from the template itself — otherwise every site you mass-produce next keeps copying the same hole.

What this site does itself

This site's foundation is to keep secrets — keys, tokens, connection strings — out of both the public directory and the code repository. Deploy artifacts contain only built public assets; secrets are held as environment variables of the runtime, in a place that isn't served. The reason: this article's incident class is the textbook "one leftover, and one URL leaks everything." We design placement itself as access control, and run this audit on the same cadence as the vulnerability-response inventory.

FAQ

QWhy is putting files in a public directory dangerous?
A

Anything in the web public directory (webroot — public/, public_html/, etc.) can be fetched by anyone who hits its URL. A leftover token/credential JSON, .env, backup, or key file gets retrieved before anyone notices, leading to immediate harm. The public directory should hold only things that are safe to be public.

QIf I find one on a site, should I check the others?
A

Yes. If you mass-produce sites from a shared template or boilerplate, the same leftover is usually fanned out across all of them. Find one and audit every site and host of the same origin.

QWhat do I do when I find one?
A

1) Remove the file from the webroot so it returns 404 by URL. 2) Refresh/revoke any token or key that may have leaked (assume it leaked). 3) Going forward, keep secrets outside the webroot at perms 600 (owner-read only). Do all three as a set.