What a Replit app ships with
- An Express (or Flask, FastAPI) server that serves the front-end and the API from one place.
- Secrets in the Replit Secrets tab, unless the prompt asked for a .env file, which then sits in the project like any other file.
- A Postgres or Replit DB reached from the server, with the API routes as the only wall.
- Deployment on *.replit.app or a custom domain, where every response header is the server's job.
The five things that go wrong
- A .env served as a file. When the server does express.static on the project root, /.env, /.git/HEAD, and config.json come with it. The scan asks for each and confirms by content.
- CORS copied from a tutorial. app.use(cors()) with credentials, or an Origin header echoed back, lets any website call the API with a visitor's cookies.
- No security headers. Express sends none by default. A four-line middleware (or the helmet package) fixes the common ones.
- Version banners. X-Powered-By: Express and Server: nginx/1.18 tell an attacker exactly what to look up. One line turns them off.
- Plain HTTP on a custom domain. Replit's own domains redirect to HTTPS; a custom domain behind your own proxy might not.
How to fix them
Security headers and no version banner in Express
app.disable("x-powered-by");
app.use((req, res, next) => {
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
res.setHeader("X-Content-Type-Options", "nosniff");
res.setHeader("X-Frame-Options", "DENY");
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
next();
});Never serve dotfiles, and keep static files in their own folder
app.use((req, res, next) => {
if (/^\/\.(?!well-known)/.test(req.path)) return res.status(404).end();
next();
});
app.use(express.static("public", { index: "index.html" })); // not the project rootCORS from a fixed list
import cors from "cors";
const allowed = ["https://your-app.com"];
app.use(cors({
origin: (origin, done) => done(null, !origin || allowed.includes(origin)),
credentials: true,
}));
Every finding in a scan comes with the exact lines for your host and framework, opened for the one it detected.
Questions people ask
- Replit Secrets or .env?
- Secrets. They are injected as environment variables and never sit in a file the server could serve or Git could commit. If a .env exists, add it to .gitignore and make sure express.static does not point at its folder.
- Does the scanner test my API routes?
- It reads the routes your front-end names (fetch('/api/...')), sends each a request from a made-up origin, and reports the ones that answer with that origin allowed plus credentials. Nothing is written.
- My app uses Flask, not Express. Do the fixes apply?
- The findings are the same; the lines differ. Flask-Talisman sets the headers, flask-cors takes an origins list, and the dotfile rule belongs in whatever serves static files.
Check your Replit app now
Free, read-only, ten seconds. Then, if you want it watched: weekly re-scans, uptime and error alerts, code checks on the repo.
Scan your app free