Skip to content

fix: absolute-form middleware bypass (fastify) and unserializable pattern crash (microservices) - #17737

Merged
kamilmysliwiec merged 2 commits into
masterfrom
fix/middie-and-pattern-hardening
Sep 14, 2026
Merged

kamilmysliwiec merged 2 commits into
masterfrom
fix/middie-and-pattern-hardening

Conversation

@kamilmysliwiec

Copy link
Copy Markdown
Member

PR Checklist

PR Type

  • Bugfix

What is the current behavior?

@nestjs/platform-fastify: a request with an absolute-form request target (GET http://host/path HTTP/1.1) is routed to the handler by the Fastify router, but path-scoped middleware never runs, because the middleware engine matches against the raw request target. The inlined @fastify/middie fork in this repo was affected by the same issue as upstream (GHSA-hx87-8wv7-pjv8), which upstream fixed in 9.3.4.

@nestjs/microservices: the TCP and RMQ servers stringify client-controlled message patterns with JSON.stringify, which throws a RangeError for deeply nested objects. The resulting unhandled promise rejection terminates the process.

What is the new behavior?

@nestjs/platform-fastify

  • The inlined middie fork is removed in favour of the upstream @fastify/middie@9.3.4 package, which resolves absolute-form targets before matching and already contains the decoded-path fix the fork was carrying.
  • The adapter's own route check for Nest middleware resolves absolute-form request targets to their path before applying the router normalization options (mirroring find-my-way).
  • The deprecated top-level Fastify router options (ignoreTrailingSlash, ignoreDuplicateSlashes, caseSensitive, ...) are folded into routerOptions. The adapter always passes routerOptions (for the version constraint), and in that case Fastify only reflects the top-level values in the router itself, not in initialConfig.routerOptions, which @fastify/middie relies on.
  • fastify-plugin and reusify are no longer direct dependencies of the package (they were only used by the fork).

@nestjs/microservices

  • Incoming patterns are stringified through a guarded Server#getPatternAsString, falling back to a sentinel that matches no handler, so such requests get the regular NO_MESSAGE_HANDLER response (or nack) instead of crashing the process.
  • Rejections escaping handleMessage in the TCP and RMQ servers are routed to handleError instead of being left as unhandled rejections.

Regression tests were added for both (raw-socket e2e test for the absolute-form target, unit tests for the deeply nested patterns and the rejection handling).

Does this PR introduce a breaking change?

  • No

🤖 Generated with Claude Code

kamilmysliwiec and others added 2 commits September 14, 2026 15:38
Requests using an absolute-form request target ("GET http://host/path")
were routed to the handler by Fastify while path-scoped middleware, which
matched against the raw request target, never ran.

- replace the inlined @fastify/middie fork with the upstream package
  (9.3.4), which resolves absolute-form targets before matching and
  already contains the decoded-path fix the fork was carrying
- resolve absolute-form targets in the adapter's own route check
- fold the deprecated top-level Fastify router options into
  "routerOptions" so that @fastify/middie normalizes request paths the
  same way the router does

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"JSON.stringify" throws a RangeError for deeply nested message patterns.
The TCP and RMQ servers stringified client-controlled patterns without
guarding against it, and the resulting unhandled promise rejection
terminated the process.

Patterns are now stringified safely (falling back to a sentinel that
matches no handler) and rejected message handling is routed to
"handleError" instead of being left unhandled.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@kamilmysliwiec
kamilmysliwiec merged commit e93c8e1 into master Sep 14, 2026
5 checks passed
kamilmysliwiec pushed a commit that referenced this pull request Sep 17, 2026
…17785)

follow-up to #17737, which routed the tcp and rmq handleMessage rejections
to handleError bcs an unhandled promise rejection terminates the process

the mqtt, nats and redis servers hand the same async handleMessage to their
client libraries with no catch, so the identical rejections still escape:
mqtt.js and node-redis get the handler through an EventEmitter listener and
nats.js calls the subscription callback without awaiting it, so a malformed
packet that throws in the deserializer, or a handler that rejects, kills the
whole process

the three handlers now route rejections through handleError like tcp and rmq
already do, so a bad packet logs an error instead of crashing the server
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.

1 participant