CVE-2026-33151
Description
Socket.IO is an open source, real-time, bidirectional, event-based, communication framework. Prior to versions 3.3.5, 3.4.4, and 4.2.6, a specially crafted Socket.IO packet can make the server wait for a large number of binary attachments and buffer them, which can be exploited to make the server run out of memory. This issue has been patched in versions 3.3.5, 3.4.4, and 4.2.6.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
socket.io-parsernpm | < 3.3.5 | 3.3.5 |
socket.io-parsernpm | >= 3.4.0, < 3.4.4 | 3.4.4 |
socket.io-parsernpm | >= 4.0.0, < 4.2.6 | 4.2.6 |
Affected products
1Patches
3719f9ebab077fix(parser): add a limit to the number of binary attachments
2 files changed · +46 −4
index.js+25 −4 modified@@ -218,8 +218,12 @@ function encodeAsBinary(obj, callback) { * @api public */ -function Decoder() { +function Decoder(opts) { this.reconstructor = null; + opts = opts || {}; + this.opts = { + maxAttachments: opts.maxAttachments || 10, + }; } /** @@ -242,7 +246,7 @@ Decoder.prototype.add = function(obj) { if (this.reconstructor) { throw new Error("got plaintext data when reconstructing a packet"); } - packet = decodeString(obj); + packet = decodeString(obj, this.opts.maxAttachments); if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json this.reconstructor = new BinaryReconstructor(packet); @@ -272,11 +276,12 @@ Decoder.prototype.add = function(obj) { * Decode a packet String (JSON data) * * @param {String} str + * @param {Number} maxAttachments - the maximum number of binary attachments * @return {Object} packet * @api private */ -function decodeString(str) { +function decodeString(str, maxAttachments) { var i = 0; // look up type var p = { @@ -295,7 +300,13 @@ function decodeString(str) { if (buf != Number(buf) || str.charAt(i) !== '-') { throw new Error('Illegal attachments'); } - p.attachments = Number(buf); + var n = Number(buf); + if (!isInteger(n) || n < 0) { + throw new Error("Illegal attachments"); + } else if (n > maxAttachments) { + throw new Error("too many attachments"); + } + p.attachments = n; } // look up namespace (if any) @@ -432,3 +443,13 @@ function error(msg) { data: 'parser error: ' + msg }; } + +var isInteger = + Number.isInteger || + function (value) { + return ( + typeof value === "number" && + isFinite(value) && + Math.floor(value) === value + ); + };
test/parser.js+21 −0 modified@@ -101,5 +101,26 @@ describe('parser', function(){ isInvalidPayload('2[{"toString":"foo"}]'); isInvalidPayload('2[true,"foo"]'); isInvalidPayload('2[null,"bar"]'); + + function isInvalidAttachmentCount (str) { + expect(() => new parser.Decoder().add(str)).to.throwException( + /^Illegal attachments$/, + ); + } + + isInvalidAttachmentCount("5"); + isInvalidAttachmentCount("51"); + isInvalidAttachmentCount("5a-"); + isInvalidAttachmentCount("51.23-"); + }); + + it("throws an error when receiving too many attachments", () => { + const decoder = new parser.Decoder({ maxAttachments: 2 }); + + expect(() => { + decoder.add( + '53-["hello",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1},{"_placeholder":true,"num":2}]', + ); + }).to.throwException(/^too many attachments$/); }); });
9d39f1f08051fix(parser): add a limit to the number of binary attachments
2 files changed · +46 −4
index.js+25 −4 modified@@ -218,8 +218,12 @@ function encodeAsBinary(obj, callback) { * @api public */ -function Decoder() { +function Decoder(opts) { this.reconstructor = null; + opts = opts || {}; + this.opts = { + maxAttachments: opts.maxAttachments || 10, + }; } /** @@ -242,7 +246,7 @@ Decoder.prototype.add = function(obj) { if (this.reconstructor) { throw new Error("got plaintext data when reconstructing a packet"); } - packet = decodeString(obj); + packet = decodeString(obj, this.opts.maxAttachments); if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json this.reconstructor = new BinaryReconstructor(packet); @@ -292,11 +296,12 @@ function isPayloadValid(type, payload) { * Decode a packet String (JSON data) * * @param {String} str + * @param {Number} maxAttachments - the maximum number of binary attachments * @return {Object} packet * @api private */ -function decodeString(str) { +function decodeString(str, maxAttachments) { var i = 0; // look up type var p = { @@ -315,7 +320,13 @@ function decodeString(str) { if (buf != Number(buf) || str.charAt(i) !== '-') { throw new Error('Illegal attachments'); } - p.attachments = Number(buf); + var n = Number(buf); + if (!isInteger(n) || n < 0) { + throw new Error("Illegal attachments"); + } else if (n > maxAttachments) { + throw new Error("too many attachments"); + } + p.attachments = n; } // look up namespace (if any) @@ -432,3 +443,13 @@ function error(msg) { data: 'parser error: ' + msg }; } + +var isInteger = + Number.isInteger || + function (value) { + return ( + typeof value === "number" && + isFinite(value) && + Math.floor(value) === value + ); + };
test/parser.js+21 −0 modified@@ -101,5 +101,26 @@ describe('parser', function(){ isInvalidPayload('2[{"toString":"foo"}]'); isInvalidPayload('2[true,"foo"]'); isInvalidPayload('2[null,"bar"]'); + + function isInvalidAttachmentCount (str) { + expect(() => new parser.Decoder().add(str)).to.throwException( + /^Illegal attachments$/, + ); + } + + isInvalidAttachmentCount("5"); + isInvalidAttachmentCount("51"); + isInvalidAttachmentCount("5a-"); + isInvalidAttachmentCount("51.23-"); + }); + + it("throws an error when receiving too many attachments", () => { + const decoder = new parser.Decoder({ maxAttachments: 2 }); + + expect(() => { + decoder.add( + '53-["hello",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1},{"_placeholder":true,"num":2}]', + ); + }).to.throwException(/^too many attachments$/); }); });
b25738c416c4fix(parser): add a limit to the number of binary attachments
2 files changed · +91 −5
packages/socket.io-parser/lib/index.ts+31 −5 modified@@ -135,21 +135,41 @@ interface DecoderReservedEvents { decoded: (packet: Packet) => void; } +type JSONReviver = (this: any, key: string, value: any) => any; + +export interface DecoderOptions { + /** + * Custom reviver to pass down to JSON.parse() + */ + reviver?: JSONReviver; + /** + * Maximum number of binary attachments per packet + * @default 10 + */ + maxAttachments?: number; +} + /** * A socket.io Decoder instance * * @return {Object} decoder */ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> { private reconstructor: BinaryReconstructor; + private opts: Required<DecoderOptions>; /** * Decoder constructor - * - * @param {function} reviver - custom reviver to pass down to JSON.stringify */ - constructor(private reviver?: (this: any, key: string, value: any) => any) { + constructor(opts?: DecoderOptions | JSONReviver) { super(); + this.opts = Object.assign( + { + reviver: undefined, + maxAttachments: 10, + }, + typeof opts === "function" ? { reviver: opts } : opts, + ); } /** @@ -224,7 +244,13 @@ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> { if (buf != Number(buf) || str.charAt(i) !== "-") { throw new Error("Illegal attachments"); } - p.attachments = Number(buf); + const n = Number(buf); + if (!isInteger(n) || n < 0) { + throw new Error("Illegal attachments"); + } else if (n > this.opts.maxAttachments) { + throw new Error("too many attachments"); + } + p.attachments = n; } // look up namespace (if any) @@ -271,7 +297,7 @@ export class Decoder extends Emitter<{}, {}, DecoderReservedEvents> { private tryParse(str) { try { - return JSON.parse(str, this.reviver); + return JSON.parse(str, this.opts.reviver); } catch (e) { return false; }
packages/socket.io-parser/test/parser.js+60 −0 modified@@ -107,6 +107,56 @@ describe("socket.io-parser", () => { } }); + it("throws an error when receiving too many attachments", () => { + const decoder = new Decoder({ maxAttachments: 2 }); + + expect(() => { + decoder.add( + '53-["hello",{"_placeholder":true,"num":0},{"_placeholder":true,"num":1},{"_placeholder":true,"num":2}]', + ); + }).to.throwException(/^too many attachments$/); + }); + + it("decodes with a custom reviver", () => { + const decoder = new Decoder((key, value) => { + if (key === "a") { + return value.toUpperCase(); + } else { + return value; + } + }); + + return new Promise((resolve) => { + decoder.on("decoded", (packet) => { + expect(packet.data).to.eql(["b", { a: "VAL" }]); + resolve(); + }); + + decoder.add('2["b",{"a":"val"}]'); + }); + }); + + it("decodes with a custom reviver (options object)", () => { + const decoder = new Decoder({ + reviver: (key, value) => { + if (key === "a") { + return value.toUpperCase(); + } else { + return value; + } + }, + }); + + return new Promise((resolve) => { + decoder.on("decoded", (packet) => { + expect(packet.data).to.eql(["b", { a: "VAL" }]); + resolve(); + }); + + decoder.add('2["b",{"a":"val"}]'); + }); + }); + it("throw an error upon parsing error", () => { const isInvalidPayload = (str) => expect(() => new Decoder().add(str)).to.throwException( @@ -125,6 +175,16 @@ describe("socket.io-parser", () => { isInvalidPayload('2["connect"]'); isInvalidPayload('2["disconnect","123"]'); + const isInvalidAttachmentCount = (str) => + expect(() => new Decoder().add(str)).to.throwException( + /^Illegal attachments$/, + ); + + isInvalidAttachmentCount("5"); + isInvalidAttachmentCount("51"); + isInvalidAttachmentCount("5a-"); + isInvalidAttachmentCount("51.23-"); + expect(() => new Decoder().add("999")).to.throwException( /^unknown packet type 9$/, );
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/socketio/socket.io/commit/719f9ebab0772ffb882bd614b387e585c1aa75d4nvdPatchWEB
- github.com/socketio/socket.io/commit/9d39f1f080510f036782f2177fac701cc041faafnvdPatchWEB
- github.com/socketio/socket.io/commit/b25738c416c4e32fbff62ee182afa8f6d0dacf78nvdPatchWEB
- github.com/socketio/socket.io/security/advisories/GHSA-677m-j7p3-52f9nvdPatchVendor AdvisoryWEB
- github.com/advisories/GHSA-677m-j7p3-52f9ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-33151ghsaADVISORY
News mentions
0No linked articles in our index yet.