Case-sensitive middleware paths

Improper handling of case sensitivity

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 i flag too, for example /^\/admin(?:\/|$)/i or new 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 /AdMiN for 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 i matches regardless of capitalization. Both prevent this mismatch from bypassing security middleware.

References