NODE.JS SECURITY SCANNER
Scan Node.js and Express apps for dependency CVEs, unsafe Express configuration, and missing hardening. Built for AI-generated backends that skipped the security pass.
PROBE YOUR NODE.JS APP NOW
Enter your deployed URL — we test runtime config, error handling, rate limits, and dependency exposure. No repo access required.
Why Node.js Apps Need Runtime Testing
Static analysis catches half the problem. The other half only appears when the app is running: what headers does the server send? Which routes skipped requireAuth? What does /error actually return when you send malformed JSON? Does the auth route slow down after 100 hits-per-second, or does it cheerfully serve 50,000?
A clean npm audit and a green CI pipeline don’t answer any of those questions. The runtime probe does.
What Gets Scanned
DEPENDENCIES
CVEs across every npm package, ranked by exploitability and reachability from your routes.
EXPRESS CONFIG
Helmet coverage, CORS permissiveness, body-parser limits, cookie flags, trust-proxy settings.
RATE LIMITING
Brute-force detection on login, reset, signup, and API routes. Confirms 429s actually fire.
ERROR LEAKAGE
Stack traces, sensitive paths, env var echoes, and database errors in 500 responses.
AUTH ROUTE COVERAGE
Routes that look authenticated but skip the middleware on at least one verb (POST guarded, PATCH not).
PROTOTYPE POLLUTION SURFACE
Body parsers and merge utilities in the dep tree that accept __proto__ / constructor keys.
SSRF VECTORS
Endpoints that fetch user-supplied URLs server-side without an allowlist (image proxy, webhook tester, OG fetcher).
OPEN REDIRECT
Login or auth-callback routes that 302 to any ?next= destination — used in phishing chains.
Typical Findings
- Missing Helmet — no CSP, no HSTS, no frame-ancestors. Default Express ships zero security headers.
- CORS:
*on auth routes — any origin can hit your session endpoints. When paired withcredentials: true, browsers refuse, but devs sometimes “fix” by reflectingOriginblindly. - Unbounded body parser —
express.json()withoutlimitaccepts a 100 MB POST and OOMs the worker. - Debug routes in production —
/debug/env,/debug/routes,/api/__test,/_statusexposed because no env gate. - Express stack traces — default error handler leaks file paths, package versions, ORM queries.
trust proxynot set — rate limiting keys offreq.ipwhich is the load balancer’s IP, so every request looks like the same client.- Cookie missing flags — session cookie without
Secure,HttpOnly, orSameSite. - JWT verified with
algorithms: undefined— the dreadedalg: noneaccepted as valid.
How attackers find this
The recon path for a Node app is short:
- Hit
/withcurl -I. TheX-Powered-By: Expressheader (default on, you have to disable it) confirms the stack.Servermay include the Node version. - Probe common Express endpoints:
/api,/admin,/health,/status,/metrics,/debug,/.env. Many AI-scaffolded apps ship one or more. - Trigger an error: send
{to a JSON endpoint. The default error handler returns the stack trace as JSON, complete withat /Users/dev/.../routes/users.js:42:15. Now the attacker has your file structure. - Identify the dep tree: a 500 page often names the failing package and version. Cross-reference Snyk or GHSA for known CVEs in that version.
- Replay the auth route 200×/sec. If 429s never come, brute force or credential stuffing is on the table. Most fresh Express apps have no rate limit.
- Try prototype-pollution payloads:
POST /api/user/update {"__proto__": {"isAdmin": true}}. If the merge utility downstream is vulnerable, future requests pick upisAdmin: truefrom the polluted prototype.
The whole sequence is automatable and runs without raising any meaningful logs on the target side.
Fix Recipes
Hardened Express boot:
import express from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import cors from 'cors';
const app = express();
app.disable('x-powered-by');
app.set('trust proxy', 1); // adjust to your proxy depth
app.use(helmet());
app.use(express.json({ limit: '100kb' }));
app.use(express.urlencoded({ limit: '100kb', extended: false }));
app.use(cors({
origin: ['https://app.example.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 20,
standardHeaders: true,
legacyHeaders: false,
});
app.use('/auth', authLimiter);
app.use('/login', authLimiter);
app.use('/reset-password', authLimiter);
Safe error handler:
app.use((err, req, res, next) => {
const id = crypto.randomUUID();
console.error({ id, err });
res.status(err.status || 500).json({
error: 'Internal error',
requestId: id,
// No stack, no message, no path. The id ties to your logs.
});
});
JWT verification — pin the algorithm:
import jwt from 'jsonwebtoken';
const payload = jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256'], // Without this, alg:none and HS256-with-pubkey-as-secret both work.
audience: 'api.example.com',
issuer: 'https://auth.example.com',
});
Cookie flags:
res.cookie('sid', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 1000 * 60 * 60 * 24 * 7,
path: '/',
});
Environment-gated debug routes:
if (process.env.NODE_ENV !== 'production') {
app.use('/debug', debugRouter);
}
Dependency hygiene
npm audit --omit=devweekly in CI; fail the build onhighorcritical.- Pin via
package-lock.json+ Renovate/Dependabot. Don’t accept floating ranges in production. - Scan post-install scripts:
npm pkg get scripts.postinstallacross the tree, deny anything that fetches remote code. - Replace abandoned packages — anything with no commits in 18 months is a future CVE.
- Watch for hallucinated packages — AI tools invent package names that attackers then register with malware.
What this scanner does NOT flag
- Pure code-level issues like unsanitised template strings or insecure deserialisation that only fire with a specific payload — runtime probing covers the boundary, not every internal function.
- Application-logic bugs (broken access control between two legitimate users) — that’s a VibeEval agent job, requires authenticated multi-user testing.
- Issues behind feature flags or paid plans the scanner can’t reach.
- Background-job vulnerabilities in BullMQ, Agenda, etc. — workers don’t have HTTP surface to probe from the outside.
- Memory leaks and event-loop blocks — these are perf problems, not security; out of scope.
Related tools and guides
- Package Hallucination Scanner — pair with this to catch AI-invented deps before install.
- Security Headers Checker — same headers angle, deeper grading.
- Token Leak Checker — also catches server keys leaked into bundled frontend code.
- Vibe Code Scanner — full app scan including authenticated routes.
COMMON QUESTIONS
RUN THE FULL NODE.JS AUDIT
The VibeEval agent tests your live endpoints for auth, injection, and rate-limit gaps.