mirror of
https://github.com/obra/superpowers.git
synced 2026-06-11 21:29:07 +08:00
fix(brainstorming): cap websocket frame payloads
This commit is contained in:
@@ -7,6 +7,7 @@ const path = require('path');
|
|||||||
|
|
||||||
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
|
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A };
|
||||||
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||||
|
const MAX_FRAME_PAYLOAD_BYTES = 10 * 1024 * 1024;
|
||||||
|
|
||||||
function computeAcceptKey(clientKey) {
|
function computeAcceptKey(clientKey) {
|
||||||
return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
|
return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64');
|
||||||
@@ -53,10 +54,18 @@ function decodeFrame(buffer) {
|
|||||||
offset = 4;
|
offset = 4;
|
||||||
} else if (payloadLen === 127) {
|
} else if (payloadLen === 127) {
|
||||||
if (buffer.length < 10) return null;
|
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;
|
offset = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payloadLen > MAX_FRAME_PAYLOAD_BYTES) {
|
||||||
|
throw new Error('WebSocket frame payload exceeds maximum allowed size');
|
||||||
|
}
|
||||||
|
|
||||||
const maskOffset = offset;
|
const maskOffset = offset;
|
||||||
const dataOffset = offset + 4;
|
const dataOffset = offset + 4;
|
||||||
const totalLen = dataOffset + payloadLen;
|
const totalLen = dataOffset + payloadLen;
|
||||||
@@ -351,4 +360,4 @@ if (require.main === module) {
|
|||||||
startServer();
|
startServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES };
|
module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES, MAX_FRAME_PAYLOAD_BYTES };
|
||||||
|
|||||||
@@ -329,6 +329,21 @@ function runTests() {
|
|||||||
assert.strictEqual(result.payload.length, 65536);
|
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 ==========
|
// ========== Close Frame with Status Code ==========
|
||||||
console.log('\n--- Close Frame Details ---');
|
console.log('\n--- Close Frame Details ---');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user