Locking the Front Door on Modern Web Apps: Why Your Production Container Should Never Serve From the Project Root (Plus Our 8-Point Pre-Launch Gate)
Within minutes of pointing a domain at a new server, automated scanners probe /.env, /.git/config, /backup.sql, and /server.mjs. Here is how we isolate static asset serving inside every production container and verify 8 mandatory security checks before launch.
Automated vulnerability scanners or search crawlers access sensitive configuration files (/.env, /package.json, /.git/HEAD, or .map source maps), or spam bots flood contact forms.
Serving static files relative to the application root directory instead of a strictly confined dist/ build folder, leaving production source maps enabled, or shipping forms without server-side rate limiting and honeypots.
Enforce strict path.resolve(DIST_DIR) confinement with explicit 403/404 dotfile blocking, disable production .map files, keep secrets strictly server-side, and run an 8-point negative-testing gate before launch.
Interactive 8-Point Pre-Launch Web Application Security Checklist
Run through all 8 checks before putting any Node.js, React, or PHP web application into production:
1. Why Static File Servers Must Be Locked Inside dist/—Never the Application Root
When developers move a custom Node.js, Vite, or Python web application from local development to a production Linux server, one of the most dangerous mistakes is configuring the static file middleware to serve files relative to the project root directory (.) instead of the compiled /dist subdirectory.
If your static file handler resolves paths against the project root without strict directory confinement, an attacker who requests https://yourdomain.com/server.mjs, https://yourdomain.com/package.json, or https://yourdomain.com/.env may receive your raw backend source code or environment secrets!
In our production container standard, the static file handler resolves every incoming request strictly inside DIST_DIR = path.resolve(__dirname, "dist"), verifies that the normalized path begins with DIST_DIR, and explicitly rejects any URL containing hidden dot-segments (/.) or sensitive extensions (.sql, .bak, .env, .map).
| Probe Vector | Unconfined Root Static Handler | GreenTree Confined DIST_DIR + 8-Point Gate |
|---|---|---|
| GET /.env or /.git/config | Risk of leaking environment variables or Git metadata | Blocked immediately at edge & request router (HTTP 403/404) |
| GET /server.mjs or /package.json | Exposed if static middleware points at project root | Physically outside DIST_DIR; impossible to serve as static asset |
| GET /assets/index-*.js.map | Exposes full original TypeScript source tree to scrapers | build.sourcemap = false enforced in production builds |
| POST /api/forms/submit (Bot Flood) | Floods inbox with thousands of automated spam submissions | 7-layer shield: honeypot + interaction timer + IP rate limit |
2. Testing What Should Be Blocked (Not Just What Should Load)
Most people test a new website by opening the homepage in a browser and making sure it looks right. A systems engineer tests a new website by requesting the things that must fail.
Before any site on our high-availability platform goes live, we run an automated negative-testing check against the production container to prove that dotfile probes, backend script filenames, path-traversal attempts, and unauthenticated admin requests are all rejected cleanly while legitimate prerendered pages return HTTP 200 with strict security headers.
# Verify that sensitive paths return 403/404 (and NEVER 200 OK with file contents)
for probe in "/.env" "/.git/config" "/server.mjs" "/package.json" "/wp-config.php" "/backup.sql"; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://www.yourdomain.com${probe}")
echo "Probe ${probe} -> HTTP ${status}"
done
# Verify that compiled client JS bundles in dist/assets/ contain zero private keys or .map files
ls -la dist/assets/*.map 2>/dev/null || echo "PASS: Zero production .map files present"
grep -rnE "sk_live_|PRIVATE_KEY|POSTGRES_PASSWORD|SMTP_PASS" dist/assets/ || echo "PASS: Zero server secrets in client bundle"Common Questions on Container Static Isolation & 8-Point Security Gate
Why do I see thousands of requests for /wp-login.php and /.env in my logs even on a React/Node site?
Automated botnets continuously scan every IPv4 address and newly issued SSL certificate on the internet, firing a standard list of 500+ URL probes (/.env, /.git/config, /wp-admin, /phpmyadmin) regardless of what software you actually run. Blocking those paths cleanly at the edge and confining static files to dist/ ensures they never touch your application logic.
Is hiding a secret key in a VITE_ environment variable safe if my Git repo is private?
No. Even if your GitHub repository is private, Vite embeds every VITE_* variable directly into the compiled JavaScript bundle served to every visitor’s browser. Any secret API key, database service-role key, or SMTP password must live only in server-side environment variables.
How does your 7-layer form shield stop spam without annoying customers with visual puzzles?
Instead of forcing prospective customers to click traffic lights or fire hydrants, our form shield combines invisible honeypot fields, page-load interaction timing (rejecting instant sub-second bot POSTs), cryptographic payload checks, and per-IP sliding-window rate limiting.
You Built the Prototype. Here’s How We Engineer the Rest: Turning a Lovable, Famous.ai, Bolt, or Replit App Into a Production Web Application
Read the full 4,200+ word guide on closing the 4 security doors (VITE_ secret leaks, CVE-2025-48757 RLS exposure, unthrottled webhooks, and .map source maps).
Built an App in Lovable, Famous.ai, Bolt, Replit, or React That Needs Production Hardening?
We take prototypes and custom web applications, lock the 4 security doors, isolate the database and static build pipeline, prerender every route for Google and AI search, and host them on our monitored high-availability Linux cloud.
Related Notes From the Sysadmin Desk
Moving Off Usage-Metered Cloud (Netlify Credits & Non-Pausing Supabase Compute): Our Production Cutover Runbook to Flat-Rate Docker & PostgreSQL 16
Export the Supabase PostgreSQL schema and data via pg_dump, replace client-side VITE_SUPABASE_ANON_KEY calls with server-side Express + pg pool endpoints, translate netlify.toml redirects/headers into server.mjs, and deploy inside an isolated Docker container.
Writable Standby PostgreSQL Failback Merges: Why ON CONFLICT (id) DO NOTHING Silently Drops Rows on Sequential IDs (And How to Fix It With UUIDs or Split Sequence Ranges)
Audit every table in the failback merge list: use UUID primary keys (DEFAULT gen_random_uuid()) on append-only lead/submission tables, or configure non-overlapping odd/even sequence steps (INCREMENT BY 2) on integer-keyed tables—and log conflicted rows before skipping.
The Zero-Downtime Server Transfer Runbook: How We Move Live Websites, Databases, and Mailboxes Without Losing a Single Order or Email
Execute a 5-stage cutover: lower DNS TTLs to 60 seconds 48 hours early, stage and test via local hosts-file override, pre-issue TLS certificates, run a final sub-second rsync + database delta sync, and enforce a 5-source 301 redirect map.