From 08f3bb2185a89401e7eaa7710c8f756c01649511 Mon Sep 17 00:00:00 2001 From: Luis Montes Date: Fri, 6 Feb 2026 23:56:53 -0700 Subject: [PATCH 1/2] fix: add safe JSON parsing to prevent crashes (CVE-HSYNC-2026-005) --- lib/peers.js | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/peers.js b/lib/peers.js index 255e38b..58e41b4 100644 --- a/lib/peers.js +++ b/lib/peers.js @@ -116,7 +116,13 @@ export function initPeers(hsyncClient) { const msg = await parsePacket(toParse); const [p1] = msg.topic.split('/'); if (p1 === 'rpc') { - const rpcMsg = JSON.parse(msg.payload.toString()); + let rpcMsg; + try { + rpcMsg = JSON.parse(msg.payload.toString()); + } catch (parseErr) { + debug('error parsing RPC message', parseErr); + return; + } debug('↓ peer RTC rpc', rpcMsg); // if (rpcMsg.method) { transport.receiveData(rpcMsg); @@ -272,7 +278,17 @@ export function initPeers(hsyncClient) { transport.receiveData = (msg) => { debug('↓ transport.receiveData', msg); if (typeof msg === 'string') { - msg = JSON.parse(msg); + try { + msg = JSON.parse(msg); + } catch (parseErr) { + debug('error parsing transport message', parseErr); + return; + } + } + // Ensure msg is a valid object before processing + if (!msg || typeof msg !== 'object') { + debug('invalid message format, ignoring'); + return; } debug('↓ peer rpc receivedData', msg); if (msg.params && Array.isArray(msg.params)) { @@ -299,8 +315,15 @@ export function initPeers(hsyncClient) { hsyncClient.mqConn.publish(topic, Buffer.from(msg)); }; transport.receiveData = (msg) => { - if (msg) { + if (!msg) { + debug('↓ server rpc inbound: empty message, ignoring'); + return; + } + try { msg = JSON.parse(msg); + } catch (parseErr) { + debug('error parsing server RPC message', parseErr); + return; } debug('↓ server rpc inbound', msg); transport.emit('rpc', msg); From c1b8ac7150540c55c7b659c03a60d4849811579f Mon Sep 17 00:00:00 2001 From: Luis Montes Date: Fri, 6 Feb 2026 23:56:54 -0700 Subject: [PATCH 2/2] test: add JSON parsing security tests --- test/unit/peers.test.js | 79 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/unit/peers.test.js b/test/unit/peers.test.js index b32c42c..49ad5c6 100644 --- a/test/unit/peers.test.js +++ b/test/unit/peers.test.js @@ -271,5 +271,84 @@ describe('peers', () => { expect(peer.dcOpen).toBe(false); expect(peer.packAndSend).toBeUndefined(); }); + + describe('JSON parsing security (CVE-HSYNC-2026-005)', () => { + it('should handle invalid JSON in transport.receiveData without crashing', () => { + const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' }); + + // Should not throw on invalid JSON + expect(() => { + peer.transport.receiveData('not valid json {{{'); + }).not.toThrow(); + }); + + it('should handle empty string in transport.receiveData', () => { + const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' }); + + // Empty string should not throw + expect(() => { + peer.transport.receiveData(''); + }).not.toThrow(); + }); + + it('should handle malformed JSON payloads gracefully', () => { + const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' }); + + // Various malformed inputs - should not crash + const malformedInputs = ['{"unclosed": ', '[1, 2, 3', 'undefined', 'NaN']; + + for (const input of malformedInputs) { + expect(() => { + peer.transport.receiveData(input); + }).not.toThrow(); + } + }); + + it('should reject non-object JSON values', () => { + const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' }); + + // Valid JSON but not objects - should not crash + const nonObjectInputs = ['"just a string"', '123', 'true', 'null']; + + for (const input of nonObjectInputs) { + expect(() => { + peer.transport.receiveData(input); + }).not.toThrow(); + } + }); + + it('should process valid JSON normally', () => { + const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' }); + const rpcEmitSpy = vi.spyOn(peer.transport, 'emit'); + + const validMsg = JSON.stringify({ method: 'test', params: [] }); + peer.transport.receiveData(validMsg); + + expect(rpcEmitSpy).toHaveBeenCalledWith('rpc', expect.objectContaining({ method: 'test' })); + }); + + it('should handle invalid JSON in server peer transport', () => { + const serverPeer = peerLib.createServerPeer(); + + // Should not throw on invalid JSON + expect(() => { + serverPeer.transport.receiveData('invalid json'); + }).not.toThrow(); + }); + + it('should handle null/undefined in server peer transport', () => { + const serverPeer = peerLib.createServerPeer(); + + // Null should not throw + expect(() => { + serverPeer.transport.receiveData(null); + }).not.toThrow(); + + // Undefined should not throw + expect(() => { + serverPeer.transport.receiveData(undefined); + }).not.toThrow(); + }); + }); }); });