diff --git a/skills/brainstorming/scripts/server.cjs b/skills/brainstorming/scripts/server.cjs index 562c17f8..79661a99 100644 --- a/skills/brainstorming/scripts/server.cjs +++ b/skills/brainstorming/scripts/server.cjs @@ -7,6 +7,7 @@ const path = require('path'); const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; +const MAX_FRAME_PAYLOAD_BYTES = 10 * 1024 * 1024; function computeAcceptKey(clientKey) { return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); @@ -53,10 +54,18 @@ function decodeFrame(buffer) { offset = 4; } else if (payloadLen === 127) { if (buffer.length < 10) return null; - payloadLen = Number(buffer.readBigUInt64BE(2)); + const extendedLen = buffer.readBigUInt64BE(2); + if (extendedLen > BigInt(MAX_FRAME_PAYLOAD_BYTES)) { + throw new Error('WebSocket frame payload exceeds maximum allowed size'); + } + payloadLen = Number(extendedLen); offset = 10; } + if (payloadLen > MAX_FRAME_PAYLOAD_BYTES) { + throw new Error('WebSocket frame payload exceeds maximum allowed size'); + } + const maskOffset = offset; const dataOffset = offset + 4; const totalLen = dataOffset + payloadLen; @@ -351,4 +360,4 @@ if (require.main === module) { startServer(); } -module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES }; +module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES, MAX_FRAME_PAYLOAD_BYTES }; diff --git a/tests/brainstorm-server/ws-protocol.test.js b/tests/brainstorm-server/ws-protocol.test.js index 4f2540d7..8e98bc32 100644 --- a/tests/brainstorm-server/ws-protocol.test.js +++ b/tests/brainstorm-server/ws-protocol.test.js @@ -329,6 +329,21 @@ function runTests() { assert.strictEqual(result.payload.length, 65536); }); + test('rejects oversized 64-bit frames before payload allocation', () => { + const mask = Buffer.from([0x00, 0x00, 0x00, 0x00]); + const header = Buffer.alloc(14); + header[0] = 0x81; // FIN + TEXT + header[1] = 0x80 | 127; // masked, 64-bit length + header.writeBigUInt64BE(BigInt(ws.MAX_FRAME_PAYLOAD_BYTES) + 1n, 2); + mask.copy(header, 10); + + assert.throws( + () => ws.decodeFrame(header), + /exceeds maximum allowed size/i, + 'oversized advertised payload must be rejected from header alone' + ); + }); + // ========== Close Frame with Status Code ========== console.log('\n--- Close Frame Details ---');