Security advisory GHSA-qm95-pgcg-qqfq: Insufficient validation when decoding a Socket.IO packet
Package: socket.io-parser (npm), vulnerable version: 4.0.4, fixed in: 4.0.5
CWE: CWE-1287
CWE-20
CWE-89
Files changed by the fix:
binary.js
index.js

Fix diff:
diff --git a/binary.js b/binary.js
index 3e2347d..95a1450 100644
--- a/binary.js
+++ b/binary.js
@@ -70,8 +70,16 @@ exports.reconstructPacket = function(packet, buffers) {
 function _reconstructPacket(data, buffers) {
   if (!data) return data;
 
-  if (data && data._placeholder) {
-    return buffers[data.num]; // appropriate buffer (should be natural order anyway)
+  if (data && data._placeholder === true) {
+    var isIndexValid =
+      typeof data.num === "number" &&
+      data.num >= 0 &&
+      data.num < buffers.length;
+    if (isIndexValid) {
+      return buffers[data.num]; // appropriate buffer (should be natural order anyway)
+    } else {
+      throw new Error("illegal attachments");
+    }
   } else if (isArray(data)) {
     for (var i = 0; i < data.length; i++) {
       data[i] = _reconstructPacket(data[i], buffers);
diff --git a/index.js b/index.js
index ff613cc..245a800 100644
--- a/index.js
+++ b/index.js
@@ -239,6 +239,9 @@ Emitter(Decoder.prototype);
 Decoder.prototype.add = function(obj) {
   var packet;
   if (typeof obj === 'string') {
+    if (this.reconstructor) {
+      throw new Error("got plaintext data when reconstructing a packet");
+    }
     packet = decodeString(obj);
     if (exports.BINARY_EVENT === packet.type || exports.BINARY_ACK === packet.type) { // binary packet's json
       this.reconstructor = new BinaryReconstructor(packet);
diff --git a/test/buffer.js b/test/buffer.js
index 3aba898..f18e68a 100644
--- a/test/buffer.js
+++ b/test/buffer.js
@@ -1,8 +1,7 @@
 var parser = require('../index.js');
 var expect = require('expect.js');
 var helpers = require('./helpers.js');
-var encode = parser.encode;
-var decode = parser.decode;
+var Decoder = parser.Decoder;
 
 describe('parser', function() {
   it('encodes a Buffer', function() {
@@ -14,6 +13,15 @@ describe('parser', function() {
       });
   });
 
+  it("encodes a nested Buffer", function() {
+    helpers.test_bin({
+      type: parser.BINARY_EVENT,
+      data: ["a", { b: ["c", Buffer.from("abc", "utf8")] }],
+      id: 23,
+      nsp: "/cool",
+    });
+  });
+
   it('encodes a binary ack with Buffer', function() {
     helpers.test_bin({
       type: parser.BINARY_ACK,
@@ -22,4 +30,39 @@ describe('parser', function() {
       nsp: '/back'
     })
   });
+
+  it("throws an error when adding an attachment with an invalid 'num' attribute (string)", function() {
+    var decoder = new Decoder();
+
+    expect(function() {
+      decoder.add('51-["hello",{"_placeholder":true,"num":"splice"}]');
+      decoder.add(Buffer.from("world"));
+    }).to.throwException(/^illegal attachments$/);
+  });
+
+  it("throws an error when adding an attachment with an invalid 'num' attribute (out-of-bound)", function() {
+    var decoder = new Decoder();
+
+    expect(function() {
+      decoder.add('51-["hello",{"_placeholder":true,"num":1}]');
+      decoder.add(Buffer.from("world"));
+    }).to.throwException(/^illegal attachments$/);
+  });
+
+  it("throws an error when adding an attachment without header", function() {
+    var decoder = new Decoder();
+
+    expect(function() {
+      decoder.add(Buffer.from("world"));
+    }).to.throwException(/^got binary data when not reconstructing a packet$/);
+  });
+
+  it("throws an error when decoding a binary event without attachments", function() {
+    var decoder = new Decoder();
+
+    expect(function() {
+      decoder.add('51-["hello",{"_placeholder":true,"num":0}]');
+      decoder.add('2["hello"]');
+    }).to.throwException(/^got plaintext data when reconstructing a packet$/);
+  });
 });