XSS in Express.js Applications
Express.js provides no built-in XSS protection. Every response that includes user input - whether through template engines, res.send(), or JSON responses rendered in HTML - must be explicitly escaped. This makes Express apps particularly susceptible to XSS when developers overlook output encoding.
Scan Your Express AppHow XSS Manifests in Express
Express apps are vulnerable when user input is reflected in HTML responses without encoding. Common patterns include: Using res.send() to return HTML that includes query parameters or form data directly. Template engines like EJS and Pug escape by default with their standard syntax, but unescaped output tags (<%- %> in EJS, != in Pug) bypass this protection. Error pages that reflect the requested URL or error message back to the user are a frequent XSS vector. Express error handlers often include the path or query string in error responses. JSON API responses consumed by frontend JavaScript can also lead to XSS if the frontend renders the JSON values using innerHTML or similar unsafe methods.
Real-World Impact
An Express.js application serving a 404 page displayed the requested URL in the error message using res.send(). An attacker crafted a URL containing a script payload that stole admin cookies. Because the URL was shared in a support ticket, a support agent clicked it and had their admin session hijacked. Another common exploit targets Express apps using EJS with unescaped output (<%- %>) for rendering user profiles, allowing stored XSS through profile fields.
Step-by-Step Fix
Install and configure Helmet
Helmet sets security headers including Content Security Policy to prevent inline script execution.
import helmet from 'helmet';
import express from 'express';
const app = express();
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
},
},
}));Use escaped template output
Always use auto-escaping syntax in your template engine. Never use unescaped output for user data.
<!-- EJS: UNSAFE -->
<p>Welcome, <%- user.name %></p>
<!-- EJS: SAFE (auto-escapes HTML) -->
<p>Welcome, <%= user.name %></p>
<!-- Pug: UNSAFE -->
p!= user.bio
<!-- Pug: SAFE -->
p= user.bioEncode user input in HTML responses
When building HTML in route handlers, always encode user-supplied values.
import { escape } from 'html-escaper';
// UNSAFE
app.get('/search', (req, res) => {
res.send(`<h1>Results for: ${req.query.q}</h1>`);
});
// SAFE
app.get('/search', (req, res) => {
const safeQuery = escape(String(req.query.q || ''));
res.send(`<h1>Results for: ${safeQuery}</h1>`);
});Secure error handlers
Ensure error pages do not reflect user input without encoding.
import { escape } from 'html-escaper';
// UNSAFE error handler
app.use((req, res) => {
res.status(404).send(`<h1>Page ${req.path} not found</h1>`);
});
// SAFE error handler
app.use((req, res) => {
res.status(404).send(`<h1>Page not found</h1>`);
// Or if you must show the path:
// res.status(404).send(`<h1>Page ${escape(req.path)} not found</h1>`);
});Prevention Best Practices
1. Use template engine auto-escaping (EJS: <%= %>, Pug: = syntax) and never use unescaped output for user data. 2. Set the Content-Type header correctly - return application/json for API responses, not text/html. 3. Use the helmet middleware to add security headers including CSP. 4. Sanitize HTML content with DOMPurify on the server side before storing or rendering. 5. Never reflect URL paths, query parameters, or headers back to users without encoding.
How to Test
1. Test URL parameters: visit /search?q=<script>alert(1)</script> and check if the script executes. 2. Submit <img src=x onerror=alert(1)> in form fields and check rendered output. 3. Search for <%- and != in EJS/Pug templates - these bypass auto-escaping. 4. Search for res.send() calls that include req.query, req.params, or req.body values. 5. Use Vibe App Scanner to automatically detect XSS patterns in your Express application.
Frequently Asked Questions
Does Express.js have built-in XSS protection?
No. Express itself provides no XSS protection. Security depends entirely on your template engine configuration, output encoding practices, and security middleware like Helmet. Template engines like EJS and Pug have auto-escaping in their default syntax, but it is easily bypassed with unescaped output tags.
Is Helmet enough to prevent XSS in Express?
Helmet adds a Content Security Policy header that blocks inline scripts, which mitigates many XSS attacks. However, CSP alone is not sufficient - you still need to sanitize output and use proper template escaping. CSP is a defense-in-depth measure, not a replacement for proper encoding.
How do I sanitize HTML in Express server-side?
Use the isomorphic-dompurify package which works in Node.js. Sanitize HTML content before storing it in the database and again before rendering it in templates. This double-sanitization approach protects against both stored XSS and any data that might have been inserted before sanitization was added.
Related Security Resources
Is Your Express App Vulnerable to XSS?
VAS automatically scans for xss vulnerabilities in Express applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Express stack.