Skip to content

fix: restore req/res prototypes when a mounted app hands control back - #7428

Open
lazerg wants to merge 3 commits into
expressjs:masterfrom
lazerg:fix/issue-7427-mounted-app-prototype
Open

fix: restore req/res prototypes when a mounted app hands control back#7428
lazerg wants to merge 3 commits into
expressjs:masterfrom
lazerg:fix/issue-7427-mounted-app-prototype

Conversation

@lazerg

@lazerg lazerg commented Aug 25, 2026

Copy link
Copy Markdown

app.handle swaps the req/res prototypes for its own, but only app.use() puts them back, inside the mounted_app closure it wraps around a sub-app. Mount an app with router.use() instead and the swap is never undone, so every middleware that runs after the sub-app calls next() reads req.app, req.ip, req.secure and req.hostname through the sub-app's settings rather than the parent's.

app.handle now saves the prototypes it replaces and restores them before invoking the callback it was handed, so the cleanup happens on whichever path the app was mounted through. The issue suggested patching restore() inside router, but the swap originates here, and doing it here also covers any other caller that passes a callback without needing a router release.

This covers the prototype half of #7427 only. A router-mounted app still gets no mount event and so does not inherit trust proxy, which would need router to know about express apps.

Fixes #7427

@santusht06

Copy link
Copy Markdown

Great work on identifying the core prototype swap in app.handle!

The approach of capturing origReqProto / origResProto when callback is provided and restoring them inside done is definitely the clean and canonical way to address #7427 across all mounting paths.

To make the fix completely airtight for maintainers and prevent future regressions, there are a few important edge cases that should also be covered in the test suite:

  1. Error Handling Pipeline (next(err)): When a sub-app encounters an error and triggers next(new Error(...)), verifying that the parent application's 4-argument error middleware (app.use((err, req, res, next) => ...)) also receives the restored prototype and req.app === parentApp.
  2. Custom Prototype Extensions: Verifying that custom helper methods attached to the parent application's prototype (e.g. app.request.customHelper()) remain callable and intact on req and res after delegating back.
  3. Context Restoration: Explicitly asserting req.app === parentApp in addition to Object.getPrototypeOf(req) === app.request.

I've put together a PR in #7434 with these comprehensive test cases if you'd like to incorporate them or collaborate to help get this merged quickly!

@lazerg

lazerg commented Aug 27, 2026

Copy link
Copy Markdown
Author

Added a test for the next(err) path (e418c6f): the parent's error middleware also gets the restored prototypes. The other two cases don't need separate assertions — req.app is defined on the same request object the prototype check already compares, and any custom methods on app.request/app.response come along with that same identity check.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants