Skip to content

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

Description

@synapes620

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)
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions