Skip to content

Commit 83a63c0

Browse files
authored
Merge pull request #33 from monteslu/fix/safe-json-parsing
fix: Safe JSON parsing (CVE-HSYNC-2026-005)
2 parents 0383412 + c1b8ac7 commit 83a63c0

File tree

2 files changed

+105
-3
lines changed

2 files changed

+105
-3
lines changed

lib/peers.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,13 @@ export function initPeers(hsyncClient) {
116116
const msg = await parsePacket(toParse);
117117
const [p1] = msg.topic.split('/');
118118
if (p1 === 'rpc') {
119-
const rpcMsg = JSON.parse(msg.payload.toString());
119+
let rpcMsg;
120+
try {
121+
rpcMsg = JSON.parse(msg.payload.toString());
122+
} catch (parseErr) {
123+
debug('error parsing RPC message', parseErr);
124+
return;
125+
}
120126
debug('↓ peer RTC rpc', rpcMsg);
121127
// if (rpcMsg.method) {
122128
transport.receiveData(rpcMsg);
@@ -272,7 +278,17 @@ export function initPeers(hsyncClient) {
272278
transport.receiveData = (msg) => {
273279
debug('↓ transport.receiveData', msg);
274280
if (typeof msg === 'string') {
275-
msg = JSON.parse(msg);
281+
try {
282+
msg = JSON.parse(msg);
283+
} catch (parseErr) {
284+
debug('error parsing transport message', parseErr);
285+
return;
286+
}
287+
}
288+
// Ensure msg is a valid object before processing
289+
if (!msg || typeof msg !== 'object') {
290+
debug('invalid message format, ignoring');
291+
return;
276292
}
277293
debug('↓ peer rpc receivedData', msg);
278294
if (msg.params && Array.isArray(msg.params)) {
@@ -299,8 +315,15 @@ export function initPeers(hsyncClient) {
299315
hsyncClient.mqConn.publish(topic, Buffer.from(msg));
300316
};
301317
transport.receiveData = (msg) => {
302-
if (msg) {
318+
if (!msg) {
319+
debug('↓ server rpc inbound: empty message, ignoring');
320+
return;
321+
}
322+
try {
303323
msg = JSON.parse(msg);
324+
} catch (parseErr) {
325+
debug('error parsing server RPC message', parseErr);
326+
return;
304327
}
305328
debug('↓ server rpc inbound', msg);
306329
transport.emit('rpc', msg);

test/unit/peers.test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,5 +271,84 @@ describe('peers', () => {
271271
expect(peer.dcOpen).toBe(false);
272272
expect(peer.packAndSend).toBeUndefined();
273273
});
274+
275+
describe('JSON parsing security (CVE-HSYNC-2026-005)', () => {
276+
it('should handle invalid JSON in transport.receiveData without crashing', () => {
277+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
278+
279+
// Should not throw on invalid JSON
280+
expect(() => {
281+
peer.transport.receiveData('not valid json {{{');
282+
}).not.toThrow();
283+
});
284+
285+
it('should handle empty string in transport.receiveData', () => {
286+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
287+
288+
// Empty string should not throw
289+
expect(() => {
290+
peer.transport.receiveData('');
291+
}).not.toThrow();
292+
});
293+
294+
it('should handle malformed JSON payloads gracefully', () => {
295+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
296+
297+
// Various malformed inputs - should not crash
298+
const malformedInputs = ['{"unclosed": ', '[1, 2, 3', 'undefined', 'NaN'];
299+
300+
for (const input of malformedInputs) {
301+
expect(() => {
302+
peer.transport.receiveData(input);
303+
}).not.toThrow();
304+
}
305+
});
306+
307+
it('should reject non-object JSON values', () => {
308+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
309+
310+
// Valid JSON but not objects - should not crash
311+
const nonObjectInputs = ['"just a string"', '123', 'true', 'null'];
312+
313+
for (const input of nonObjectInputs) {
314+
expect(() => {
315+
peer.transport.receiveData(input);
316+
}).not.toThrow();
317+
}
318+
});
319+
320+
it('should process valid JSON normally', () => {
321+
const peer = peerLib.createRPCPeer({ hostName: 'other.example.com' });
322+
const rpcEmitSpy = vi.spyOn(peer.transport, 'emit');
323+
324+
const validMsg = JSON.stringify({ method: 'test', params: [] });
325+
peer.transport.receiveData(validMsg);
326+
327+
expect(rpcEmitSpy).toHaveBeenCalledWith('rpc', expect.objectContaining({ method: 'test' }));
328+
});
329+
330+
it('should handle invalid JSON in server peer transport', () => {
331+
const serverPeer = peerLib.createServerPeer();
332+
333+
// Should not throw on invalid JSON
334+
expect(() => {
335+
serverPeer.transport.receiveData('invalid json');
336+
}).not.toThrow();
337+
});
338+
339+
it('should handle null/undefined in server peer transport', () => {
340+
const serverPeer = peerLib.createServerPeer();
341+
342+
// Null should not throw
343+
expect(() => {
344+
serverPeer.transport.receiveData(null);
345+
}).not.toThrow();
346+
347+
// Undefined should not throw
348+
expect(() => {
349+
serverPeer.transport.receiveData(undefined);
350+
}).not.toThrow();
351+
});
352+
});
274353
});
275354
});

0 commit comments

Comments
 (0)