Skip to content

🚨 [security] Update @nestjs/microservices 11.1.19 → 11.2.7 (minor) - #85

Open
depfu[bot] wants to merge 1 commit into
mainfrom
depfu/update/pnpm/@nestjs/microservices-11.2.7
Open

depfu[bot] wants to merge 1 commit into
mainfrom
depfu/update/pnpm/@nestjs/microservices-11.2.7

Conversation

@depfu

@depfu depfu Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Welcome to Depfu 👋

This is one of the first three pull requests with dependency updates we've sent your way. We tried to start with a few easy patch-level updates. Hopefully your tests will pass and you can merge this pull request without too much risk. This should give you an idea how Depfu works in general.

After you merge your first pull request, we'll send you a few more. We'll never open more than seven PRs at the same time so you're not getting overwhelmed with updates.

Let us know if you have any questions. Thanks so much for giving Depfu a try!



🚨 Your current dependencies have known security vulnerabilities 🚨

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this update. Please take a good look at what changed and the test results before merging this pull request.

What changed?

✳️ @​nestjs/microservices (11.1.19 → 11.2.7) · Repo · Changelog

Security Advisories 🚨

🚨 Nest: Unbounded memory growth in the NestJS TCP microservice transport

Summary

A peer that can open a TCP connection to a NestJS microservice using the built-in TCP
transport can make the server process allocate memory without limit, on either side of the
connection, until the process is killed by the OS or by its container memory limit. No
authentication, no credentials, and no valid message are required.

Applications are affected only if they start a microservice with Transport.TCP and the
transport's port is reachable by an untrusted peer.

Affected versions

Package Affected Patched
@nestjs/microservices >= 12.0.0, < 12.0.3 12.0.3
@nestjs/microservices < 11.2.5 11.2.5

The same code is present in the 10.x line and earlier. Those lines are end-of-life and
will not receive a patch; upgrade to a supported major.

Only @nestjs/microservices is affected. No other package needs updating for this issue.

Details

Two independent paths let one peer grow the heap without bound.

Partial packets are buffered with nothing to reap them

The TCP transport frames messages as <length>#<payload>. A peer may declare a length,
send part of the payload, and then stop. JsonSocket keeps the partial payload buffered
while it waits for the rest, and nothing ever reclaimed it: there was no socket timeout, no
cap on the number of connections, and no tracking of accepted sockets.

maxBufferSize did not close this. It caps a single connection, defaulting to 128M
characters, and is enforced per connection, so the effective ceiling was the number of
connections an attacker chose to open.

Ten connections that each send 20MB and then go silent:

rss heapUsed
baseline 152.3MB 28.1MB
after 200MB sent, all stalled 561.2MB 229.5MB

The memory was held for as long as the connections stayed open. Closing them released it,
so a peer could hold it indefinitely at negligible cost to itself.

Response backpressure was ignored

JsonSocket#handleSend discarded the return value of socket.write. A peer that issues
requests and never reads the responses therefore made the process queue every response in
memory, with no ceiling and no signal to stop producing more.

Thirty requests from a client whose stream is paused, against a handler returning an 8MB
payload, grew rss from 152.4MB to 419.1MB. The amplification here is roughly 1:1, because
the response size is set by the application rather than by the attacker; the defect is that
the server buffers all of it rather than applying backpressure or giving up.

Impact

An unauthenticated peer that can reach the transport's port can drive the process to an
out-of-memory kill. Under a container memory limit this is fast and repeatable, and it
restarts the pod rather than merely degrading it.

There is no confidentiality or integrity impact. Nothing is read, written, or executed;
the failure mode is availability only.

Applications are not affected if the TCP transport is not used, or if the transport's port
is reachable only by trusted peers.

Patches

Upgrade @nestjs/microservices to 12.0.3 or 11.2.5.

Three changes landed together:

  • A stall timer drops a peer that stops sending mid-packet. It is armed only while a
    packet is partially received and is refreshed on every read, so a slow but progressing
    transfer is never interrupted and a peer sitting idle between packets is left alone.
    Configurable as incompleteMessageTimeout, default 30000ms.
  • Reading is suspended while a peer's outgoing buffer is backed up and resumes on
    drain, so a peer that stops reading cannot keep issuing requests.
  • Responses queued for a peer that reads nothing are capped. Suspending reads is not
    sufficient on its own, because message handlers are asynchronous: a burst of pipelined
    requests is dispatched before the first response is written, and those responses still
    queue. Configurable as maxSendBufferSize, default 128MB.

Both limits are configurable on the server and on the client, and either can be disabled by
setting it to 0.

Behaviour change

Both limits are enabled by default, following the precedent set by maxBufferSize. A peer
that stops sending mid-packet for 30 seconds, or that lets more than 128MB of responses
queue unread on a single connection, is now disconnected where previously it was not.

The send cap can also drop a peer that is genuinely reading, if responses are produced
faster than the socket drains for long enough. The default is set high for that reason.
Applications that stream very large responses to deliberately slow consumers should raise
maxSendBufferSize or set it to 0.

Workarounds

For anyone who cannot upgrade immediately:

  • Restrict who can reach the port. The TCP transport is designed for trusted
    service-to-service traffic. A network policy, security group, or L4 firewall that limits
    the port to known peers removes the exposure entirely, and is worth doing regardless of
    version.
  • Lower maxBufferSize. This reduces what a single connection can pin on the receive
    side. It does not bound the total, because the limit is per connection and the number of
    connections is not capped, and it does nothing for the response-queueing path.
  • Supply a custom socketClass. An application-provided socket class can implement its
    own idle timeout and backpressure handling. This is the only pre-patch mitigation that
    addresses the sending side.

Setting a process memory limit does not mitigate this. It converts the failure from an
out-of-memory kill of the host into an out-of-memory kill of the process.

Credit

Reported by Salman Aljardan, who provided a detailed write-up and self-contained proofs of
concept for each issue, and coordinated disclosure.

🚨 Nest: Remote process termination via a deeply nested microservice message pattern

Field Value
Ecosystem npm
Package @nestjs/microservices
Affected versions >= 12.0.0, < 12.0.2 and < 11.2.4
Patched versions 12.0.2 and 11.2.4 (upgrade to 12.0.3 / 11.2.5)

Summary

A single message whose pattern is a deeply nested object terminates a NestJS microservice that uses the TCP or
RabbitMQ transport. The server serialized the client-supplied pattern with JSON.stringify to derive the handler
lookup key; on deeply nested input this throws RangeError: Maximum call stack size exceeded. The exception escaped
the asynchronous message handler as an unhandled promise rejection, which terminates the Node.js process under the
default --unhandled-rejections=throw.

Impact

Denial of service, one message per crash, repeatable. The attacker needs to be able to reach the transport: connect
to the TCP transport's port, or publish to the queue or exchange the service consumes from. The TCP transport
performs no authentication by default, so on a reachable port this requires nothing else.

Only the TCP and RabbitMQ transports are affected. The other transports take the pattern as a string from the
broker topic or channel and never serialize a client-supplied object to build it.

Details

In ServerTCP#handleMessage and ServerRMQ#handleMessage the pattern was stringified without a guard:

const pattern = isString(packet.pattern)
  ? packet.pattern
  : JSON.stringify(packet.pattern);

JSON.parse accepts nesting depths that JSON.stringify cannot re-serialize, because JSON.stringify recurses
natively, so an attacker can craft a payload that parses successfully on arrival and then throws when the pattern is
converted back to a string. Neither transport attached a rejection handler to the promise returned by
handleMessage, so the RangeError propagated out as an unhandled rejection.

Proof of concept

Against a NestJS microservice on the TCP transport (default port 3001). The nested JSON is built as text rather than
with JSON.stringify, which is what makes the payload serializable by the attacker but not by the victim:

const { connect } = require('node:net');

const DEPTH = 100_000;
const pattern = '{"nested":'.repeat(DEPTH) + '{}' + '}'.repeat(DEPTH);
const payload = {"pattern":<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">pattern</span><span class="pl-kos">}</span></span>,"data":null,"id":"1"};

const socket = connect(3001, '127.0.0.1', () => {
// Nest's TCP framing is <byteLength>#<json>
socket.write(<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-v">Buffer</span><span class="pl-kos">.</span><span class="pl-en">byteLength</span><span class="pl-kos">(</span><span class="pl-s1">payload</span><span class="pl-kos">)</span><span class="pl-kos">}</span></span>#<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">payload</span><span class="pl-kos">}</span></span>);
});

The service exits with RangeError: Maximum call stack size exceeded. The equivalent payload published to the
consumed queue crashes a RabbitMQ-transport service.

Patches

Fixed in 12.0.2 and 11.2.4.

  • Incoming patterns are converted through a guarded Server#getPatternAsString, which falls back to a sentinel value
    that matches no handler. Such a message now receives the ordinary "no message handler" response (TCP) or is
    negatively acknowledged (RabbitMQ) instead of crashing the process.
  • Rejections escaping handleMessage in both transports are routed to handleError rather than left unhandled.

Workarounds

If you cannot upgrade, restrict network access to the transport so that only trusted peers can reach it. Running the
process with --unhandled-rejections=warn prevents the crash but leaves the message unprocessed and is not a
substitute for the fix.

Credit

Reported by ZeroVuln Labs.

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.


Depfu Status

Depfu will automatically keep this PR conflict-free, as long as you don't add any commits to this branch yourself. You can also trigger a rebase manually by commenting with @depfu rebase.

All Depfu comment commands
@​depfu rebase
Rebases against your default branch and redoes this update
@​depfu recreate
Recreates this PR, overwriting any edits that you've made to it
@​depfu merge
Merges this PR once your tests are passing and conflicts are resolved
@​depfu cancel merge
Cancels automatic merging of this PR
@​depfu close
Closes this PR and deletes the branch
@​depfu reopen
Restores the branch and reopens this PR (if it's closed)
@​depfu pause
Ignores all future updates for this dependency and closes this PR
@​depfu pause [minor|major]
Ignores all future minor/major updates for this dependency and closes this PR
@​depfu resume
Future versions of this dependency will create PRs again (leaves this PR as is)

@depfu depfu Bot added the depfu label Sep 30, 2026
@depfu
depfu Bot requested a review from microwavekonijn September 30, 2026 16:16
@changeset-bot

changeset-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 8ee37a7

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants