Skip to content

Handle Bad Content-Type Headers RESTfully#255

Open
thehabes wants to merge 38 commits intomainfrom
246-bad-content-type-headers
Open

Handle Bad Content-Type Headers RESTfully#255
thehabes wants to merge 38 commits intomainfrom
246-bad-content-type-headers

Conversation

@thehabes
Copy link
Member

@thehabes thehabes commented Mar 23, 2026

Closes #245
Closes #246
Closes #248
Closes #256

Summary

Requests with wrong or missing Content-Type headers were causing 500 errors and connection resets instead of proper HTTP error responses. This was because express.json() left req.body unparsed, and controllers crashed accessing undefined properties — dropping the TCP connection before any response was sent.

Changes

  • app.js — Restricted express.json() to only parse application/json and application/ld+json bodies. This also fixes a latent issue where application/ld+json bodies (core to a JSON-LD API) were never being parsed by the JSON middleware. Removed unnecessary express.urlencoded() middleware (this is a JSON API, not a form-processing app).
  • utils.js — Relocated createExpressError() here from controllers/utils.js so it is available to both controllers and REST middleware. Fixed the bug (createExpressError overwrites err.code switch values unconditionally #256) where the switch (err.code) block set statusCode/statusMessage correctly, but unconditional fallback lines immediately overwrote both values. Restructured to set defaults in the initializer, then override for err.code === 11000.
  • controllers/utils.js — Removed the old createExpressError() definition and its named export.
  • All controllers (bulk.js, crud.js, delete.js, gog.js, history.js, overwrite.js, patchSet.js, patchUnset.js, patchUpdate.js, putUpdate.js, release.js, search.js) — Migrated all createExpressError(err) calls to utils.createExpressError(err). Removed superfluous console.log().
  • rest.js — Added three per-route Content-Type validation middlewares applied before controllers:
    • verifyJsonContentType — requires application/json or application/ld+json. Applied to create, update, patch, set, unset, overwrite, query, bulkCreate, and bulkUpdate routes.
    • verifyTextContentType — requires text/plain. Not currently mounted on any route but available for future use.
    • verifyEitherContentType — requires application/json, application/ld+json, or text/plain. Applied to search routes.
    • All three return 415 Unsupported Media Type for missing, empty, multi-value, or unsupported Content-Type headers.
    • Routes that don't receive bodies (GET /id, DELETE /delete, PATCH /release) do not mount any Content-Type middleware.
    • Added 415 case to the messenger error handler.
    • Fixed pre-existing bug in messenger's 403 handler where err.message was used instead of error.message, causing the "Forbidden" text to be silently dropped from the response.
    • Truncated Bearer tokens in 401/403 error messages to token.slice(0, 15) to avoid exposing full credentials in responses.
  • Route files (bulkCreate.js, bulkUpdate.js, create.js, overwrite.js, patchSet.js, patchUnset.js, patchUpdate.js, putUpdate.js, query.js, search.js) — Mounted the appropriate Content-Type middleware (rest.verifyJsonContentType or rest.verifyEitherContentType) on each route handler.
  • routes/api-routes.js — Spacing fix only.
  • routes/static.js — Removed unnecessary express.urlencoded() middleware.
  • routes/__tests__/contentType.test.js — 15+ tests covering all three middlewares: valid types, rejected types, case insensitivity, charset parameters, PUT/PATCH methods, and Content-Type smuggling via comma injection.
  • routes/__tests__/history.test.js, since.test.js — Increased Jest timeout to 10s to fix intermittent test timeouts.
  • routes/__tests__/*.test.js — Removed express.urlencoded() from all test app setups to match production configuration.

… it that doesn't have to touch man files in the api/ directory.
@thehabes thehabes self-assigned this Mar 23, 2026
@thehabes thehabes requested a review from Copilot March 24, 2026 15:54

This comment was marked as outdated.

@thehabes thehabes marked this pull request as draft March 24, 2026 16:45

router.route('/')
.patch(auth.checkJwt, controller.patchUpdate)
.patch(auth.checkJwt, rest.jsonContent, controller.patchUpdate)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, to fix missing rate limiting for database-backed route handlers in Express, we should use a rate-limiting middleware (such as express-rate-limit) and apply it to the specific sensitive routes or to the entire router. This middleware should be placed before the handler that performs the database access so that excessive request rates are rejected or delayed before reaching the database.

For this specific file, the best minimally invasive fix is to: (1) import express-rate-limit, (2) define a limiter instance configured for update operations (e.g., a reasonable number of PATCH/POST update requests per IP in a given time window), and (3) add this limiter as middleware to the router.route('/') chain before auth.checkJwt / rest.jsonContent / controller.patchUpdate. This ensures both the .patch and .post handlers share the same rate limiting, covering all the alert variants without changing existing business logic or response semantics. Concretely, inside routes/patchUpdate.js, add a RateLimit import near the other imports, define a patchUpdateLimiter (e.g., 100 requests per 15 minutes per IP, or a stricter value as desired), and include patchUpdateLimiter as the first middleware in the .patch and .post routes. No changes to the controller or auth logic are needed.

Suggested changeset 2
routes/patchUpdate.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/patchUpdate.js b/routes/patchUpdate.js
--- a/routes/patchUpdate.js
+++ b/routes/patchUpdate.js
@@ -1,14 +1,20 @@
 #!/usr/bin/env node
 import express from 'express'
+import RateLimit from 'express-rate-limit'
 const router = express.Router()
 //This controller will handle all MongoDB interactions.
 import controller from '../db-controller.js'
 import rest from '../rest.js'
 import auth from '../auth/index.js'
 
+const patchUpdateLimiter = RateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 update requests per windowMs
+})
+
 router.route('/')
-    .patch(auth.checkJwt, rest.jsonContent, controller.patchUpdate) 
-    .post(auth.checkJwt, (req, res, next) => {
+    .patch(patchUpdateLimiter, auth.checkJwt, rest.jsonContent, controller.patchUpdate) 
+    .post(patchUpdateLimiter, auth.checkJwt, (req, res, next) => {
         if (rest.checkPatchOverrideSupport(req, res)) {
             controller.patchUpdate(req, res, next)
         }
EOF
@@ -1,14 +1,20 @@
#!/usr/bin/env node
import express from 'express'
import RateLimit from 'express-rate-limit'
const router = express.Router()
//This controller will handle all MongoDB interactions.
import controller from '../db-controller.js'
import rest from '../rest.js'
import auth from '../auth/index.js'

const patchUpdateLimiter = RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 update requests per windowMs
})

router.route('/')
.patch(auth.checkJwt, rest.jsonContent, controller.patchUpdate)
.post(auth.checkJwt, (req, res, next) => {
.patch(patchUpdateLimiter, auth.checkJwt, rest.jsonContent, controller.patchUpdate)
.post(patchUpdateLimiter, auth.checkJwt, (req, res, next) => {
if (rest.checkPatchOverrideSupport(req, res)) {
controller.patchUpdate(req, res, next)
}
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

router.route('/')
.post(auth.checkJwt, controller.bulkCreate)
.post(auth.checkJwt, rest.jsonContent, controller.bulkCreate)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, you should protect expensive HTTP handlers (especially those that perform bulk database operations) by applying a rate-limiting middleware. For an Express-based router, a common approach is to use express-rate-limit and attach a limiter to the specific route or to the router as a whole. This ensures that regardless of how heavy controller.bulkCreate is, any single client can only invoke it a limited number of times in a defined time window.

For this specific file (routes/bulkCreate.js), the minimal, non-breaking fix is:

  • Import express-rate-limit (CommonJS-style require is the idiomatic form for that library, but mixing import and require in the same file is legal in Node when using transpilation or appropriate settings, and we must not alter existing imports).
  • Create a limiter instance configured for this bulk-create endpoint (for example, allowing a modest number of bulk requests per 15 minutes).
  • Insert this limiter into the middleware chain of the POST route before controller.bulkCreate, so that requests exceeding the limit are rejected before hitting the database logic.
  • Do not alter the behavior of other routes or middlewares.

Concretely:

  • At the top of routes/bulkCreate.js, add const RateLimit = require('express-rate-limit') and define const bulkCreateLimiter = RateLimit({ ... }).
  • Update line 11 so that the .post(...) call becomes .post(auth.checkJwt, rest.jsonContent, bulkCreateLimiter, controller.bulkCreate).
Suggested changeset 2
routes/bulkCreate.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/bulkCreate.js b/routes/bulkCreate.js
--- a/routes/bulkCreate.js
+++ b/routes/bulkCreate.js
@@ -6,9 +6,15 @@
 import controller from '../db-controller.js'
 import auth from '../auth/index.js'
 import rest from '../rest.js'
+const RateLimit = require('express-rate-limit')
 
+const bulkCreateLimiter = RateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 bulk create requests per windowMs
+})
+
 router.route('/')
-    .post(auth.checkJwt, rest.jsonContent, controller.bulkCreate)
+    .post(auth.checkJwt, rest.jsonContent, bulkCreateLimiter, controller.bulkCreate)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for creating, please use POST.'
         res.status(405)
EOF
@@ -6,9 +6,15 @@
import controller from '../db-controller.js'
import auth from '../auth/index.js'
import rest from '../rest.js'
const RateLimit = require('express-rate-limit')

const bulkCreateLimiter = RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 bulk create requests per windowMs
})

router.route('/')
.post(auth.checkJwt, rest.jsonContent, controller.bulkCreate)
.post(auth.checkJwt, rest.jsonContent, bulkCreateLimiter, controller.bulkCreate)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for creating, please use POST.'
res.status(405)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

router.route('/')
.put(auth.checkJwt, controller.bulkUpdate)
.put(auth.checkJwt, rest.jsonContent, controller.bulkUpdate)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, to fix missing rate limiting you should introduce a rate-limiting middleware (e.g., using express-rate-limit) and apply it to routes that perform expensive operations such as database writes. The middleware should be placed before the heavy controller in the middleware chain so that excessive requests are rejected early.

For this specific route, the best minimal change is to add a rate limiter middleware function in routes/bulkUpdate.js and insert it into the .put(...) chain before controller.bulkUpdate. We will:

  • Import express-rate-limit at the top of this file.
  • Define a limiter instance (e.g., bulkUpdateLimiter) with a reasonable window and max setting, local to this router.
  • Update line 11 to include bulkUpdateLimiter between rest.jsonContent and controller.bulkUpdate.

This preserves existing authentication and JSON processing, while adding rate limiting immediately before the database-heavy handler.

Concretely:

  • At the top of routes/bulkUpdate.js, add import rateLimit from 'express-rate-limit'.
  • Define a const bulkUpdateLimiter = rateLimit({...}) just after the imports, with sensible defaults (e.g., 100 requests per 15 minutes from a single IP).
  • Change line 11 from .put(auth.checkJwt, rest.jsonContent, controller.bulkUpdate) to .put(auth.checkJwt, rest.jsonContent, bulkUpdateLimiter, controller.bulkUpdate).

No other behavior of the route changes, aside from rate limiting.


Suggested changeset 2
routes/bulkUpdate.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/bulkUpdate.js b/routes/bulkUpdate.js
--- a/routes/bulkUpdate.js
+++ b/routes/bulkUpdate.js
@@ -6,9 +6,15 @@
 import controller from '../db-controller.js'
 import auth from '../auth/index.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+const bulkUpdateLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 bulk update requests per window
+})
+
 router.route('/')
-    .put(auth.checkJwt, rest.jsonContent, controller.bulkUpdate)
+    .put(auth.checkJwt, rest.jsonContent, bulkUpdateLimiter, controller.bulkUpdate)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for creating, please use PUT.'
         res.status(405)
EOF
@@ -6,9 +6,15 @@
import controller from '../db-controller.js'
import auth from '../auth/index.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

const bulkUpdateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 bulk update requests per window
})

router.route('/')
.put(auth.checkJwt, rest.jsonContent, controller.bulkUpdate)
.put(auth.checkJwt, rest.jsonContent, bulkUpdateLimiter, controller.bulkUpdate)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for creating, please use PUT.'
res.status(405)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
routes/create.js Outdated

router.route('/')
.post(auth.checkJwt, controller.create)
.post(auth.checkJwt, rest.jsonContent, controller.create)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, the fix is to ensure that requests reaching the expensive controller.create database operation are subject to a rate limit. In an Express environment, this is typically done by adding a middleware like express-rate-limit that tracks requests per IP (or per user) over a time window and rejects excess requests.

In this file, the least invasive fix that preserves existing behavior is:

  • Import express-rate-limit.
  • Create a rate limiter instance configured for this specific route (e.g., allowing some number of create operations per IP per time window).
  • Insert this limiter into the middleware chain before controller.create on the POST handler.

Concretely:

  • At the top of routes/create.js, add an import rateLimit from 'express-rate-limit'.
  • After creating the router (after const router = express.Router()), define a createRateLimiter with appropriate settings (e.g., a certain windowMs, max, and a simple JSON or text response when the limit is exceeded).
  • Modify line 10 so that the middleware sequence is .post(auth.checkJwt, rest.jsonContent, createRateLimiter, controller.create).

No existing behavior for non-limited requests changes: authenticated, well-formed JSON POSTs will still reach controller.create, just with the added protection that very frequent requests will be rejected.

Suggested changeset 2
routes/create.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/create.js b/routes/create.js
--- a/routes/create.js
+++ b/routes/create.js
@@ -5,9 +5,17 @@
 import controller from '../db-controller.js'
 import auth from '../auth/index.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+const createRateLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 create requests per windowMs
+    standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
+    legacyHeaders: false, // Disable the `X-RateLimit-*` headers
+})
+
 router.route('/')
-    .post(auth.checkJwt, rest.jsonContent, controller.create)
+    .post(auth.checkJwt, rest.jsonContent, createRateLimiter, controller.create)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for creating, please use POST.'
         res.status(405)
EOF
@@ -5,9 +5,17 @@
import controller from '../db-controller.js'
import auth from '../auth/index.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

const createRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 create requests per windowMs
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
})

router.route('/')
.post(auth.checkJwt, rest.jsonContent, controller.create)
.post(auth.checkJwt, rest.jsonContent, createRateLimiter, controller.create)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for creating, please use POST.'
res.status(405)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

router.route('/')
.put(auth.checkJwt, controller.overwrite)
.put(auth.checkJwt, rest.jsonContent, controller.overwrite)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, fix this by adding an Express-compatible rate-limiting middleware in front of the expensive handler so that repeated requests from the same client are capped over a time window. The standard way is to use a well-known library such as express-rate-limit and apply a limiter either to this specific route or the router as a whole.

For this specific file (routes/overwrite.js), the minimal change that preserves existing behavior is:

  1. Import express-rate-limit.
  2. Define a limiter instance configured for this overwrite endpoint (for example, a modest limit like 60 requests per 15 minutes per IP, which you can later tune).
  3. Insert this limiter into the middleware chain for PUT /, between auth.checkJwt and rest.jsonContent (or at the start). This ensures:
    • Authentication still happens as before.
    • The handler controller.overwrite runs only if the client is within the allowed request budget.
    • No other routes are affected.

Concretely:

  • At the top of routes/overwrite.js, add import rateLimit from 'express-rate-limit'.
  • After creating the router (after line 3), define const overwriteLimiter = rateLimit({ ... }).
  • Update line 10 to:
    .put(auth.checkJwt, overwriteLimiter, rest.jsonContent, controller.overwrite)
Suggested changeset 2
routes/overwrite.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/overwrite.js b/routes/overwrite.js
--- a/routes/overwrite.js
+++ b/routes/overwrite.js
@@ -5,9 +5,17 @@
 import controller from '../db-controller.js'
 import auth from '../auth/index.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+const overwriteLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 overwrite requests per windowMs
+    standardHeaders: true,
+    legacyHeaders: false,
+})
+
 router.route('/')
-    .put(auth.checkJwt, rest.jsonContent, controller.overwrite)
+    .put(auth.checkJwt, overwriteLimiter, rest.jsonContent, controller.overwrite)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for overwriting, please use PUT to overwrite this object.'
         res.status(405)
EOF
@@ -5,9 +5,17 @@
import controller from '../db-controller.js'
import auth from '../auth/index.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

const overwriteLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 overwrite requests per windowMs
standardHeaders: true,
legacyHeaders: false,
})

router.route('/')
.put(auth.checkJwt, rest.jsonContent, controller.overwrite)
.put(auth.checkJwt, overwriteLimiter, rest.jsonContent, controller.overwrite)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for overwriting, please use PUT to overwrite this object.'
res.status(405)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

router.route('/')
.patch(auth.checkJwt, controller.patchSet)
.patch(auth.checkJwt, rest.jsonContent, controller.patchSet)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, the problem is fixed by applying a rate-limiting middleware to routes that perform expensive operations (like database access). In an Express app, a common solution is to use the express-rate-limit package to limit the number of requests per IP over a defined time window, and to attach that limiter middleware to the vulnerable route or router.

For this specific file (routes/patchSet.js), the best minimally invasive fix is:

  • Import express-rate-limit at the top of the file.
  • Define a limiter instance configured for a sensible rate (e.g., 100 requests per 15 minutes per IP; you can later tune this).
  • Apply this limiter to the router so that all HTTP methods on / that reach controller.patchSet are rate-limited.
    • Since all routes in this file are on the same router, adding router.use(limiter) after creating the router will ensure that .patch, .post, and .all on this router are all rate-limited, without changing existing behavior of the handlers themselves.

Concretely:

  • At the top of routes/patchSet.js, add import rateLimit from 'express-rate-limit'.
  • Immediately after const router = express.Router(), define const patchSetLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }) (or similar configuration).
  • Then add router.use(patchSetLimiter) so the limiter is applied to all routes within this router.

No changes are needed to controller.patchSet or to the existing route chaining; we’re only inserting middleware in front of them.

Suggested changeset 2
routes/patchSet.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/patchSet.js b/routes/patchSet.js
--- a/routes/patchSet.js
+++ b/routes/patchSet.js
@@ -4,7 +4,15 @@
 import controller from '../db-controller.js'
 import auth from '../auth/index.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+const patchSetLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 requests per windowMs
+})
+
+router.use(patchSetLimiter)
+
 router.route('/')
     .patch(auth.checkJwt, rest.jsonContent, controller.patchSet)
     .post(auth.checkJwt, (req, res, next) => {
EOF
@@ -4,7 +4,15 @@
import controller from '../db-controller.js'
import auth from '../auth/index.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

const patchSetLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
})

router.use(patchSetLimiter)

router.route('/')
.patch(auth.checkJwt, rest.jsonContent, controller.patchSet)
.post(auth.checkJwt, (req, res, next) => {
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated

router.route('/')
.put(auth.checkJwt, controller.putUpdate)
.put(auth.checkJwt, rest.jsonContent, controller.putUpdate)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, to fix missing rate limiting on an Express route that performs database access, you introduce a rate-limiting middleware (e.g., via express-rate-limit) and apply it either globally or specifically to the sensitive route(s). The middleware should be placed before the expensive handler in the middleware chain so that abusive traffic is rejected before reaching the database.

For this specific code, the least invasive and clearest fix is:

  • Import a known rate-limiting library, such as express-rate-limit, at the top of routes/putUpdate.js without altering existing imports.
  • Define a limiter configured for this update route only (e.g., a reasonable per-IP limit over a time window, such as 100 requests per 15 minutes). This preserves existing functionality while adding protection.
  • Insert the limiter into the route definition before controller.putUpdate, so the middleware order becomes: auth.checkJwt, rest.jsonContent, limiter, controller.putUpdate.
  • Leave the .all(...) handler and other existing behavior unchanged.

Concretely:

  • In routes/putUpdate.js, add import rateLimit from 'express-rate-limit' near the top.

  • Add something like:

    const updateLimiter = rateLimit({
        windowMs: 15 * 60 * 1000,
        max: 100,
    })

    after the imports and before using router.

  • Update line 10 to:

    .put(auth.checkJwt, rest.jsonContent, updateLimiter, controller.putUpdate)

No other files need to be changed to address the alert variants, since all variants refer to the same route handler.

Suggested changeset 2
routes/putUpdate.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/putUpdate.js b/routes/putUpdate.js
--- a/routes/putUpdate.js
+++ b/routes/putUpdate.js
@@ -5,9 +5,15 @@
 import controller from '../db-controller.js'
 import auth from '../auth/index.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+const updateLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 update requests per windowMs
+})
+
 router.route('/')
-    .put(auth.checkJwt, rest.jsonContent, controller.putUpdate)
+    .put(auth.checkJwt, rest.jsonContent, updateLimiter, controller.putUpdate)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for updating, please use PUT to update this object.'
         res.status(405)
EOF
@@ -5,9 +5,15 @@
import controller from '../db-controller.js'
import auth from '../auth/index.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

const updateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 update requests per windowMs
})

router.route('/')
.put(auth.checkJwt, rest.jsonContent, controller.putUpdate)
.put(auth.checkJwt, rest.jsonContent, updateLimiter, controller.putUpdate)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for updating, please use PUT to update this object.'
res.status(405)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
routes/query.js Outdated

router.route('/')
.post(controller.query)
.post(rest.jsonContent, controller.query)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, the issue is fixed by ensuring that any route that triggers database access is protected by a rate-limiting middleware. In Express, the standard approach is to use a library such as express-rate-limit and apply a limiter either globally (via app.use) or to specific routes (by inserting the limiter in the route’s middleware chain). This prevents a client from issuing unlimited requests in a given time window.

For this specific file, the best fix with minimal behavioral change is to import express-rate-limit, define a rate limiter instance appropriate for query operations, and insert that limiter into the POST / route before rest.jsonContent and controller.query. This keeps the existing controller and JSON parsing logic untouched and only adds a protective wrapper. Concretely, in routes/query.js, we will:

  • Add import rateLimit from 'express-rate-limit' alongside the other imports.
  • Define a const queryLimiter = rateLimit({ ... }) with conservative defaults (e.g., 100 requests per 15 minutes per IP) near the top of the file.
  • Update the route definition on lines 7–8 so that .post(rest.jsonContent, controller.query) becomes .post(queryLimiter, rest.jsonContent, controller.query).

No additional helper methods are required beyond the limiter instance, and no existing imports need to be modified.

Suggested changeset 2
routes/query.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/query.js b/routes/query.js
--- a/routes/query.js
+++ b/routes/query.js
@@ -3,9 +3,15 @@
 //This controller will handle all MongoDB interactions.
 import controller from '../db-controller.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+const queryLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 requests per windowMs for this route
+})
+
 router.route('/')
-    .post(rest.jsonContent, controller.query)
+    .post(queryLimiter, rest.jsonContent, controller.query)
     .head(controller.queryHeadRequest)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for requesting objects with matching properties.  Please use POST.'
EOF
@@ -3,9 +3,15 @@
//This controller will handle all MongoDB interactions.
import controller from '../db-controller.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

const queryLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs for this route
})

router.route('/')
.post(rest.jsonContent, controller.query)
.post(queryLimiter, rest.jsonContent, controller.query)
.head(controller.queryHeadRequest)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for requesting objects with matching properties. Please use POST.'
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
routes/search.js Outdated

router.route('/')
.post(controller.searchAsWords)
.post(rest.eitherContent, controller.searchAsWords)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

In general, to fix missing rate limiting on Express routes that hit the database, we should introduce a rate-limiting middleware and apply it to the relevant routes before the expensive handlers. The common approach is to use a well-known library such as express-rate-limit, configure appropriate thresholds (e.g., N requests per window), and attach the limiter either to all routes in the router or only to specific sensitive endpoints.

For this file, the least invasive and clearest fix is:

  • Import express-rate-limit.
  • Create one or more limiter instances configured for search endpoints.
  • Apply the limiter as middleware on the routes that call controller.searchAsWords and controller.searchAsPhrase, placing it before rest.eitherContent so it executes first and can reject excessive traffic before any body parsing or DB access.
  • Keep existing behavior and messages intact for valid and invalid methods.

Concretely, within routes/search.js:

  1. Add import rateLimit from 'express-rate-limit' (ES module form to match existing imports).

  2. Define a searchLimiter constant, e.g.:

    const searchLimiter = rateLimit({
        windowMs: 15 * 60 * 1000,
        max: 100,
        standardHeaders: true,
        legacyHeaders: false,
    })

    This uses standard, sensible defaults while not altering any existing functionality of the search handlers themselves.

  3. Attach searchLimiter to both router.route('/') and router.route('/phrase'), before rest.eitherContent:

    router.route('/')
        .post(searchLimiter, rest.eitherContent, controller.searchAsWords)
        ...

    and similarly for /phrase.

No other files need to be changed, and the core functional behavior of the handlers remains the same except they will now be rate-limited.

Suggested changeset 2
routes/search.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/search.js b/routes/search.js
--- a/routes/search.js
+++ b/routes/search.js
@@ -2,9 +2,17 @@
 const router = express.Router()
 import controller from '../db-controller.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+const searchLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 search requests per window
+    standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
+    legacyHeaders: false, // Disable the `X-RateLimit-*` headers
+})
+
 router.route('/')
-    .post(rest.eitherContent, controller.searchAsWords)
+    .post(searchLimiter, rest.eitherContent, controller.searchAsWords)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for search.  Please use POST.'
         res.status(405)
@@ -12,7 +18,7 @@
     })
 
 router.route('/phrase')
-    .post(rest.eitherContent, controller.searchAsPhrase)
+    .post(searchLimiter, rest.eitherContent, controller.searchAsPhrase)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for search.  Please use POST.'
         res.status(405)
EOF
@@ -2,9 +2,17 @@
const router = express.Router()
import controller from '../db-controller.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

const searchLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 search requests per window
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
})

router.route('/')
.post(rest.eitherContent, controller.searchAsWords)
.post(searchLimiter, rest.eitherContent, controller.searchAsWords)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for search. Please use POST.'
res.status(405)
@@ -12,7 +18,7 @@
})

router.route('/phrase')
.post(rest.eitherContent, controller.searchAsPhrase)
.post(searchLimiter, rest.eitherContent, controller.searchAsPhrase)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for search. Please use POST.'
res.status(405)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
routes/search.js Outdated

router.route('/phrase')
.post(controller.searchAsPhrase)
.post(rest.eitherContent, controller.searchAsPhrase)

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a database access
, but is not rate-limited.
This route handler performs
a database access
, but is not rate-limited.

Copilot Autofix

AI about 3 hours ago

To fix this, add an Express rate-limiting middleware and apply it to the search routes so that requests hitting controller.searchAsPhrase (and ideally all search routes) are throttled. We should use a well-known package such as express-rate-limit rather than building custom logic. To avoid changing existing functionality, the limiter should sit before the existing middlewares (rest.eitherContent, controller methods) and simply block or delay excessive requests; normal traffic should behave exactly as before.

Concretely in routes/search.js:

  1. Add an import for express-rate-limit near the other imports.
  2. Define a limiter instance configured for a reasonable window and maximum, e.g. 100 requests per 15 minutes per IP (matching the example in the background).
  3. Apply this limiter to the relevant routes. To cover both word and phrase search (and address both alert variants), apply it to the base '/' route and the /phrase route, by inserting it as a middleware argument before rest.eitherContent.

No existing imports need changing; we just add a new one. No other files are modified.

Suggested changeset 2
routes/search.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/routes/search.js b/routes/search.js
--- a/routes/search.js
+++ b/routes/search.js
@@ -2,9 +2,16 @@
 const router = express.Router()
 import controller from '../db-controller.js'
 import rest from '../rest.js'
+import rateLimit from 'express-rate-limit'
 
+// Rate limiter for search endpoints to mitigate denial-of-service attacks
+const searchLimiter = rateLimit({
+    windowMs: 15 * 60 * 1000, // 15 minutes
+    max: 100, // limit each IP to 100 search requests per windowMs
+})
+
 router.route('/')
-    .post(rest.eitherContent, controller.searchAsWords)
+    .post(searchLimiter, rest.eitherContent, controller.searchAsWords)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for search.  Please use POST.'
         res.status(405)
@@ -12,7 +17,7 @@
     })
 
 router.route('/phrase')
-    .post(rest.eitherContent, controller.searchAsPhrase)
+    .post(searchLimiter, rest.eitherContent, controller.searchAsPhrase)
     .all((req, res, next) => {
         res.statusMessage = 'Improper request method for search.  Please use POST.'
         res.status(405)
EOF
@@ -2,9 +2,16 @@
const router = express.Router()
import controller from '../db-controller.js'
import rest from '../rest.js'
import rateLimit from 'express-rate-limit'

// Rate limiter for search endpoints to mitigate denial-of-service attacks
const searchLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 search requests per windowMs
})

router.route('/')
.post(rest.eitherContent, controller.searchAsWords)
.post(searchLimiter, rest.eitherContent, controller.searchAsWords)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for search. Please use POST.'
res.status(405)
@@ -12,7 +17,7 @@
})

router.route('/phrase')
.post(rest.eitherContent, controller.searchAsPhrase)
.post(searchLimiter, rest.eitherContent, controller.searchAsPhrase)
.all((req, res, next) => {
res.statusMessage = 'Improper request method for search. Please use POST.'
res.status(405)
package.json
Outside changed files

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -37,7 +37,8 @@
     "express-oauth2-jwt-bearer": "^1.7.4",
     "express-urlrewrite": "~2.0.3",
     "mongodb": "^7.1.0",
-    "morgan": "~1.10.1"
+    "morgan": "~1.10.1",
+    "express-rate-limit": "^8.3.1"
   },
   "devDependencies": {
     "@jest/globals": "^30.3.0",
EOF
@@ -37,7 +37,8 @@
"express-oauth2-jwt-bearer": "^1.7.4",
"express-urlrewrite": "~2.0.3",
"mongodb": "^7.1.0",
"morgan": "~1.10.1"
"morgan": "~1.10.1",
"express-rate-limit": "^8.3.1"
},
"devDependencies": {
"@jest/globals": "^30.3.0",
This fix introduces these dependencies
Package Version Security advisories
express-rate-limit (npm) 8.3.1 None
Copilot is powered by AI and may make mistakes. Always verify output.
Unable to commit as this autofix suggestion is now outdated
@thehabes thehabes marked this pull request as ready for review March 24, 2026 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants