Description
Express middleware paths defined as RegExp are case-sensitive by default. If security middleware matches case-sensitively but the actual route does not, changing the endpoint's capitalization can bypass the middleware. For example, protection matching /admin may miss /Admin or /AdMiN.
Potential impact
- Authentication or authorization bypass for admin pages or protected APIs
- Missing logging or audit events when middleware does not match
- Inconsistent rate limits, CSRF protection, or security headers such as CSP and HSTS
- Access-control mistakes when apparently equivalent paths receive different protection
Remediation
- If the actual route is case-insensitive, give the security middleware's regular expression the
iflag too, for example/^\/admin(?:\/|$)/iornew RegExp('^/admin(?:/|$)', 'i'). - Prefer string paths where possible to match Express's default case-insensitive routing, such as
app.use('/admin', authMiddleware). - Check framework settings. Case-sensitive routing is disabled by default; document and test any differences when mixing regular expression and string paths.
- Where practical, apply security middleware globally and allow only specific exceptions.
- Test capitalization variants such as
/admin,/Admin, and/AdMiNfor bypasses.
Examples
Before
javascript
const express = require("express");
const app = express();
function requireAdmin(req, res, next) {
if (!req.user || !req.user.isAdmin) return res.sendStatus(403);
next();
}
// Before: no 'i' flag, so matching is case-sensitive.
app.use(/^\/admin\/api\/.*$/, requireAdmin);
app.get("/admin/api/users", (req, res) => {
res.send("users");
});
// GET /Admin/api/users bypasses requireAdmin.
After
javascript
const express = require("express");
const app = express();
function requireAdmin(req, res, next) {
if (!req.user || !req.user.isAdmin) return res.sendStatus(403);
next();
}
// Option 1: string paths are case-insensitive by default.
app.use("/admin", requireAdmin);
// Option 2: add 'i' when a regular expression is necessary.
// app.use(/^\/admin(?:\/|$)/i, requireAdmin);
app.get("/admin/api/users", (req, res) => {
res.send("users");
});
Explanation:
- Before: Without
i, the expression protects/admin/...but misses/Admin/.... The middleware is case-sensitive while the string route uses Express's case-insensitive default. - After: A string path uses the same default matching behavior, or an expression with
imatches regardless of capitalization. Both prevent this mismatch from bypassing security middleware.