Skip to content

A client receiving an Object with more than 10 binary fields closes the connection #5504

Description

@guillaume-mueller

Describe the bug

When the Python or Node.js server sends an Object containing more than 10 binary fields to a web browser JS client got via NPM, it fails and the connection is interrupted.

The issue doesn't happen when the package is got via the CDN.

To Reproduce

Socket.IO server version: 4.8.3

Server

import { createServer } from 'http';
import { Server } from 'socket.io';

const PAYLOAD = Buffer.alloc(1); // Size doesn't matter

const httpServer = createServer();
const io = new Server(httpServer, {
    cors: { origin: "*" },
});

io.on('connection', (socket) => {
    console.log(`[socket ${socket.id}] connected`);

    socket.on('request_data', () => {
        socket.emit('data', {
            "1": PAYLOAD,
            "2": PAYLOAD,
            "3": PAYLOAD,
            "4": PAYLOAD,
            "5": PAYLOAD,
            "6": PAYLOAD,
            "7": PAYLOAD,
            "8": PAYLOAD,
            "9": PAYLOAD,
            "10": PAYLOAD,
            "11": PAYLOAD, // Commenting this line makes the issue disappear
        });
    });

    socket.on('disconnect', reason => {
        console.log(`[socket ${socket.id}] disconnected: ${reason}`);
    });
});

httpServer.listen(8000, 'localhost', () => {
    console.log('Server running at http://localhost:8000');
});

In Python with python-socketio 5.16.2 and uvicorn[standard] 0.48.0:

import socketio
import uvicorn

sio = socketio.AsyncServer(
    async_mode='asgi',
    cors_allowed_origins="*",
)

PAYLOAD = b"\x00" * 1  # Size doesn't matter

@sio.event
async def request_data(sid: str) -> None:
    await sio.emit('data', {
        "1": PAYLOAD,
        "2": PAYLOAD,
        "3": PAYLOAD,
        "4": PAYLOAD,
        "5": PAYLOAD,
        "6": PAYLOAD,
        "7": PAYLOAD,
        "8": PAYLOAD,
        "9": PAYLOAD,
        "10": PAYLOAD,
        "11": PAYLOAD,  # Commenting this line makes the issue disappear
    })

app = socketio.ASGIApp(sio)

if __name__ == '__main__':
    uvicorn.run(
        app,
        host='localhost',
        port=8000,
    )

Socket.IO client version: 4.8.3

Client

import { io } from 'socket.io-client';

const socket = io(`http://localhost:8000`);

socket.on('data', data => {
    console.log("Received data");
    console.log(data);
});

document.querySelector('#app').innerHTML = `
    <button id="requestData" type="button">Request Data</button>
`;

document.querySelector('#requestData').addEventListener(
    'click', () => socket.emit('request_data')
);
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>vite-project</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

Expected behavior

The message should be transmitted, showing in the browser console (F12), and the transport not disconnected.

Platform:

  • Device: x64 computer
  • OS: Linux Mint 22.3

Additional context

The issue does not happen via CDN:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Test Socket.IO</title>
</head>
<body>
    <button id="requestData" type="button">Request Data</button>
    <script src="https://cdn.socket.io/4.8.3/socket.io.min.js"></script>
    <script>
        const socket = io('http://localhost:8000');

        document.getElementById('requestData').addEventListener('click', () => {
            socket.emit('request_data');
        });

        socket.on('data', data => {
            console.log("Received data");
            console.log(data);
        });
    </script>
</body>
</html>

Activity

  1. darrachequesne commented on May 28, 2026

    @darrachequesne
    Member

    Hi! This behavior was indeed introduced in b25738c (released in socket.io-parser@4.2.6, included in socket.io@^4.6.0), in order to prevent a malicious user from making the server run out of memory (by buffering a lot binary attachments forever).

    See also: CVE-2026-33151

    The limit is indeed set to 10 binary attachments. It can be increased with a custom parser:

    import { Encoder, Decoder } from "socket.io-parser";
    
    const io = new Server({
      parser: {
        Encoder,
        Decoder: class extends Decoder {
          constructor() {
            super({
              maxAttachments: 20
            });
          }
        }
      }
    });

    There is no release of the socket.io-client package with the fix yet, that's why the bundle from the CDN does not show this behavior.

  2. added
    questionFurther information is requested
    and removed
    to triageWaiting to be triaged by a member of the team
    on May 28, 2026
  3. guillaume-mueller commented on May 28, 2026

    @guillaume-mueller
    Author

    I'm not sure to understand.

    Actually I need to use a Python server and a browser JS client got from NPM and bundled with Vite.

    I can do a variety of server and client combinations among Python, Node.js and browser JS and I think the issue is only caused by using the socket.io-client NPM package, but the solution you give involves tweaking the server.

    Here is also a Python client using python-socketio[asyncio-client] 5.16.2, which also doesn't suffer the issue while communicating with either the Python or Node.js server as described in my first message:

    import asyncio
    import socketio
    
    sio = socketio.AsyncClient()
    
    @sio.event
    async def connect():
        print('Connected to server')
        await asyncio.sleep(2)
        await sio.emit('request_data')
    
    @sio.event
    async def data(data):
        print('Received data from server')
        for key, value in data.items():
            print(f'{key}: {len(value)} bytes')
    
    async def main():
        await sio.connect('http://localhost:8000')
        await sio.wait()
    
    if __name__ == '__main__':
        asyncio.run(main())
  4. guillaume-mueller commented on May 28, 2026

    @guillaume-mueller
    Author

    OK, I saw the parser option is also settable to the client, so this solves the issue:

    import { io } from 'socket.io-client';
    import { Encoder, Decoder } from "socket.io-parser";
    
    const socket = io(
        `http://localhost:8000`,
        {
            parser: {
                Encoder,
                Decoder: class extends Decoder {
                    constructor() {
                        super({ maxAttachments: Infinity });
                    }
                },
            },
        },
    );

    Now I don't understand the security concern addressed.

    When I use the Node.js server described in my first message with a Python or browser JS via CDN client, the issue doesn't occur.

    So finally the limitation is only in the client, which a malicious end user can tweak as they wish.

    Also it would have been convenient if this behavior was documented, because I struggled quite a lot.

  5. nml0bts commented on May 29, 2026

    @nml0bts

    I believe CDN bundle is built against an older version of the parser (socket.io-parser < 4.2.6), thus the difference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions