Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions lib/peers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)) {
Expand All @@ -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);
Expand Down
79 changes: 79 additions & 0 deletions test/unit/peers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
});