Express (Node.js) Web Security Spec (Express 5.x / 4.19.2+, Node.js LTS)
This document is designed as a security spec that supports:
- Secure-by-default code generation for new Express apps and routes.
- Security review / vulnerability hunting in existing Express code (passive “notice issues while working” and active “scan the repo and report findings”).
It is intentionally written as a set of normative requirements (“MUST/SHOULD/MAY”) plus audit rules (what bad patterns look like, how to detect them, and how to fix/mitigate them).
0) Safety, boundaries, and anti-abuse constraints (MUST FOLLOW)
- MUST NOT request, output, log, or commit secrets (API keys, passwords, private keys, session secrets, cookies, tokens).
- MUST NOT “fix” security by disabling protections (e.g., weakening cookie flags, disabling CSRF defenses for cookie-authenticated apps, enabling permissive CORS, trusting proxy headers from the open internet, turning on debugging/stack traces in production, disabling TLS without a replacement).
- MUST provide evidence-based findings during audits: cite file paths, code snippets, middleware/config values, and runtime assumptions that justify the claim.
- MUST treat uncertainty honestly: if a protection might exist in infrastructure (reverse proxy, gateway, WAF, CDN), report it as “not visible in app code; verify at runtime/config.”
- MUST prefer vetted libraries and platform controls over “roll your own” crypto/auth/session/CSRF. Express explicitly expects the application to validate/handle user input correctly; it does not do this automatically. ([Express][1])
1) Operating modes
1.1 Generation mode (default)
When asked to write new Express code or modify existing code:
- MUST follow every MUST requirement in this spec.
- SHOULD follow every SHOULD requirement unless the user explicitly says otherwise.
- MUST prefer safe-by-default APIs and proven libraries over custom security code.
- MUST avoid introducing new risky sinks (shell execution, dynamic code evaluation, unsafe redirects, serving user files as HTML, template rendering from untrusted strings, unsafe filesystem paths, SSRF URL fetch endpoints, etc.).
1.2 Passive review mode (always on while editing)
While working anywhere in an Express repo (even if the user did not ask for a security scan):
- MUST “notice” violations of this spec in touched/nearby code.
- SHOULD mention issues as they come up, with a brief explanation + safe fix.
1.3 Active audit mode (explicit scan request)
When the user asks to “scan”, “audit”, or “hunt for vulns”:
- MUST systematically search the codebase for violations of this spec.
- MUST output findings in a structured format (see §2.3).
Recommended audit order:
- Entrypoints (server/app bootstrap), deployment manifests, Dockerfiles, process manager config, CI/CD.
- Express settings + middleware stack order (helmet, parsers, auth, sessions, CSRF, CORS).
- Proxy trust (
trust proxy) and IP/protocol/host handling. ([Express][2])
- Auth flows, sessions, cookies, password reset links, redirect handling. ([Express][1])
- State-changing routes + CSRF protections (cookie-authenticated apps). ([OWASP Cheat Sheet Series][3])
- Template rendering and XSS defenses (HTML generation, CSP,
res.locals). ([OWASP Cheat Sheet Series][4])
- File handling (uploads + downloads + static files) and path traversal. ([Express][5])
- Injection classes (SQL, NoSQL, command execution, unsafe deserialization). ([OWASP Cheat Sheet Series][6])
- Outbound requests (SSRF) and webhook/callback delivery. ([OWASP Cheat Sheet Series][7])
- Rate limiting / brute-force defenses / abuse controls. ([Express][1])
- Dependency hygiene / lockfiles / npm audit / vulnerable Express versions. ([Express][1])
2) Definitions and review guidance
2.1 Untrusted input (treat as attacker-controlled unless proven otherwise)
In Express, common untrusted inputs include:
req.params (route parameters)
req.query (query string parameters; can be strings/arrays/objects depending on parsing) ([OWASP Cheat Sheet Series][8])
req.body from express.json(), express.urlencoded(), express.text(), express.raw() ([Express][5])
req.headers / req.get(...)
req.cookies / req.signedCookies (if cookie parsing middleware is used)
- Upload metadata and filenames (e.g., multer
file.originalname, file.mimetype)
- Any data from external systems (webhooks, third-party APIs, message queues)
- Any persisted user content (DB rows) that originated from users
Special proxy note:
- If
trust proxy is enabled, values like req.ip, req.hostname, and req.protocol may be derived from X-Forwarded-* headers which can be attacker-controlled if your proxy chain is not correctly overwriting/removing them. ([Express][2])
2.2 State-changing request
A request is state-changing if it can create/update/delete data, change auth/session state, trigger side effects (purchase, email send, webhook send), or initiate privileged actions.
2.3 Required audit finding format
For each issue found, output:
- Rule ID:
- Severity: Critical / High / Medium / Low
- Location: file path + function/route/middleware name + line(s)
- Evidence: the exact code/config snippet
- Impact: what could go wrong, who can exploit it
- Fix: safe change (prefer minimal diff)
- Mitigation: defense-in-depth if immediate fix is hard
- False positive notes: what to verify if uncertain
3) Secure baseline: minimum production configuration (MUST in production)
This is the smallest “production baseline” that prevents common Express misconfigurations.
Minimum baseline targets:
helmet() is used and configured (especially CSP where applicable), and fingerprinting is reduced (disable x-powered-by). ([Express][1])
A custom 404 handler and a custom error handler exist, and production does not leak internal stack traces. ([Express][1])
Cookie/session usage is deliberate:
- Not using default session cookie names
- Cookies use secure attributes (
Secure, HttpOnly, SameSite) as appropriate
- Cookie-backed sessions never store secrets (they are readable by the client)
- Server-side sessions never use MemoryStore in production. ([Express][1])
Request body parsing has explicit limits (express.json({ limit }), express.urlencoded({ limit, parameterLimit, depth })). ([Express][5])
trust proxy is configured explicitly to match your proxy topology; not blindly true. ([Express][2])
Login/auth endpoints have brute-force protection and rate limiting. ([Express][1])
Dependencies are regularly audited/updated (npm audit + advisory response). ([Express][1])
4) Rules (generation + audit)
Each rule contains: required practice, insecure patterns, detection hints, and remediation.
EXPRESS-INPUT-001: Treat all user input as untrusted and validate it
Severity: High
Required:
- MUST validate and normalize untrusted inputs before using them in security-sensitive logic or dangerous sinks (DB queries, redirects, filesystem, HTML output, shell commands). Ensure the untrusted inputs are type checked and structure checked before using or passing forward.
- SHOULD apply allowlists (known-good) rather than blocklists when feasible.
- MUST reject or safely handle unexpected types/shapes in
req.query, req.params, and req.body.
Insecure patterns:
- Passing
req.query, req.params, req.body directly into database/query builders, redirects, filesystem paths, or templates.
- Assuming
req.query.foo is always a string (it can be an array/object depending on parsing). ([OWASP Cheat Sheet Series][8])
Detection hints:
- Identify “untrusted-to-sink” flows: request → sink (
res.redirect, SQL execution, sendFile, child_process, template render, outbound fetch).
- Search for direct usage of
req.query.*, req.body.*, req.params.* in sensitive calls.
Fix:
- Add schema validation (e.g., zod/joi/express-validator) at route boundaries.
- Normalize types (e.g., force IDs to integers; reject arrays when scalar expected).
Notes:
- Express production security guidance explicitly says input validation/handling is the application’s responsibility. ([Express][1])
EXPRESS-REDIRECT-001: Prevent open redirects; validate redirect targets
Severity: Medium
Required:
- MUST validate redirect destinations derived from untrusted input (
next, return_to, url).
- SHOULD allowlist only same-site relative paths (preferred) or a strict allowlist of domains.
- MUST fall back to a safe default when validation fails.
Insecure patterns:
res.redirect(req.query.next) with no validation.
res.redirect(req.body.url) or res.location(...) using untrusted URLs.
Detection hints:
- Search for
res.redirect( and res.location( and trace the source of the target.
- Look for query params named
next, redirect, return, url.
Fix:
- Only allow relative paths (starting with
/) and disallow //, backslashes, and encoded variants.
- If cross-domain redirects are required, allowlist exact hosts and enforce
https.
Notes:
- Express documentation calls out open redirects as dangerous user input and shows validating the host before redirecting. ([Express][1])
- Keep Express updated: Express has had an open-redirect-related CVE affecting some versions, and upgrades are part of the mitigation posture. ([NVD][9])
EXPRESS-HEADERS-001: Use Helmet (or equivalent) to set essential security headers
Severity: Medium
Required:
- SHOULD use
helmet() to set common security headers.
- SHOULD configure CSP realistically (avoid
unsafe-inline where possible) for pages that render user-influenced content.
- SHOULD set
X-Content-Type-Options: nosniff, clickjacking defenses (X-Frame-Options or CSP frame-ancestors), and appropriate referrer policy.
NOTE: It is most important to set the CSP's script-src. All other directives are not as important and can generally be excluded for the ease of development.
Insecure patterns:
- No security headers set in app code and no evidence they are set at the edge.
- CSP missing on apps that display user content.
- Misconfigured framing headers that unintentionally allow clickjacking.
Detection hints:
- Search for
helmet( usage; check if CSP is configured or disabled.
- Search for
res.setHeader( / res.set( for security header setting.
- If not visible in app code, check nginx/CDN config; otherwise flag “verify at edge.”
Fix:
Notes:
- Express production security best practices recommend Helmet and list headers Helmet sets by default. ([Express][1])
- OWASP HTTP Headers guidance is a useful reference when tuning policies. ([OWASP Cheat Sheet Series][10])
EXPRESS-FINGERPRINT-001: Reduce fingerprinting by disabling x-powered-by and customizing error/404 responses
Severity: Low (defense-in-depth)
Required:
- SHOULD disable
X-Powered-By using app.disable('x-powered-by').
- SHOULD provide a custom 404 handler and a custom error handler to avoid distinct default responses and to control information leakage.
Insecure patterns:
- Default
X-Powered-By: Express header left enabled.
- Default Express 404/error responses in production with identifiable formatting and/or stack traces.
Detection hints:
- Search for
app.disable('x-powered-by').
- Check middleware tail for a custom 404 (
app.use((req,res)=>...)) and a custom error handler (app.use((err,req,res,next)=>...)).
- Check if
NODE_ENV is correctly set for production behavior (see EXPRESS-ERROR-001). ([Express][11])
Fix:
Add:
app.disable('x-powered-by')
- A custom 404 handler
- A custom error handler that logs server-side and returns generic messages client-side
Notes:
- Express docs explicitly recommend disabling
x-powered-by and adding your own not-found and error handlers. ([Express][1])
EXPRESS-COOKIE-001: Cookies must use secure attributes and minimal scope
Severity: Medium
Required:
MUST set cookie flags appropriately for any authentication/session cookie:
Secure when HTTPS (production) IMPORTANT NOTE: Only set Secure in production environment if TLS is configured. When running in a local dev environment over HTTP, do not set Secure property on cookies. You should do this conditionally based on if the app is running in production mode. You should also include a property like SESSION_COOKIE_SECURE which can be used to disable Secure cookies when testing over HTTP.
HttpOnly for auth/session cookies
SameSite set deliberately (Lax is a common baseline; Strict if compatible; None only with Secure and a justified cross-site need)
SHOULD avoid setting domain broadly (avoid “all subdomains” unless required).
SHOULD set bounded expiry appropriate to risk and UX.
Insecure patterns:
- Session/auth cookies without
HttpOnly.
- Cookies without
Secure in production HTTPS.
SameSite=None + cookie-authenticated state-changing endpoints without CSRF protections.
Detection hints:
- Search for
res.cookie(, Set-Cookie, cookie: { ... }, express-session, cookie-session.
- Verify cookie flags in session middleware configuration.
Fix:
- Set these attributes centrally in session/cookie middleware configuration.
Notes:
- Express production security guidance lists cookie security options (
secure, httpOnly, etc.). ([Express][1])
res.cookie() ultimately sets Set-Cookie with options; defaults follow RFC 6265 behavior when options are omitted. ([Express][5])
- OWASP session management guidance is relevant for choosing flags and lifetimes. ([OWASP Cheat Sheet Series][12])
EXPRESS-SESS-001: Do not use the default session cookie name; avoid session fingerprinting
Severity: Low (defense-in-depth)
Required:
- SHOULD override the default session cookie name (e.g., do not keep
connect.sid when using express-session).
- SHOULD use a generic name (e.g.,
sessionId) unless you have a compatibility reason.
Insecure patterns:
express-session used with no name: configured (default cookie name).
- Multiple apps on the same domain sharing a cookie name accidentally.
Detection hints:
- Search for
express-session config blocks; check for name:.
Fix:
- Set
name: 'sessionId' (or similar) in express-session options.
Notes:
- Express docs explicitly recommend not using the default session cookie name to reduce fingerprinting. ([Express][1])
EXPRESS-SESS-002: Session storage and lifecycle must be production-safe
Severity: High
Required:
- MUST NOT use
MemoryStore in production (it is not designed for production use).
- MUST store session secrets outside source control and rotate them safely.
- SHOULD regenerate sessions on login / privilege changes to reduce session fixation risk.
- MUST NOT store sensitive secrets in client-readable cookie sessions.
Insecure patterns:
app.use(session({ store: new MemoryStore(), ... })) or missing store (defaults to MemoryStore).
- Hard-coded for example:
secret: 'keyboard cat' / secret: 's3Cur3' in repo.
- Using
cookie-session to store access tokens, refresh tokens, or PII.
Detection hints:
- Search for
express-session and look for MemoryStore usage or missing store.
- Search for
secret: in session config and check if it’s hard-coded.
- Look for
req.session = ... patterns and whether sensitive data is stored.
Fix:
- Use a production session store (Redis, database-backed, etc.).
- Load secrets from environment/secret manager.
- On login:
req.session.regenerate(...) or equivalent flow with safe privilege re-binding.
Notes:
express-session explicitly warns that MemoryStore is not designed for production. ([Express][1])
express-session documents rotating secrets and session regeneration to guard against fixation. ([Express][1])
- Express notes that cookie-backed sessions serialize data into the cookie and that cookie data is visible to the client; keep it small and non-secret. ([Express][1])
EXPRESS-CSRF-001: Cookie-authenticated state-changing requests MUST be CSRF-protected
Severity: High
- IMPORTANT NOTE: If cookies are not being used for auth (ie auth is via Authentication header or other passed token), then there is no CSRF risk.
Required:
- MUST protect all state-changing endpoints (POST/PUT/PATCH/DELETE) that rely on cookies for authentication.
- SHOULD use a well-understood CSRF mitigation (token-based is the typical baseline).
- MAY add defense-in-depth: Origin/Referer validation, Fetch Metadata enforcement, SameSite cookies, custom header requirements for XHR/fetch—but do not treat these as a full replacement unless explicitly designed and justified.
- MUST use at a minimum require a custom HTTP header if form based CRSF tokens are not practical, as this is the second strongest method.
IMPORTANT NOTE:
- If authentication is done via
Authorization: Bearer ... headers (and not cookies), classic browser CSRF is typically not applicable;
Insecure patterns:
- Cookie-authenticated endpoints that change state with no CSRF protection.
- Using GET for state-changing actions (amplifies CSRF risk).
- “CSRF protection” that only checks a user-controlled field.
Detection hints:
- Enumerate routes with methods other than GET/HEAD and identify whether cookies gate auth.
- Look for presence/absence of CSRF middleware and token checks.
- Check JSON APIs too, not only HTML forms.
Fix:
- Implement CSRF tokens for cookie-authenticated flows.
- Add Origin/Referer checks where feasible, and ensure SameSite is set appropriately.
Notes:
- OWASP CSRF guidance and OWASP Node.js guidance both recommend anti-CSRF tokens as a standard control for web apps. ([OWASP Cheat Sheet Series][3])
EXPRESS-CORS-001: CORS must be explicit and least-privilege
Severity: Medium (High if misconfigured with credentials)
Required:
Insecure patterns:
Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true.
- Reflecting
Origin for all requests without allowlist validation.
- Applying permissive CORS middleware globally when only a subset needs cross-origin access.
Detection hints:
- Search for
cors(, Access-Control-Allow-Origin, Access-Control-Allow-Credentials.
- Inspect whether cookies are used for auth on endpoints exposed cross-origin.
Fix:
- Implement strict origin allowlist and ensure credentialed requests only for intended origins.
- Consider splitting CORS config per route group rather than global.
Notes:
- OWASP HTTP header guidance covers security implications of response headers, including those that affect browser behavior; use it as a reference when reviewing header posture. ([OWASP Cheat Sheet Series][10])
EXPRESS-PROXY-001: Reverse proxy trust (trust proxy) must be configured correctly
Severity: Medium (High if using IP based authentication)
Required:
- If behind a reverse proxy/LB, MUST configure
app.set('trust proxy', ...) to match the real proxy chain.
- MUST NOT blindly set
trust proxy = true unless you fully control the proxy behavior and header rewriting.
- MUST ensure the last trusted proxy overwrites/removes
X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto so clients cannot spoof them.
Insecure patterns:
app.set('trust proxy', true) in an app directly exposed to the internet or behind unknown proxies.
- Using
req.ip, req.protocol, req.hostname for security decisions without correct proxy trust configuration.
- Rate limiting keyed by
req.ip with spoofable forwarded headers.
Detection hints:
- Search for
app.set('trust proxy'.
- Check infra docs (nginx/LB) for header rewriting behavior.
- Identify any security logic using
req.ip, req.ips, req.protocol, req.hostname.
Fix:
- Set
trust proxy to a hop count, explicit IP/subnet list, or a custom function matching your network.
- Ensure proxies overwrite forwarded headers.
Notes:
- Express explicitly warns that when
trust proxy is true, the client IP is derived from X-Forwarded-For, and if proxies don’t overwrite forwarded headers, the client can provide any value. It also describes that enabling trust proxy impacts req.hostname and req.protocol derived from forwarded headers. ([Express][2])
EXPRESS-BODY-001: Request body size and parsing limits MUST be set appropriately
Severity: Low
Required:
SHOULD set explicit body size limits for:
express.json({ limit })
express.urlencoded({ limit, parameterLimit, depth })
SHOULD only enable the parsers you need; do not parse large bodies by default for all routes.
SHOULD enforce additional limits at the reverse proxy / gateway level.
Insecure patterns:
- No explicit body limits (accepting arbitrarily large JSON/urlencoded).
- Global parsers applied to all routes when only some need bodies.
parameterLimit very high without justification (DoS potential).
Detection hints:
- Search for
express.json( and confirm limit is set (or consciously accepted).
- Search for
express.urlencoded( and inspect limit, parameterLimit, and depth.
- Review upload/webhook endpoints for special parsing needs.
Fix:
- Configure parsers with conservative defaults and override per route group when needed.
Notes:
- Express documents
express.json options (including limit, defaulting to 100kb) and explicitly notes req.body is untrusted and should be validated. ([Express][5])
- Express documents
express.urlencoded options including limit, parameterLimit, and depth. ([Express][5])
- OWASP Node.js guidance also recommends setting request size limits. ([OWASP Cheat Sheet Series][8])
EXPRESS-INPUT-002: Prevent HTTP Parameter Pollution and type confusion in req.query
Severity: Medium
Required:
- MUST treat
req.query values as potentially multi-valued (array/object), depending on query parsing.
- SHOULD reject ambiguous multi-valued parameters for security-sensitive fields (e.g.,
role, isAdmin, redirect, amount, userId).
- SHOULD consider explicit parsing or dedicated middleware if parameter pollution is a concern.
Insecure patterns:
if (req.query.admin) { ... } without type checks (arrays/objects may coerce truthy).
- Passing
req.query directly into ORM/NoSQL query objects.
Detection hints:
- Search for security-sensitive comparisons on
req.query.* without type enforcement.
- Look for code that assumes query params are strings.
Fix:
- Validate shape: enforce string-only for certain params and reject arrays/objects.
- Normalize query parsing settings (simple vs extended) where applicable and documented.
Notes:
- OWASP Node.js cheat sheet explicitly highlights that Express query parsing can produce strings, arrays, or objects and recommends preventing HTTP Parameter Pollution. ([OWASP Cheat Sheet Series][8])
EXPRESS-XSS-001: Prevent reflected/stored XSS in HTML responses and templating
Severity: High
Required:
- MUST escape untrusted content in HTML output (templates should auto-escape by default; do not bypass).
- MUST NOT inject untrusted strings into HTML without escaping/sanitization.
- SHOULD set CSP (via Helmet) for apps rendering user-controlled content.
- SHOULD keep
res.locals free of user-controlled input intended for templates unless it is validated/escaped.
Insecure patterns:
res.send("<div>" + req.query.q + "</div>")
- Passing untrusted HTML through “safe” template flags/filters.
- Writing untrusted strings into
res.locals and then rendering without escaping.
Detection hints:
- Search for
res.send( with strings containing user input.
- Search for template “safe” flags (engine-specific) and trace data origin.
- Search for assignments to
res.locals and whether they might contain untrusted data.
Fix:
- Use a template engine with autoescaping; pass only validated data.
- For rich text that must contain HTML, use a trusted sanitizer and an allowlist policy.
- Add CSP with realistic directives.
Notes:
- Express API docs explicitly warn that
res.locals “should not contain user-controlled input” and is often used to expose things like CSRF tokens to templates. ([Express][5])
- OWASP XSS prevention guidance provides standard output-encoding and policy recommendations. ([OWASP Cheat Sheet Series][4])
- Helmet can mitigate some XSS classes via headers such as CSP. ([Express][1])
EXPRESS-TEMPLATE-001: Never render untrusted templates or template paths (SSTI / LFI risk)
Severity: Critical (if you can prove template strings/paths are user/attacker-controlled)
Required:
- MUST NOT render templates whose contents or template path/name is influenced by untrusted input.
- MUST NOT load templates from user-controlled filesystem locations.
- SHOULD treat “email template editors”, “theme engines”, and “CMS-like template storage” as high-risk designs requiring sandboxing and isolation.
Insecure patterns:
res.render(req.query.view, data) where view is not allowlisted.
- Rendering a template from a string that includes user input (engine-specific).
- Loading templates from uploads directories.
Detection hints:
- Search for
res.render( where the first argument is derived from request/DB without allowlist.
- Search for template compilation APIs (engine-specific) fed by user content.
Fix:
- Use allowlisted template names and a fixed templates directory.
- If user-defined templates are required, implement strict sandboxing and isolate execution.
Notes:
- Express’s template system depends on the chosen engine; assume unsafe if user input influences template selection or source.
EXPRESS-FILES-001: Prevent path traversal and unsafe file serving (sendFile/download)
Severity: High
Required:
- MUST NOT pass user-controlled filesystem paths directly to
res.sendFile() / res.download() / filesystem APIs.
- SHOULD use
res.sendFile with a fixed root and strict options (e.g., deny dotfiles) when serving user-selected files from a directory.
- MUST enforce authorization checks before serving user-specific files.
Insecure patterns:
res.sendFile(req.query.path) or res.download(req.params.file) with no root restriction.
- File-serving routes that accept
.. segments, encoded traversal, or absolute paths.
Detection hints:
- Search for
res.sendFile( and trace the path argument origin.
- Search for
res.download( and trace the path argument origin.
- Look for
fs.readFile/createReadStream on paths derived from requests.
Fix:
- Use an identifier-to-path mapping stored server-side (DB), not raw paths from clients.
- Use
root: <trusted_base_dir> and dotfiles: 'deny' where appropriate; validate the filename component strictly.
Notes:
- Express’s
res.sendFile docs show using a root option and dotfiles: 'deny' as part of a safe serving configuration. ([Express][5])
res.download transfers the file as an attachment, but you still must control/validate the underlying path. ([Express][5])
EXPRESS-STATIC-001: Harden express.static / serve-static and never serve untrusted uploads as active content
Severity: Medium (if serving untrusted user files if there are not robust limits tot eh file extensions)
Required:
MUST NOT serve user uploads from a public static directory as active content (especially HTML/JS/SVG) unless explicitly intended and sandboxed. If sure that the content is inactive (png, jpg, other images etc) then it may be safe. It may be good to validate image file extensions are allow-listed before serving them.
SHOULD configure static serving to:
- deny/ignore dotfiles
- avoid unintended directory indexes if not needed
- apply appropriate cache controls for immutable assets
Insecure patterns:
app.use(express.static('uploads')) where users can upload arbitrary files.
- Serving uploaded HTML or SVG inline from the same origin as the app.
Detection hints:
- Search for
express.static( and identify served directories.
- Compare served directories with upload storage locations.
- Check for
dotfiles and index options in static middleware.
Fix:
- Store uploads outside any static web root and serve via controlled routes that set safe
Content-Type and Content-Disposition: attachment when appropriate.
- Configure
express.static(root, { dotfiles: 'deny'|'ignore', index: false (if desired) }).
Notes:
- Express documents
express.static options, including dotfiles behavior and index. ([Express][5])
EXPRESS-UPLOAD-001: File uploads must be validated, stored safely, and served safely
Severity: Low - Medium
Required:
- SHOULD enforce upload size limits (app + edge).
- MUST validate file type using allowlists and content checks (not only filename extension).
- MUST store uploads outside executable/static roots when possible.
- SHOULD generate server-side filenames (random IDs); do not trust original names.
- MUST serve potentially active formats safely (download attachment) unless explicitly intended.
Insecure patterns:
- Accepting arbitrary file types and serving them back inline.
- Using
file.originalname as the storage path.
- Missing size/type validation.
Detection hints:
- Look for multer/busboy/formidable usage and check for
limits.
- Check where uploaded files are written and how they are served.
- Check whether uploads end up under
public/ or any express.static root.
Fix:
- Implement allowlist validation + safe storage + safe serving, per OWASP upload guidance.
Notes:
- OWASP File Upload guidance covers allowlists, content validation, storage, and safe serving patterns. ([OWASP Cheat Sheet Series][13])
EXPRESS-INJECT-001: Prevent SQL injection (use parameterized queries / ORM)
Severity: High
Required:
- MUST use parameterized queries or an ORM/query builder that parameterizes under the hood.
- MUST NOT build SQL via string concatenation/template literals with untrusted input.
Insecure patterns:
db.query(`SELECT * FROM users WHERE id = ${req.query.id}`)
"SELECT ... WHERE name = '" + req.body.name + "'"
Detection hints:
- Grep for
SELECT, INSERT, UPDATE, DELETE strings in JS/TS.
- Trace untrusted input into
.query(...), .execute(...), or raw SQL APIs.
Fix:
- Replace with parameterized queries (placeholders) or ORM query APIs.
- Validate types (e.g., integer IDs) before querying.
Notes:
- OWASP SQL injection prevention guidance strongly favors parameterized queries. ([OWASP Cheat Sheet Series][6])
EXPRESS-INJECT-002: Prevent NoSQL injection / operator injection (Mongo-style)
Severity: High (app-dependent)
Required:
- MUST validate types and schemas for any query object built from untrusted input.
- MUST prevent operator injection (e.g.,
$ne, $gt, $where) if user input is merged into query objects.
- SHOULD consider defensive libraries/middleware when appropriate.
Insecure patterns:
collection.find(req.body) where the body is attacker-controlled.
- Merging
req.query/req.body into Mongo queries without schema validation.
Detection hints:
- Search for
find(, findOne(, aggregate( calls where argument is request-derived.
- Check for patterns like
{ ...req.query } or Object.assign(query, req.body).
Fix:
- Use schema validation at boundary; explicitly construct query objects from validated fields only.
Notes:
- OWASP Node.js cheat sheet discusses input validation and mentions Node ecosystem modules commonly used for sanitization in NoSQL contexts. ([OWASP Cheat Sheet Series][8])
EXPRESS-CMD-001: Prevent OS command injection (child_process)
Severity: Critical to High (depends on exposure), please prove it is user/attacker controlled
Required:
Insecure patterns:
exec(req.query.cmd)
exec(convert ${userPath} ...)
spawn('sh', ['-c', userString])
spawn(userString, ['foo'])
Detection hints:
- Search for
child_process, exec(, execSync(, spawn(, fork(.
- Trace request/DB data into command construction.
Fix:
- If possible, write the functionality in javascript or use a library instead of subprocess.
- If unavoidable, hard-code command and strictly allowlist parameters.
Notes:
- OWASP OS command injection defense guidance covers avoid-shell and allowlist patterns. ([OWASP Cheat Sheet Series][14])
EXPRESS-SSRF-001: Prevent server-side request forgery (SSRF) in outbound HTTP
Severity: Medium (High in cloud/LAN deployments)
NOTE: This is mostly only applicable to apps which will be deployed in a cloud/LAN setup or have other http services on the same box. Sometimes the feature requires this functionality unavoidably (webhooks).
Required:
MUST treat outbound requests to user-provided URLs as high risk if there are other reachable private http endpoints.
SHOULD validate and restrict destinations (allowlist hosts/domains) for any user-influenced URL fetch.
SHOULD block access to:
- localhost / private IP ranges / link-local
- cloud metadata endpoints
MUST allow only http/https for URL fetch features (to avoid schemas such as file:,javascript:)
SHOULD set timeouts and restrict redirects.
Insecure patterns:
fetch(req.query.url)
- “URL preview” / “import from URL” endpoints that accept arbitrary URLs.
Detection hints:
- Search for
fetch(, axios(, got(, request(, node-fetch usage where URL originates from users/DB.
- Review webhook testers, previewers, image fetchers.
Fix:
- Enforce scheme allowlist, host allowlist, DNS/IP resolution checks, timeouts, and redirect policy.
- Consider network egress controls at infrastructure level.
Notes:
- OWASP SSRF prevention guidance provides standard controls and common pitfalls. ([OWASP Cheat Sheet Series][7])
EXPRESS-ERROR-001: Error handling MUST not leak sensitive details in production
Severity: Low
Required:
- SHOULD define a centralized error handler (
app.use((err, req, res, next) => ...)) at the end of middleware.
- MUST avoid returning stack traces, internal error messages, or secrets to clients in production.
- SHOULD log errors server-side with appropriate redaction.
- SHOULD ensure the app runs with production settings so default behavior doesn’t leak details.
- MUST avoid logging or returning sensitive information such as secrets, env vars, sessions, cookies in error messages in production.
Insecure patterns:
- Returning
err.stack to clients.
- Using dev-only error middleware in production.
NODE_ENV left as development, causing verbose error responses.
Detection hints:
- Verify there is a final error-handling middleware.
- Search for
res.status(500).send(err) or similar.
- Check production environment variables and startup scripts.
Fix:
- Add a production-safe error handler that returns generic messages and logs details internally.
- Ensure environment is configured for production behavior.
Notes:
- Express production security guidance recommends custom error handling. ([Express][1])
- Express error handling docs describe the default error handler behavior and how production mode affects what is exposed. ([Express][11])
EXPRESS-AUTH-001: Prevent brute-force attacks against authorization endpoints
Severity: Medium
NOTE: This is highly application specific and while it is good to bring to the attention of the user, it is hard to fix without additional complex configurations. Prefer to inform the user and if they request you to help implement a solution, help walk them through possible solutions.
Required:
Insecure patterns:
- Unlimited login attempts.
Detection hints:
- Identify all auth endpoints and check for rate limiting/throttling.
- Search for
rate-limiter-flexible, express-rate-limit, or gateway policies.
Fix:
- Implement rate-limiting/throttling (app or edge). Express docs point to
rate-limiter-flexible as a tool for this approach. ([Express][1])
Notes:
- OWASP Node.js cheat sheet also recommends precautions against brute forcing. ([OWASP Cheat Sheet Series][8])
EXPRESS-DEPS-001: Dependency and patch hygiene (Express + Node + critical middleware)
Severity: Medium / Low
NOTE: npm audit often returns a large number of insignificant "vulnerabilities" which do not actually matter. You should only focus on Express or other extremely critical packages, ignoring ones listed in dev tools, bundlers, etc.
Do not upgrade packages without concent from the user. This may break existing code in unexpected ways. Instead, inform them of the outdated packages.
Required:
- MUST keep Express on a maintained version line (avoid EOL major versions).
- MAY use
npm audit in CI and during maintenance work.
- SHOULD pin dependencies via lockfiles and review major updates carefully.
Insecure patterns:
- Running EOL Express versions (e.g., very old major lines).
- Ignoring
npm audit findings without triage.
- Unpinned dependency ranges that auto-upgrade into insecure versions.
Detection hints:
- Check
package.json and lockfiles for express version and other critical middleware versions.
- Inspect CI pipelines for
npm audit/SCA steps.
Fix:
- Upgrade to latest stable Express and apply patches.
- Add automated dependency scanning and upgrade process.
Notes:
- Express production security guidance emphasizes that dependency vulnerabilities can compromise the app, and recommends
npm audit. ([Express][1])
- Track security issues affecting Express versions (including known open-redirect-related CVEs). ([NVD][9])
EXPRESS-DOS-001: Configure DoS protections (timeouts, limits, reverse proxy)
Severity: Low
NOTE: It may be hard to tell from the provided application context if the application runs behind a reverse proxy. You can inform the user or recommend one, but do not attempt to configure one without them initiating it. This is highly deployment dependant.
Required:
- SHOULD use a reverse proxy to provide caching, load balancing, and filtering controls when feasible.
- MAY configure server/proxy timeouts and connection limits to reduce exposure to Slowloris and similar DoS patterns.
- MUST ensure server/socket errors are handled so malformed connections do not crash the process. (Express should handle exceptions, but there are edgecases)
Insecure patterns:
- No reverse proxy in front of a public Node server, with defaults everywhere.
- Missing error handlers on server/socket objects.
- Extremely permissive timeouts and unlimited body sizes.
Detection hints:
- Inspect server creation (
http.createServer, https.createServer) and whether timeouts are set.
- Check proxy/gateway config for timeouts and max body size.
Fix:
- Explain how to configure reverse proxy and timeouts, set request size limits
- add ro
…(truncated)
1---2name: 2634-javascript-express-web-server-security-427835cb3description: Express (Node.js) Web Security Spec (Express 5.x / 4.19.2+, Node.js LTS)4---5# Express (Node.js) Web Security Spec (Express 5.x / 4.19.2+, Node.js LTS)67This document is designed as a **security spec** that supports:891. **Secure-by-default code generation** for new Express apps and routes.102. **Security review / vulnerability hunting** in existing Express code (passive “notice issues while working” and active “scan the repo and report findings”).1112It is intentionally written as a set of **normative requirements** (“MUST/SHOULD/MAY”) plus **audit rules** (what bad patterns look like, how to detect them, and how to fix/mitigate them).1314---1516## 0) Safety, boundaries, and anti-abuse constraints (MUST FOLLOW)1718* MUST NOT request, output, log, or commit secrets (API keys, passwords, private keys, session secrets, cookies, tokens).19* MUST NOT “fix” security by disabling protections (e.g., weakening cookie flags, disabling CSRF defenses for cookie-authenticated apps, enabling permissive CORS, trusting proxy headers from the open internet, turning on debugging/stack traces in production, disabling TLS without a replacement).20* MUST provide **evidence-based findings** during audits: cite file paths, code snippets, middleware/config values, and runtime assumptions that justify the claim.21* MUST treat uncertainty honestly: if a protection might exist in infrastructure (reverse proxy, gateway, WAF, CDN), report it as “not visible in app code; verify at runtime/config.”22* MUST prefer vetted libraries and platform controls over “roll your own” crypto/auth/session/CSRF. Express explicitly expects the application to validate/handle user input correctly; it does not do this automatically. ([Express][1])2324---2526## 1) Operating modes2728### 1.1 Generation mode (default)2930When asked to write new Express code or modify existing code:3132* MUST follow every **MUST** requirement in this spec.33* SHOULD follow every **SHOULD** requirement unless the user explicitly says otherwise.34* MUST prefer safe-by-default APIs and proven libraries over custom security code.35* MUST avoid introducing new risky sinks (shell execution, dynamic code evaluation, unsafe redirects, serving user files as HTML, template rendering from untrusted strings, unsafe filesystem paths, SSRF URL fetch endpoints, etc.).3637### 1.2 Passive review mode (always on while editing)3839While working anywhere in an Express repo (even if the user did not ask for a security scan):4041* MUST “notice” violations of this spec in touched/nearby code.42* SHOULD mention issues as they come up, with a brief explanation + safe fix.4344### 1.3 Active audit mode (explicit scan request)4546When the user asks to “scan”, “audit”, or “hunt for vulns”:4748* MUST systematically search the codebase for violations of this spec.49* MUST output findings in a structured format (see §2.3).5051Recommended audit order:52531. Entrypoints (server/app bootstrap), deployment manifests, Dockerfiles, process manager config, CI/CD.542. Express settings + middleware stack order (helmet, parsers, auth, sessions, CSRF, CORS).553. Proxy trust (`trust proxy`) and IP/protocol/host handling. ([Express][2])564. Auth flows, sessions, cookies, password reset links, redirect handling. ([Express][1])575. State-changing routes + CSRF protections (cookie-authenticated apps). ([OWASP Cheat Sheet Series][3])586. Template rendering and XSS defenses (HTML generation, CSP, `res.locals`). ([OWASP Cheat Sheet Series][4])597. File handling (uploads + downloads + static files) and path traversal. ([Express][5])608. Injection classes (SQL, NoSQL, command execution, unsafe deserialization). ([OWASP Cheat Sheet Series][6])619. Outbound requests (SSRF) and webhook/callback delivery. ([OWASP Cheat Sheet Series][7])6210. Rate limiting / brute-force defenses / abuse controls. ([Express][1])6311. Dependency hygiene / lockfiles / npm audit / vulnerable Express versions. ([Express][1])6465---6667## 2) Definitions and review guidance6869### 2.1 Untrusted input (treat as attacker-controlled unless proven otherwise)7071In Express, common untrusted inputs include:7273* `req.params` (route parameters)74* `req.query` (query string parameters; can be strings/arrays/objects depending on parsing) ([OWASP Cheat Sheet Series][8])75* `req.body` from `express.json()`, `express.urlencoded()`, `express.text()`, `express.raw()` ([Express][5])76* `req.headers` / `req.get(...)`77* `req.cookies` / `req.signedCookies` (if cookie parsing middleware is used)78* Upload metadata and filenames (e.g., multer `file.originalname`, `file.mimetype`)79* Any data from external systems (webhooks, third-party APIs, message queues)80* Any persisted user content (DB rows) that originated from users8182Special proxy note:8384* If `trust proxy` is enabled, values like `req.ip`, `req.hostname`, and `req.protocol` may be derived from `X-Forwarded-*` headers which **can be attacker-controlled** if your proxy chain is not correctly overwriting/removing them. ([Express][2])8586### 2.2 State-changing request8788A request is state-changing if it can create/update/delete data, change auth/session state, trigger side effects (purchase, email send, webhook send), or initiate privileged actions.8990### 2.3 Required audit finding format9192For each issue found, output:9394* Rule ID:95* Severity: Critical / High / Medium / Low96* Location: file path + function/route/middleware name + line(s)97* Evidence: the exact code/config snippet98* Impact: what could go wrong, who can exploit it99* Fix: safe change (prefer minimal diff)100* Mitigation: defense-in-depth if immediate fix is hard101* False positive notes: what to verify if uncertain102103---104105## 3) Secure baseline: minimum production configuration (MUST in production)106107This is the smallest “production baseline” that prevents common Express misconfigurations.108109Minimum baseline targets:110111* `helmet()` is used and configured (especially CSP where applicable), and fingerprinting is reduced (disable `x-powered-by`). ([Express][1])112* A custom 404 handler and a custom error handler exist, and production does not leak internal stack traces. ([Express][1])113* Cookie/session usage is deliberate:114115 * Not using default session cookie names116 * Cookies use secure attributes (`Secure`, `HttpOnly`, `SameSite`) as appropriate117 * Cookie-backed sessions never store secrets (they are readable by the client)118 * Server-side sessions never use MemoryStore in production. ([Express][1])119* Request body parsing has explicit limits (`express.json({ limit })`, `express.urlencoded({ limit, parameterLimit, depth })`). ([Express][5])120* `trust proxy` is configured explicitly to match your proxy topology; not blindly `true`. ([Express][2])121* Login/auth endpoints have brute-force protection and rate limiting. ([Express][1])122* Dependencies are regularly audited/updated (`npm audit` + advisory response). ([Express][1])123124---125126## 4) Rules (generation + audit)127128Each rule contains: required practice, insecure patterns, detection hints, and remediation.129130### EXPRESS-INPUT-001: Treat all user input as untrusted and validate it131132Severity: High133134Required:135136* MUST validate and normalize untrusted inputs before using them in security-sensitive logic or dangerous sinks (DB queries, redirects, filesystem, HTML output, shell commands). Ensure the untrusted inputs are type checked and structure checked before using or passing forward.137* SHOULD apply allowlists (known-good) rather than blocklists when feasible.138* MUST reject or safely handle unexpected types/shapes in `req.query`, `req.params`, and `req.body`.139140Insecure patterns:141142* Passing `req.query`, `req.params`, `req.body` directly into database/query builders, redirects, filesystem paths, or templates.143* Assuming `req.query.foo` is always a string (it can be an array/object depending on parsing). ([OWASP Cheat Sheet Series][8])144145Detection hints:146147* Identify “untrusted-to-sink” flows: request → sink (`res.redirect`, SQL execution, `sendFile`, `child_process`, template render, outbound fetch).148* Search for direct usage of `req.query.*`, `req.body.*`, `req.params.*` in sensitive calls.149150Fix:151152* Add schema validation (e.g., zod/joi/express-validator) at route boundaries.153* Normalize types (e.g., force IDs to integers; reject arrays when scalar expected).154155Notes:156157* Express production security guidance explicitly says input validation/handling is the application’s responsibility. ([Express][1])158159---160161### EXPRESS-REDIRECT-001: Prevent open redirects; validate redirect targets162163Severity: Medium164165Required:166167* MUST validate redirect destinations derived from untrusted input (`next`, `return_to`, `url`).168* SHOULD allowlist only same-site relative paths (preferred) or a strict allowlist of domains.169* MUST fall back to a safe default when validation fails.170171Insecure patterns:172173* `res.redirect(req.query.next)` with no validation.174* `res.redirect(req.body.url)` or `res.location(...)` using untrusted URLs.175176Detection hints:177178* Search for `res.redirect(` and `res.location(` and trace the source of the target.179* Look for query params named `next`, `redirect`, `return`, `url`.180181Fix:182183* Only allow relative paths (starting with `/`) and disallow `//`, backslashes, and encoded variants.184* If cross-domain redirects are required, allowlist exact hosts and enforce `https`.185186Notes:187188* Express documentation calls out open redirects as dangerous user input and shows validating the host before redirecting. ([Express][1])189* Keep Express updated: Express has had an open-redirect-related CVE affecting some versions, and upgrades are part of the mitigation posture. ([NVD][9])190191---192193### EXPRESS-HEADERS-001: Use Helmet (or equivalent) to set essential security headers194195Severity: Medium196197Required:198199* SHOULD use `helmet()` to set common security headers.200* SHOULD configure CSP realistically (avoid `unsafe-inline` where possible) for pages that render user-influenced content.201* SHOULD set `X-Content-Type-Options: nosniff`, clickjacking defenses (`X-Frame-Options` or CSP `frame-ancestors`), and appropriate referrer policy.202203NOTE: It is most important to set the CSP's script-src. All other directives are not as important and can generally be excluded for the ease of development.204205Insecure patterns:206207* No security headers set in app code and no evidence they are set at the edge.208* CSP missing on apps that display user content.209* Misconfigured framing headers that unintentionally allow clickjacking.210211Detection hints:212213* Search for `helmet(` usage; check if CSP is configured or disabled.214* Search for `res.setHeader(` / `res.set(` for security header setting.215* If not visible in app code, check nginx/CDN config; otherwise flag “verify at edge.”216217Fix:218219* Add `helmet()` early in middleware order and configure:220221 * CSP (`contentSecurityPolicy`)222 * Frame protections (`frameguard` or CSP `frame-ancestors`)223 * `X-Content-Type-Options` (`noSniff`)224225Notes:226227* Express production security best practices recommend Helmet and list headers Helmet sets by default. ([Express][1])228* OWASP HTTP Headers guidance is a useful reference when tuning policies. ([OWASP Cheat Sheet Series][10])229230---231232### EXPRESS-FINGERPRINT-001: Reduce fingerprinting by disabling `x-powered-by` and customizing error/404 responses233234Severity: Low (defense-in-depth)235236Required:237238* SHOULD disable `X-Powered-By` using `app.disable('x-powered-by')`.239* SHOULD provide a custom 404 handler and a custom error handler to avoid distinct default responses and to control information leakage.240241Insecure patterns:242243* Default `X-Powered-By: Express` header left enabled.244* Default Express 404/error responses in production with identifiable formatting and/or stack traces.245246Detection hints:247248* Search for `app.disable('x-powered-by')`.249* Check middleware tail for a custom 404 (`app.use((req,res)=>...)`) and a custom error handler (`app.use((err,req,res,next)=>...)`).250* Check if `NODE_ENV` is correctly set for production behavior (see EXPRESS-ERROR-001). ([Express][11])251252Fix:253254* Add:255256 * `app.disable('x-powered-by')`257 * A custom 404 handler258 * A custom error handler that logs server-side and returns generic messages client-side259260Notes:261262* Express docs explicitly recommend disabling `x-powered-by` and adding your own not-found and error handlers. ([Express][1])263264---265266### EXPRESS-COOKIE-001: Cookies must use secure attributes and minimal scope267268Severity: Medium269270Required:271272* MUST set cookie flags appropriately for any authentication/session cookie:273274 * `Secure` when HTTPS (production) IMPORTANT NOTE: Only set `Secure` in production environment if TLS is configured. When running in a local dev environment over HTTP, do not set `Secure` property on cookies. You should do this conditionally based on if the app is running in production mode. You should also include a property like `SESSION_COOKIE_SECURE` which can be used to disable `Secure` cookies when testing over HTTP.275 * `HttpOnly` for auth/session cookies276 * `SameSite` set deliberately (`Lax` is a common baseline; `Strict` if compatible; `None` only with `Secure` and a justified cross-site need)277* SHOULD avoid setting `domain` broadly (avoid “all subdomains” unless required).278* SHOULD set bounded expiry appropriate to risk and UX.279280Insecure patterns:281282* Session/auth cookies without `HttpOnly`.283* Cookies without `Secure` in production HTTPS.284* `SameSite=None` + cookie-authenticated state-changing endpoints without CSRF protections.285286Detection hints:287288* Search for `res.cookie(`, `Set-Cookie`, `cookie: { ... }`, `express-session`, `cookie-session`.289* Verify cookie flags in session middleware configuration.290291Fix:292293* Set these attributes centrally in session/cookie middleware configuration.294295Notes:296297* Express production security guidance lists cookie security options (`secure`, `httpOnly`, etc.). ([Express][1])298* `res.cookie()` ultimately sets `Set-Cookie` with options; defaults follow RFC 6265 behavior when options are omitted. ([Express][5])299* OWASP session management guidance is relevant for choosing flags and lifetimes. ([OWASP Cheat Sheet Series][12])300301---302303### EXPRESS-SESS-001: Do not use the default session cookie name; avoid session fingerprinting304305Severity: Low (defense-in-depth)306307Required:308309* SHOULD override the default session cookie name (e.g., do not keep `connect.sid` when using `express-session`).310* SHOULD use a generic name (e.g., `sessionId`) unless you have a compatibility reason.311312Insecure patterns:313314* `express-session` used with no `name:` configured (default cookie name).315* Multiple apps on the same domain sharing a cookie name accidentally.316317Detection hints:318319* Search for `express-session` config blocks; check for `name:`.320321Fix:322323* Set `name: 'sessionId'` (or similar) in `express-session` options.324325Notes:326327* Express docs explicitly recommend not using the default session cookie name to reduce fingerprinting. ([Express][1])328329---330331### EXPRESS-SESS-002: Session storage and lifecycle must be production-safe332333Severity: High334335Required:336337* MUST NOT use `MemoryStore` in production (it is not designed for production use).338* MUST store session secrets outside source control and rotate them safely.339* SHOULD regenerate sessions on login / privilege changes to reduce session fixation risk.340* MUST NOT store sensitive secrets in client-readable cookie sessions.341342Insecure patterns:343344* `app.use(session({ store: new MemoryStore(), ... }))` or missing store (defaults to MemoryStore).345* Hard-coded for example: `secret: 'keyboard cat'` / `secret: 's3Cur3'` in repo.346* Using `cookie-session` to store access tokens, refresh tokens, or PII.347348Detection hints:349350* Search for `express-session` and look for `MemoryStore` usage or missing `store`.351* Search for `secret:` in session config and check if it’s hard-coded.352* Look for `req.session = ...` patterns and whether sensitive data is stored.353354Fix:355356* Use a production session store (Redis, database-backed, etc.).357* Load secrets from environment/secret manager.358* On login: `req.session.regenerate(...)` or equivalent flow with safe privilege re-binding.359360Notes:361362* `express-session` explicitly warns that `MemoryStore` is not designed for production. ([Express][1])363* `express-session` documents rotating secrets and session regeneration to guard against fixation. ([Express][1])364* Express notes that cookie-backed sessions serialize data into the cookie and that cookie data is visible to the client; keep it small and non-secret. ([Express][1])365366---367368### EXPRESS-CSRF-001: Cookie-authenticated state-changing requests MUST be CSRF-protected369370Severity: High371372- IMPORTANT NOTE: If cookies are not being used for auth (ie auth is via Authentication header or other passed token), then there is no CSRF risk.373374Required:375376* MUST protect all state-changing endpoints (POST/PUT/PATCH/DELETE) that rely on cookies for authentication.377* SHOULD use a well-understood CSRF mitigation (token-based is the typical baseline).378* MAY add defense-in-depth: Origin/Referer validation, Fetch Metadata enforcement, SameSite cookies, custom header requirements for XHR/fetch—**but do not treat these as a full replacement** unless explicitly designed and justified.379* MUST use at a minimum require a custom HTTP header if form based CRSF tokens are not practical, as this is the second strongest method.380381IMPORTANT NOTE:382383* If authentication is done via `Authorization: Bearer ...` headers (and not cookies), classic browser CSRF is typically not applicable; 384385Insecure patterns:386387* Cookie-authenticated endpoints that change state with no CSRF protection.388* Using GET for state-changing actions (amplifies CSRF risk).389* “CSRF protection” that only checks a user-controlled field.390391Detection hints:392393* Enumerate routes with methods other than GET/HEAD and identify whether cookies gate auth.394* Look for presence/absence of CSRF middleware and token checks.395* Check JSON APIs too, not only HTML forms.396397Fix:398399* Implement CSRF tokens for cookie-authenticated flows.400* Add Origin/Referer checks where feasible, and ensure SameSite is set appropriately.401402Notes:403404* OWASP CSRF guidance and OWASP Node.js guidance both recommend anti-CSRF tokens as a standard control for web apps. ([OWASP Cheat Sheet Series][3])405406---407408### EXPRESS-CORS-001: CORS must be explicit and least-privilege409410Severity: Medium (High if misconfigured with credentials)411412Required:413414* If CORS is not needed, MUST keep it disabled.415* If CORS is needed:416417 * MUST allowlist trusted origins (do not reflect arbitrary `Origin` without validation).418 * MUST NOT combine broad origins with credentialed cookies (`Access-Control-Allow-Credentials: true`).419 * SHOULD restrict methods, headers, and exposed headers to what’s required.420421Insecure patterns:422423* `Access-Control-Allow-Origin: *` with `Access-Control-Allow-Credentials: true`.424* Reflecting `Origin` for all requests without allowlist validation.425* Applying permissive CORS middleware globally when only a subset needs cross-origin access.426427Detection hints:428429* Search for `cors(`, `Access-Control-Allow-Origin`, `Access-Control-Allow-Credentials`.430* Inspect whether cookies are used for auth on endpoints exposed cross-origin.431432Fix:433434* Implement strict origin allowlist and ensure credentialed requests only for intended origins.435* Consider splitting CORS config per route group rather than global.436437Notes:438439* OWASP HTTP header guidance covers security implications of response headers, including those that affect browser behavior; use it as a reference when reviewing header posture. ([OWASP Cheat Sheet Series][10])440441---442443### EXPRESS-PROXY-001: Reverse proxy trust (`trust proxy`) must be configured correctly444445Severity: Medium (High if using IP based authentication)446447Required:448449* If behind a reverse proxy/LB, MUST configure `app.set('trust proxy', ...)` to match the real proxy chain.450* MUST NOT blindly set `trust proxy = true` unless you fully control the proxy behavior and header rewriting.451* MUST ensure the last trusted proxy overwrites/removes `X-Forwarded-For`, `X-Forwarded-Host`, and `X-Forwarded-Proto` so clients cannot spoof them.452453Insecure patterns:454455* `app.set('trust proxy', true)` in an app directly exposed to the internet or behind unknown proxies.456* Using `req.ip`, `req.protocol`, `req.hostname` for security decisions without correct proxy trust configuration.457* Rate limiting keyed by `req.ip` with spoofable forwarded headers.458459Detection hints:460461* Search for `app.set('trust proxy'`.462* Check infra docs (nginx/LB) for header rewriting behavior.463* Identify any security logic using `req.ip`, `req.ips`, `req.protocol`, `req.hostname`.464465Fix:466467* Set `trust proxy` to a hop count, explicit IP/subnet list, or a custom function matching your network.468* Ensure proxies overwrite forwarded headers.469470Notes:471472* Express explicitly warns that when `trust proxy` is `true`, the client IP is derived from `X-Forwarded-For`, and if proxies don’t overwrite forwarded headers, the client can provide any value. It also describes that enabling trust proxy impacts `req.hostname` and `req.protocol` derived from forwarded headers. ([Express][2])473474---475476### EXPRESS-BODY-001: Request body size and parsing limits MUST be set appropriately477478Severity: Low479480Required:481482* SHOULD set explicit body size limits for:483484 * `express.json({ limit })`485 * `express.urlencoded({ limit, parameterLimit, depth })`486* SHOULD only enable the parsers you need; do not parse large bodies by default for all routes.487* SHOULD enforce additional limits at the reverse proxy / gateway level.488489Insecure patterns:490491* No explicit body limits (accepting arbitrarily large JSON/urlencoded).492* Global parsers applied to all routes when only some need bodies.493* `parameterLimit` very high without justification (DoS potential).494495Detection hints:496497* Search for `express.json(` and confirm `limit` is set (or consciously accepted).498* Search for `express.urlencoded(` and inspect `limit`, `parameterLimit`, and `depth`.499* Review upload/webhook endpoints for special parsing needs.500501Fix:502503* Configure parsers with conservative defaults and override per route group when needed.504505Notes:506507* Express documents `express.json` options (including `limit`, defaulting to 100kb) and explicitly notes `req.body` is untrusted and should be validated. ([Express][5])508* Express documents `express.urlencoded` options including `limit`, `parameterLimit`, and `depth`. ([Express][5])509* OWASP Node.js guidance also recommends setting request size limits. ([OWASP Cheat Sheet Series][8])510511---512513### EXPRESS-INPUT-002: Prevent HTTP Parameter Pollution and type confusion in `req.query`514515Severity: Medium516517Required:518519* MUST treat `req.query` values as potentially multi-valued (array/object), depending on query parsing.520* SHOULD reject ambiguous multi-valued parameters for security-sensitive fields (e.g., `role`, `isAdmin`, `redirect`, `amount`, `userId`).521* SHOULD consider explicit parsing or dedicated middleware if parameter pollution is a concern.522523Insecure patterns:524525* `if (req.query.admin) { ... }` without type checks (arrays/objects may coerce truthy).526* Passing `req.query` directly into ORM/NoSQL query objects.527528Detection hints:529530* Search for security-sensitive comparisons on `req.query.*` without type enforcement.531* Look for code that assumes query params are strings.532533Fix:534535* Validate shape: enforce string-only for certain params and reject arrays/objects.536* Normalize query parsing settings (simple vs extended) where applicable and documented.537538Notes:539540* OWASP Node.js cheat sheet explicitly highlights that Express query parsing can produce strings, arrays, or objects and recommends preventing HTTP Parameter Pollution. ([OWASP Cheat Sheet Series][8])541542---543544### EXPRESS-XSS-001: Prevent reflected/stored XSS in HTML responses and templating545546Severity: High547548Required:549550* MUST escape untrusted content in HTML output (templates should auto-escape by default; do not bypass).551* MUST NOT inject untrusted strings into HTML without escaping/sanitization.552* SHOULD set CSP (via Helmet) for apps rendering user-controlled content.553* SHOULD keep `res.locals` free of user-controlled input intended for templates unless it is validated/escaped.554555Insecure patterns:556557* `res.send("<div>" + req.query.q + "</div>")`558* Passing untrusted HTML through “safe” template flags/filters.559* Writing untrusted strings into `res.locals` and then rendering without escaping.560561Detection hints:562563* Search for `res.send(` with strings containing user input.564* Search for template “safe” flags (engine-specific) and trace data origin.565* Search for assignments to `res.locals` and whether they might contain untrusted data.566567Fix:568569* Use a template engine with autoescaping; pass only validated data.570* For rich text that must contain HTML, use a trusted sanitizer and an allowlist policy.571* Add CSP with realistic directives.572573Notes:574575* Express API docs explicitly warn that `res.locals` “should not contain user-controlled input” and is often used to expose things like CSRF tokens to templates. ([Express][5])576* OWASP XSS prevention guidance provides standard output-encoding and policy recommendations. ([OWASP Cheat Sheet Series][4])577* Helmet can mitigate some XSS classes via headers such as CSP. ([Express][1])578579---580581### EXPRESS-TEMPLATE-001: Never render untrusted templates or template paths (SSTI / LFI risk)582583Severity: Critical (if you can prove template strings/paths are user/attacker-controlled)584585Required:586587* MUST NOT render templates whose contents or template path/name is influenced by untrusted input.588* MUST NOT load templates from user-controlled filesystem locations.589* SHOULD treat “email template editors”, “theme engines”, and “CMS-like template storage” as high-risk designs requiring sandboxing and isolation.590591Insecure patterns:592593* `res.render(req.query.view, data)` where `view` is not allowlisted.594* Rendering a template from a string that includes user input (engine-specific).595* Loading templates from uploads directories.596597Detection hints:598599* Search for `res.render(` where the first argument is derived from request/DB without allowlist.600* Search for template compilation APIs (engine-specific) fed by user content.601602Fix:603604* Use allowlisted template names and a fixed templates directory.605* If user-defined templates are required, implement strict sandboxing and isolate execution.606607Notes:608609* Express’s template system depends on the chosen engine; assume unsafe if user input influences template selection or source.610611---612613### EXPRESS-FILES-001: Prevent path traversal and unsafe file serving (sendFile/download)614615Severity: High616617Required:618619* MUST NOT pass user-controlled filesystem paths directly to `res.sendFile()` / `res.download()` / filesystem APIs.620* SHOULD use `res.sendFile` with a fixed `root` and strict options (e.g., deny dotfiles) when serving user-selected files from a directory.621* MUST enforce authorization checks before serving user-specific files.622623Insecure patterns:624625* `res.sendFile(req.query.path)` or `res.download(req.params.file)` with no root restriction.626* File-serving routes that accept `..` segments, encoded traversal, or absolute paths.627628Detection hints:629630* Search for `res.sendFile(` and trace the `path` argument origin.631* Search for `res.download(` and trace the `path` argument origin.632* Look for `fs.readFile`/`createReadStream` on paths derived from requests.633634Fix:635636* Use an identifier-to-path mapping stored server-side (DB), not raw paths from clients.637* Use `root: <trusted_base_dir>` and `dotfiles: 'deny'` where appropriate; validate the filename component strictly.638639Notes:640641* Express’s `res.sendFile` docs show using a `root` option and `dotfiles: 'deny'` as part of a safe serving configuration. ([Express][5])642* `res.download` transfers the file as an attachment, but you still must control/validate the underlying `path`. ([Express][5])643644---645646### EXPRESS-STATIC-001: Harden `express.static` / serve-static and never serve untrusted uploads as active content647648Severity: Medium (if serving untrusted user files if there are not robust limits tot eh file extensions)649650Required:651652* MUST NOT serve user uploads from a public static directory as active content (especially HTML/JS/SVG) unless explicitly intended and sandboxed. If sure that the content is inactive (png, jpg, other images etc) then it may be safe. It may be good to validate image file extensions are allow-listed before serving them.653* SHOULD configure static serving to:654655 * deny/ignore dotfiles656 * avoid unintended directory indexes if not needed657 * apply appropriate cache controls for immutable assets658659Insecure patterns:660661* `app.use(express.static('uploads'))` where users can upload arbitrary files.662* Serving uploaded HTML or SVG inline from the same origin as the app.663664Detection hints:665666* Search for `express.static(` and identify served directories.667* Compare served directories with upload storage locations.668* Check for `dotfiles` and `index` options in static middleware.669670Fix:671672* Store uploads outside any static web root and serve via controlled routes that set safe `Content-Type` and `Content-Disposition: attachment` when appropriate.673* Configure `express.static(root, { dotfiles: 'deny'|'ignore', index: false (if desired) })`.674675Notes:676677* Express documents `express.static` options, including `dotfiles` behavior and `index`. ([Express][5])678679---680681### EXPRESS-UPLOAD-001: File uploads must be validated, stored safely, and served safely682683Severity: Low - Medium684685Required:686687* SHOULD enforce upload size limits (app + edge).688* MUST validate file type using allowlists and content checks (not only filename extension).689* MUST store uploads outside executable/static roots when possible.690* SHOULD generate server-side filenames (random IDs); do not trust original names.691* MUST serve potentially active formats safely (download attachment) unless explicitly intended.692693Insecure patterns:694695* Accepting arbitrary file types and serving them back inline.696* Using `file.originalname` as the storage path.697* Missing size/type validation.698699Detection hints:700701* Look for multer/busboy/formidable usage and check for `limits`.702* Check where uploaded files are written and how they are served.703* Check whether uploads end up under `public/` or any `express.static` root.704705Fix:706707* Implement allowlist validation + safe storage + safe serving, per OWASP upload guidance.708709Notes:710711* OWASP File Upload guidance covers allowlists, content validation, storage, and safe serving patterns. ([OWASP Cheat Sheet Series][13])712713---714715### EXPRESS-INJECT-001: Prevent SQL injection (use parameterized queries / ORM)716717Severity: High718719Required:720721* MUST use parameterized queries or an ORM/query builder that parameterizes under the hood.722* MUST NOT build SQL via string concatenation/template literals with untrusted input.723724Insecure patterns:725726* ``db.query(`SELECT * FROM users WHERE id = ${req.query.id}`)``727* `"SELECT ... WHERE name = '" + req.body.name + "'"`728729Detection hints:730731* Grep for `SELECT`, `INSERT`, `UPDATE`, `DELETE` strings in JS/TS.732* Trace untrusted input into `.query(...)`, `.execute(...)`, or raw SQL APIs.733734Fix:735736* Replace with parameterized queries (placeholders) or ORM query APIs.737* Validate types (e.g., integer IDs) before querying.738739Notes:740741* OWASP SQL injection prevention guidance strongly favors parameterized queries. ([OWASP Cheat Sheet Series][6])742743---744745### EXPRESS-INJECT-002: Prevent NoSQL injection / operator injection (Mongo-style)746747Severity: High (app-dependent)748749Required:750751* MUST validate types and schemas for any query object built from untrusted input.752* MUST prevent operator injection (e.g., `$ne`, `$gt`, `$where`) if user input is merged into query objects.753* SHOULD consider defensive libraries/middleware when appropriate.754755Insecure patterns:756757* `collection.find(req.body)` where the body is attacker-controlled.758* Merging `req.query`/`req.body` into Mongo queries without schema validation.759760Detection hints:761762* Search for `find(`, `findOne(`, `aggregate(` calls where argument is request-derived.763* Check for patterns like `{ ...req.query }` or `Object.assign(query, req.body)`.764765Fix:766767* Use schema validation at boundary; explicitly construct query objects from validated fields only.768769Notes:770771* OWASP Node.js cheat sheet discusses input validation and mentions Node ecosystem modules commonly used for sanitization in NoSQL contexts. ([OWASP Cheat Sheet Series][8])772773---774775### EXPRESS-CMD-001: Prevent OS command injection (child_process)776777Severity: Critical to High (depends on exposure), please prove it is user/attacker controlled778779Required:780781* MUST avoid executing shell commands with untrusted input.782* If subprocess is necessary:783784 * MUST avoid `exec()` / `execSync()` with attacker-influenced strings785 * MUST NOT use `shell: true` with attacker-influenced data786 * SHOULD use `spawn()` with an argument array and strict allowlists. Ensure the executable is hardcoded or allow-listed, do not use a user supplied command name.787 * SHOULD place user-controlled values after `--` when supported by the subcommand to avoid flag injection788789Insecure patterns:790791* `exec(req.query.cmd)`792* `exec(`convert ${userPath} ...`)`793* `spawn('sh', ['-c', userString])`794* `spawn(userString, ['foo'])`795796Detection hints:797798* Search for `child_process`, `exec(`, `execSync(`, `spawn(`, `fork(`.799* Trace request/DB data into command construction.800801Fix:802803* If possible, write the functionality in javascript or use a library instead of subprocess.804* If unavoidable, hard-code command and strictly allowlist parameters.805806Notes:807808* OWASP OS command injection defense guidance covers avoid-shell and allowlist patterns. ([OWASP Cheat Sheet Series][14])809810---811812### EXPRESS-SSRF-001: Prevent server-side request forgery (SSRF) in outbound HTTP813814Severity: Medium (High in cloud/LAN deployments)815816NOTE: This is mostly only applicable to apps which will be deployed in a cloud/LAN setup or have other http services on the same box. Sometimes the feature requires this functionality unavoidably (webhooks).817818Required:819820* MUST treat outbound requests to user-provided URLs as high risk if there are other reachable private http endpoints.821* SHOULD validate and restrict destinations (allowlist hosts/domains) for any user-influenced URL fetch.822* SHOULD block access to:823824 * localhost / private IP ranges / link-local825 * cloud metadata endpoints826* MUST allow only `http`/`https` for URL fetch features (to avoid schemas such as `file:`,`javascript:`)827* SHOULD set timeouts and restrict redirects.828829Insecure patterns:830831* `fetch(req.query.url)`832* “URL preview” / “import from URL” endpoints that accept arbitrary URLs.833834Detection hints:835836* Search for `fetch(`, `axios(`, `got(`, `request(`, `node-fetch` usage where URL originates from users/DB.837* Review webhook testers, previewers, image fetchers.838839Fix:840841* Enforce scheme allowlist, host allowlist, DNS/IP resolution checks, timeouts, and redirect policy.842* Consider network egress controls at infrastructure level.843844Notes:845846* OWASP SSRF prevention guidance provides standard controls and common pitfalls. ([OWASP Cheat Sheet Series][7])847848---849850### EXPRESS-ERROR-001: Error handling MUST not leak sensitive details in production851852Severity: Low853854Required:855856* SHOULD define a centralized error handler (`app.use((err, req, res, next) => ...)`) at the end of middleware.857* MUST avoid returning stack traces, internal error messages, or secrets to clients in production.858* SHOULD log errors server-side with appropriate redaction.859* SHOULD ensure the app runs with production settings so default behavior doesn’t leak details.860* MUST avoid logging or returning sensitive information such as secrets, env vars, sessions, cookies in error messages in production.861862Insecure patterns:863864* Returning `err.stack` to clients.865* Using dev-only error middleware in production.866* `NODE_ENV` left as development, causing verbose error responses.867868Detection hints:869870* Verify there is a final error-handling middleware.871* Search for `res.status(500).send(err)` or similar.872* Check production environment variables and startup scripts.873874Fix:875876* Add a production-safe error handler that returns generic messages and logs details internally.877* Ensure environment is configured for production behavior.878879Notes:880881* Express production security guidance recommends custom error handling. ([Express][1])882* Express error handling docs describe the default error handler behavior and how production mode affects what is exposed. ([Express][11])883884---885886### EXPRESS-AUTH-001: Prevent brute-force attacks against authorization endpoints887888Severity: Medium889890NOTE: This is highly application specific and while it is good to bring to the attention of the user, it is hard to fix without additional complex configurations. Prefer to inform the user and if they request you to help implement a solution, help walk them through possible solutions.891892Required:893894* SHOULD protect login/auth endpoints against brute forcing.895* SHOULD rate-limit by:896897 1. consecutive failed attempts per username+IP898 2. failed attempts per IP over a time window899900Insecure patterns:901902* Unlimited login attempts.903904Detection hints:905906* Identify all auth endpoints and check for rate limiting/throttling.907* Search for `rate-limiter-flexible`, `express-rate-limit`, or gateway policies.908909Fix:910911* Implement rate-limiting/throttling (app or edge). Express docs point to `rate-limiter-flexible` as a tool for this approach. ([Express][1])912913Notes:914915* OWASP Node.js cheat sheet also recommends precautions against brute forcing. ([OWASP Cheat Sheet Series][8])916917---918919### EXPRESS-DEPS-001: Dependency and patch hygiene (Express + Node + critical middleware)920921Severity: Medium / Low922923NOTE: `npm audit` often returns a large number of insignificant "vulnerabilities" which do not actually matter. You should only focus on Express or other extremely critical packages, ignoring ones listed in dev tools, bundlers, etc.924925Do not upgrade packages without concent from the user. This may break existing code in unexpected ways. Instead, inform them of the outdated packages.926927Required:928929* MUST keep Express on a maintained version line (avoid EOL major versions).930* MAY use `npm audit` in CI and during maintenance work.931* SHOULD pin dependencies via lockfiles and review major updates carefully.932933Insecure patterns:934935* Running EOL Express versions (e.g., very old major lines).936* Ignoring `npm audit` findings without triage.937* Unpinned dependency ranges that auto-upgrade into insecure versions.938939Detection hints:940941* Check `package.json` and lockfiles for `express` version and other critical middleware versions.942* Inspect CI pipelines for `npm audit`/SCA steps.943944Fix:945946* Upgrade to latest stable Express and apply patches.947* Add automated dependency scanning and upgrade process.948949Notes:950951* Express production security guidance emphasizes that dependency vulnerabilities can compromise the app, and recommends `npm audit`. ([Express][1])952* Track security issues affecting Express versions (including known open-redirect-related CVEs). ([NVD][9])953954---955956### EXPRESS-DOS-001: Configure DoS protections (timeouts, limits, reverse proxy)957958Severity: Low959960NOTE: It may be hard to tell from the provided application context if the application runs behind a reverse proxy. You can inform the user or recommend one, but do not attempt to configure one without them initiating it. This is highly deployment dependant.961962Required:963964* SHOULD use a reverse proxy to provide caching, load balancing, and filtering controls when feasible.965* MAY configure server/proxy timeouts and connection limits to reduce exposure to Slowloris and similar DoS patterns.966* MUST ensure server/socket errors are handled so malformed connections do not crash the process. (Express should handle exceptions, but there are edgecases)967968Insecure patterns:969970* No reverse proxy in front of a public Node server, with defaults everywhere.971* Missing error handlers on server/socket objects.972* Extremely permissive timeouts and unlimited body sizes.973974Detection hints:975976* Inspect server creation (`http.createServer`, `https.createServer`) and whether timeouts are set.977* Check proxy/gateway config for timeouts and max body size.978979Fix:980981* Explain how to configure reverse proxy and timeouts, set request size limits982* add ro983984…(truncated)