| name | security-express |
| description | Review Express.js security audit patterns for middleware and routes. Use for auditing Helmet.js, CORS, body-parser limits, and auth middleware. Use proactively when reviewing Express.js apps.
Examples:
- user: "Secure my Express app" → add Helmet.js and disable x-powered-by
- user: "Check Express CORS config" → verify origin allowlists and credentials
- user: "Review Express auth middleware" → check route order and coverage
- user: "Scan for Express path traversal" → verify path normalization and validation
- user: "Audit Express session config" → check secure, httpOnly, and sameSite flags |
Security audit patterns for Express.js applications covering essential security middleware, CORS configuration, auth patterns, and common vulnerabilities.
Essential Security Middleware
Helmet.js (Security Headers)
const app = express();
const helmet = require('helmet');
app.use(helmet());
Check if Helmet is installed and used. It sets:
- Content-Security-Policy
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- Strict-Transport-Security
- And more...
Disable X-Powered-By
const app = express();
app.disable('x-powered-by');
CORS Configuration
app.use(cors());
app.use(cors({ origin: '*' }));
app.use(cors({
origin: true,
credentials: true
}));
app.use(cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
}));
app.use(cors({
origin: (origin, callback) => {
const allowed = ['https://app.example.com'];
if (!origin || allowed.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
}));
Body Parser Limits
app.use(express.json());
app.use(express.json({ limit: '100kb' }));
app.use(express.urlencoded({ extended: true, limit: '100kb' }));
Auth Middleware Patterns
Missing Auth on Routes
app.get('/api/admin/users', async (req, res) => {
res.json(await User.find());
});
app.get('/api/admin/users', requireAuth, requireAdmin, async (req, res) => {
res.json(await User.find());
});
Middleware Order Matters
app.use(express.static('uploads'));
app.use(requireAuth);
app.use('/public', express.static('public'));
app.use(requireAuth);
app.use('/uploads', express.static('uploads'));
Router-Level Auth Gaps
const adminRouter = express.Router();
adminRouter.use(requireAuth);
adminRouter.get('/users', getUsers);
adminRouter.delete('/users/:id', deleteUser);
const apiRouter = express.Router();
apiRouter.get('/health', getHealth);
apiRouter.use(requireAuth);
apiRouter.get('/users', getUsers);
Common Vulnerabilities
SQL/NoSQL Injection
const user = await db.query(`SELECT * FROM users WHERE id = ${req.params.id}`);
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
const user = await User.findOne({ email: req.body.email });
if (typeof req.body.email !== 'string') return res.status(400).json({ error: 'Invalid email' });
Path Traversal
app.get('/files/:filename', (req, res) => {
res.sendFile(`./uploads/${req.params.filename}`);
});
const path = require('path');
app.get('/files/:filename', (req, res) => {
const filename = path.basename(req.params.filename);
const filepath = path.join(__dirname, 'uploads', filename);
if (!filepath.startsWith(path.join(__dirname, 'uploads'))) {
return res.status(400).json({ error: 'Invalid path' });
}
res.sendFile(filepath);
});
Error Handling
app.use((err, req, res, next) => {
res.status(500).json({ error: err.stack });
});
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: 'Internal server error' });
});
Session Security
app.use(session({
secret: 'keyboard cat',
cookie: { secure: false },
}));
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 1000 * 60 * 60 * 24,
},
}));
Rate Limiting
const rateLimit = require('express-rate-limit');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts',
});
app.post('/api/login', authLimiter, loginHandler);
app.post('/api/register', authLimiter, registerHandler);
app.post('/api/forgot-password', authLimiter, forgotPasswordHandler);
Quick Audit Commands
rg -n 'helmet\\(' . -g "*.js" -g "*.ts"
rg -n "x-powered-by" . -g "*.js" -g "*.ts"
rg "helmet" package.json
rg "require\\(['\"]helmet" .
rg "from ['\"]helmet" .
rg "cors\\(" . -g "*.js" -g "*.ts" -A 5
rg "app\\.(get|post|put|delete|patch)\\(" . -A 1 | grep -v "require.*[Aa]uth"
rg "(query|find|findOne|exec).*\\`" . -g "*.js" -g "*.ts"
rg "session\\(" . -A 10
Hardening Checklist