expressjs / expressjs/express

router.use() silently accepts express() sub-apps without prototype restoration

Open
#7,427 1 comment 0 reactions 0 assignees View on GitHub
question require-triage
Dominant language
JavaScript
Stars
69.5k
Forks
25k
Avg merge
4d 20h
Merged PRs (30d)
9

Description

When an express() sub-app is mounted via router.use() instead of app.use(), the request and response prototypes are swapped by app.handle but never restored. The sub-app also never fires the mount event, so it does not inherit the parent's trust proxy setting. Any middleware that runs after the sub-app calls next() reads req.ip, req.secure, and req.hostname under the wrong app's trust policy, silently and without any error.

app.handle (lib/application.js:169-170) performs:

js
Object.setPrototypeOf(req, this.request)
Object.setPrototypeOf(res, this.response)

The only code that reverses this swap is the mounted_app closure built by app.use at lib/application.js:230-237. router.use never builds that closure, so the swap is permanent for the lifetime of the request.

The router already saves and restores request state across boundaries at index.js:171:

js
let done = restore(callback, req, 'baseUrl', 'next', 'params')

The prototype is simply not on the list.

Add __proto__ (or Object.getPrototypeOf equivalents) to the restore call so the router's own cleanup undoes the prototype swap on every exit path — next(), next(err), and unhandled throws alike.

js
// index.js — inside Router.prototype.handle, before the loop
var reqProto = Object.getPrototypeOf(req)
var resProto = Object.getPrototypeOf(res)

var done = restore(callback, req, 'baseUrl', 'next', 'params')
var _done = done
done = function(err) {
Object.setPrototypeOf(req, reqProto)
Object.setPrototypeOf(res, resProto)
_done(err)
}

Contributor guide

Open the contributing guide

Research direction

Read lib/application.js:169-170 and 230-237, then trace Router.prototype.handle in index.js around line 171 and its restore callback. Verify that request and response prototypes are restored after next(), next(err), and unhandled throws, and that later middleware reads req.ip, req.secure, and req.hostname under the original app’s trust policy.

Written by the indexing model from the issue text.

Assessment

Tech stack
express, javascript, node.js
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.