Security misconfiguration (OWASP A05) is the vulnerability you get for free: default passwords, stack traces shown to users, a debug endpoint that shipped to production, a storage bucket anyone can list. Nothing in the code is wrong; the system around it was set up carelessly, and automated scanners find such issues within minutes of a deployment.
In this lesson you will learn where misconfigurations hide, how to harden an Express application and its runtime, why XML parsing needs special care, and how to keep configuration correct over time instead of fixing it once.
| Area | Typical mistake | Consequence |
|---|---|---|
| Credentials | Default admin password, sample accounts left enabled | Immediate takeover |
| Error handling | Stack traces, SQL errors and file paths in responses | Reconnaissance for further attacks |
| Features | Debug routes, GraphQL introspection, directory listing, unused ports | Extra attack surface |
| Headers | Missing Strict-Transport-Security, CSP or X-Content-Type-Options | Weaker browser protections |
| Cloud resources | Public buckets, security groups open to 0.0.0.0/0, dashboards without auth | Data exposure |
| Parsers | XML external entities enabled | File disclosure and SSRF |
The common thread: each item is a setting, not a line of business logic, so it is invisible in code review unless configuration is reviewed too.
The sample code covers the application layer. Three settings do most of the work:
NODE_ENV=production turns off Express's verbose HTML error pages, enables view caching and makes many libraries choose production behaviour.helmet() for headers and app.disable("x-powered-by") so responses do not announce the stack.NODE_ENV=production node server.jsAlso remove anything that exists only for development: seed routes, /debug, GraphQL playground, request-body logging, and CORS set to * "just for now".
Configuration extends below the application. A container image and its environment should be minimal and explicit:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev # no test tools or dev servers in the image
COPY . .
USER node # never run as root
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]Combine this with a firewall that exposes only the ports you serve, a reverse proxy that terminates TLS, and secrets injected at runtime rather than baked into the image.
XML parsers can be told, inside the document, to load an "external entity" from a file path or URL. If your API accepts XML and the parser honours those instructions, an attacker can read /etc/passwd or reach internal services through your server. This used to be its own OWASP category and is now part of misconfiguration because the fix is a parser setting:
const { XMLParser } = require("fast-xml-parser");
// fast-xml-parser does not resolve external entities; keep DTD processing off
const parser = new XMLParser({ processEntities: false });
const doc = parser.parse(req.body);Prefer JSON where you can. When XML is required, choose a parser that ignores DTDs by default and never enable entity expansion for untrusted input.
A one-time hardening pass decays. Put configuration under the same discipline as code:
Why should a production API return a generic error message instead of the exception details?
NODE_ENV=production, use a generic final error handler and helmet(), and remove development-only routes.Next lesson: Vulnerable Components and Supply-Chain Security: npm audit, Lockfiles and SBOM — keeping the code you did not write from becoming your biggest risk.