CVE-2024-37890
Description
ws is an open source WebSocket client and server for Node.js. A request with a number of headers exceeding theserver.maxHeadersCount threshold could be used to crash a ws server. The vulnerability was fixed in ws@8.17.1 (e55e510) and backported to ws@7.5.10 (22c2876), ws@6.2.3 (eeb76d3), and ws@5.2.4 (4abd8f6). In vulnerable versions of ws, the issue can be mitigated in the following ways: 1. Reduce the maximum allowed length of the request headers using the --max-http-header-size=size and/or the maxHeaderSize options so that no more headers than the server.maxHeadersCount limit can be sent. 2. Set server.maxHeadersCount to 0 so that no limit is applied.
Patches
422c28763234a[security] Fix crash when the Upgrade header cannot be read (#2231)
4 files changed · +73 −2
lib/websocket.js+3 −1 modified@@ -799,7 +799,9 @@ function initAsClient(websocket, address, protocols, options) { req = websocket._req = null; - if (res.headers.upgrade.toLowerCase() !== 'websocket') { + const upgrade = res.headers.upgrade; + + if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') { abortHandshake(websocket, socket, 'Invalid Upgrade header'); return; }
lib/websocket-server.js+3 −1 modified@@ -210,12 +210,14 @@ class WebSocketServer extends EventEmitter { req.headers['sec-websocket-key'] !== undefined ? req.headers['sec-websocket-key'].trim() : false; + const upgrade = req.headers.upgrade; const version = +req.headers['sec-websocket-version']; const extensions = {}; if ( req.method !== 'GET' || - req.headers.upgrade.toLowerCase() !== 'websocket' || + upgrade === undefined || + upgrade.toLowerCase() !== 'websocket' || !key || !keyRegex.test(key) || (version !== 8 && version !== 13) ||
test/websocket-server.test.js+41 −0 modified@@ -494,6 +494,47 @@ describe('WebSocketServer', () => { }); describe('Connection establishing', () => { + it('fails if the Upgrade header field value cannot be read', (done) => { + const server = http.createServer(); + const wss = new WebSocket.Server({ noServer: true }); + + server.maxHeadersCount = 1; + + server.on('upgrade', (req, socket, head) => { + assert.deepStrictEqual(req.headers, { foo: 'bar' }); + wss.handleUpgrade(req, socket, head, () => { + done(new Error('Unexpected callback invocation')); + }); + }); + + server.listen(() => { + const req = http.get({ + port: server.address().port, + headers: { + foo: 'bar', + bar: 'baz', + Connection: 'Upgrade', + Upgrade: 'websocket' + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + + const chunks = []; + + res.on('data', (chunk) => { + chunks.push(chunk); + }); + + res.on('end', () => { + assert.strictEqual(Buffer.concat(chunks).toString(), 'Bad Request'); + server.close(done); + }); + }); + }); + }); + it('fails if the Sec-WebSocket-Key header is invalid (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({
test/websocket.test.js+26 −0 modified@@ -528,6 +528,32 @@ describe('WebSocket', () => { beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); + it('fails if the Upgrade header field value cannot be read', (done) => { + server.once('upgrade', (req, socket) => { + socket.on('end', socket.end); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Connection: Upgrade\r\n' + + 'Upgrade: websocket\r\n' + + '\r\n' + ); + }); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`); + + ws._req.maxHeadersCount = 1; + + ws.on('upgrade', (res) => { + assert.deepStrictEqual(res.headers, { connection: 'Upgrade' }); + + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Invalid Upgrade header'); + done(); + }); + }); + }); + it('fails if the Upgrade header field value is not "websocket"', (done) => { server.once('upgrade', (req, socket) => { socket.on('end', socket.end);
eeb76d313e2a[security] Fix crash when the Upgrade header cannot be read (#2231)
2 files changed · +44 −1
lib/websocket-server.js+3 −1 modified@@ -186,12 +186,14 @@ class WebSocketServer extends EventEmitter { req.headers['sec-websocket-key'] !== undefined ? req.headers['sec-websocket-key'].trim() : false; + const upgrade = req.headers.upgrade; const version = +req.headers['sec-websocket-version']; const extensions = {}; if ( req.method !== 'GET' || - req.headers.upgrade.toLowerCase() !== 'websocket' || + upgrade === undefined || + upgrade.toLowerCase() !== 'websocket' || !key || !keyRegex.test(key) || (version !== 8 && version !== 13) ||
test/websocket-server.test.js+41 −0 modified@@ -383,6 +383,47 @@ describe('WebSocketServer', () => { }); describe('Connection establishing', () => { + it('fails if the Upgrade header field value cannot be read', (done) => { + const server = http.createServer(); + const wss = new WebSocket.Server({ noServer: true }); + + server.maxHeadersCount = 1; + + server.on('upgrade', (req, socket, head) => { + assert.deepStrictEqual(req.headers, { foo: 'bar' }); + wss.handleUpgrade(req, socket, head, () => { + done(new Error('Unexpected callback invocation')); + }); + }); + + server.listen(() => { + const req = http.get({ + port: server.address().port, + headers: { + foo: 'bar', + bar: 'baz', + Connection: 'Upgrade', + Upgrade: 'websocket' + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + + const chunks = []; + + res.on('data', (chunk) => { + chunks.push(chunk); + }); + + res.on('end', () => { + assert.strictEqual(Buffer.concat(chunks).toString(), 'Bad Request'); + server.close(done); + }); + }); + }); + }); + it('fails if the Sec-WebSocket-Key header is invalid (1/2)', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({
4abd8f6de4b0[security] Fix crash when the Upgrade header cannot be read (#2231)
2 files changed · +44 −1
lib/websocket-server.js+3 −1 modified@@ -161,11 +161,13 @@ class WebSocketServer extends EventEmitter { handleUpgrade (req, socket, head, cb) { socket.on('error', socketOnError); + const upgrade = req.headers.upgrade; const version = +req.headers['sec-websocket-version']; const extensions = {}; if ( - req.method !== 'GET' || req.headers.upgrade.toLowerCase() !== 'websocket' || + req.method !== 'GET' || upgrade === undefined || + upgrade.toLowerCase() !== 'websocket' || !req.headers['sec-websocket-key'] || (version !== 8 && version !== 13) || !this.shouldHandle(req) ) {
test/websocket-server.test.js+41 −0 modified@@ -364,6 +364,47 @@ describe('WebSocketServer', function () { }); describe('Connection establishing', function () { + it('fails if the Upgrade header field value cannot be read', (done) => { + const server = http.createServer(); + const wss = new WebSocket.Server({ noServer: true }); + + server.maxHeadersCount = 1; + + server.on('upgrade', (req, socket, head) => { + assert.deepStrictEqual(req.headers, { foo: 'bar' }); + wss.handleUpgrade(req, socket, head, () => { + done(new Error('Unexpected callback invocation')); + }); + }); + + server.listen(() => { + const req = http.get({ + port: server.address().port, + headers: { + foo: 'bar', + bar: 'baz', + Connection: 'Upgrade', + Upgrade: 'websocket' + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + + const chunks = []; + + res.on('data', (chunk) => { + chunks.push(chunk); + }); + + res.on('end', () => { + assert.strictEqual(Buffer.concat(chunks).toString(), 'Bad Request'); + server.close(done); + }); + }); + }); + }); + it('fails if the Sec-WebSocket-Key header is invalid', function (done) { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({
e55e5106f10f[security] Fix crash when the Upgrade header cannot be read (#2231)
4 files changed · +76 −3
lib/websocket.js+3 −1 modified@@ -928,7 +928,9 @@ function initAsClient(websocket, address, protocols, options) { req = websocket._req = null; - if (res.headers.upgrade.toLowerCase() !== 'websocket') { + const upgrade = res.headers.upgrade; + + if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') { abortHandshake(websocket, socket, 'Invalid Upgrade header'); return; }
lib/websocket-server.js+3 −2 modified@@ -235,6 +235,7 @@ class WebSocketServer extends EventEmitter { socket.on('error', socketOnError); const key = req.headers['sec-websocket-key']; + const upgrade = req.headers.upgrade; const version = +req.headers['sec-websocket-version']; if (req.method !== 'GET') { @@ -243,13 +244,13 @@ class WebSocketServer extends EventEmitter { return; } - if (req.headers.upgrade.toLowerCase() !== 'websocket') { + if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') { const message = 'Invalid Upgrade header'; abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); return; } - if (!key || !keyRegex.test(key)) { + if (key === undefined || !keyRegex.test(key)) { const message = 'Missing or invalid Sec-WebSocket-Key header'; abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); return;
test/websocket-server.test.js+44 −0 modified@@ -653,6 +653,50 @@ describe('WebSocketServer', () => { }); }); + it('fails if the Upgrade header field value cannot be read', (done) => { + const server = http.createServer(); + const wss = new WebSocket.Server({ noServer: true }); + + server.maxHeadersCount = 1; + + server.on('upgrade', (req, socket, head) => { + assert.deepStrictEqual(req.headers, { foo: 'bar' }); + wss.handleUpgrade(req, socket, head, () => { + done(new Error('Unexpected callback invocation')); + }); + }); + + server.listen(() => { + const req = http.get({ + port: server.address().port, + headers: { + foo: 'bar', + bar: 'baz', + Connection: 'Upgrade', + Upgrade: 'websocket' + } + }); + + req.on('response', (res) => { + assert.strictEqual(res.statusCode, 400); + + const chunks = []; + + res.on('data', (chunk) => { + chunks.push(chunk); + }); + + res.on('end', () => { + assert.strictEqual( + Buffer.concat(chunks).toString(), + 'Invalid Upgrade header' + ); + server.close(done); + }); + }); + }); + }); + it('fails if the Upgrade header field value is not "websocket"', (done) => { const wss = new WebSocket.Server({ port: 0 }, () => { const req = http.get({
test/websocket.test.js+26 −0 modified@@ -757,6 +757,32 @@ describe('WebSocket', () => { beforeEach((done) => server.listen(0, done)); afterEach((done) => server.close(done)); + it('fails if the Upgrade header field value cannot be read', (done) => { + server.once('upgrade', (req, socket) => { + socket.on('end', socket.end); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Connection: Upgrade\r\n' + + 'Upgrade: websocket\r\n' + + '\r\n' + ); + }); + + const ws = new WebSocket(`ws://localhost:${server.address().port}`); + + ws._req.maxHeadersCount = 1; + + ws.on('upgrade', (res) => { + assert.deepStrictEqual(res.headers, { connection: 'Upgrade' }); + + ws.on('error', (err) => { + assert.ok(err instanceof Error); + assert.strictEqual(err.message, 'Invalid Upgrade header'); + done(); + }); + }); + }); + it('fails if the Upgrade header field value is not "websocket"', (done) => { server.once('upgrade', (req, socket) => { socket.on('end', socket.end);
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-3h5v-q93c-6h6qghsaADVISORY
- github.com/websockets/ws/commit/22c28763234aa75a7e1b76f5c01c181260d7917fnvd
- github.com/websockets/ws/commit/4abd8f6de4b0b65ef80b3ff081989479ed93377envd
- github.com/websockets/ws/commit/e55e5106f10fcbaac37cfa89759e4cc0d073a52cnvd
- github.com/websockets/ws/commit/eeb76d313e2a00dd5247ca3597bba7877d064a63nvd
- github.com/websockets/ws/issues/2230nvd
- github.com/websockets/ws/pull/2231nvd
- github.com/websockets/ws/security/advisories/GHSA-3h5v-q93c-6h6qnvd
- nodejs.org/api/http.htmlnvd
News mentions
0No linked articles in our index yet.