CVE-2025-68492

Chainlit IDOR: Stealing AI Chat History via Socket.IO

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 15, 2026·5 min read

Executive Summary (TL;DR)

Chainlit versions before 2.8.5 trust the client-provided `threadId` during the Socket.IO handshake without verifying ownership. If an attacker guesses or obtains a valid thread UUID, they can impersonate the thread owner, read chat history, and potentially manipulate the conversation state. The fix involves a mandatory ownership check during connection.

A classic Insecure Direct Object Reference (IDOR) vulnerability in Chainlit's Socket.IO connection handling allows authenticated users to hijack chat sessions and view sensitive history by simply supplying another user's thread ID.

The Hook: Who's Talking to Your AI?

Chainlit has exploded in popularity as the go-to Python framework for whipping up AI chatbots. It's elegant, fast, and handles the messy bits of WebSocket communication for you. But as with many rapid-development frameworks, the devil is in the details—specifically, in how it handles state.

In the world of LLMs, context is king. To keep a conversation going, the backend needs to know which "thread" you are in. Chainlit handles this by passing a threadId back and forth. It sounds simple enough: you log in, you tell the server "I'm resuming thread X," and the server loads the history.

But here's the kicker: prior to version 2.8.5, the server was a little too trusting. It verified who you were, but it didn't verify whose data you were asking for. It's the digital equivalent of a hotel concierge checking your ID, confirming you are a guest, and then handing you the key to the Penthouse just because you asked for it.

The Flaw: Trust but Don't Verify

The vulnerability lies in backend/chainlit/socket.py. This file manages the real-time Socket.IO connections that power the chat interface. When a client connects, it sends an auth payload containing credentials (like a JWT) and metadata.

The logic flow went something like this:

  1. Receive connection request.
  2. Check if the user is logged in (Authentication). ✅
  3. Check if the user provided a threadId.
  4. If yes, load that thread.

Do you see the missing step? There was zero Authorization check linking the user.identifier to the threadId. The code implicitly assumed that if a valid user possesses a UUID, they must own it. This is a textbook Insecure Direct Object Reference (IDOR), specifically CWE-639 (Authorization Bypass Through User-Controlled Key).

The Code: The Smoking Gun

Let's look at the diff. This is where the oversight becomes painful to read. In the vulnerable version, the connect handler pulls the thread ID straight from the user-controlled input and runs with it.

Vulnerable Code (Pre-2.8.5):

@sio.on("connect")
async def connect(sid, environ, auth):
    # ... authentication logic ...
    if require_login():
        user, token = await _authenticate_connection(environ, auth)
        # Authentication passes, user is set.
    
    # The Fatal Flaw:
    # We take threadId from the client, no questions asked.
    thread_id = auth.get("threadId")
    
    # ... proceeds to load thread_id context ...

The fix, introduced in commit 8f1153d, adds the missing logic layer. It forces the application to query the data layer and confirm ownership before proceeding.

Patched Code (v2.8.5):

        if thread_id:
            # ... data layer init ...
            
            # The Fix: Explicit Ownership Check
            if not (await data_layer.get_thread_author(thread_id) == user.identifier):
                logger.error("Authorization for the thread failed.")
                raise ConnectionRefusedError("authorization failed")

It’s a simple three-line check, but without it, the entire data segregation model collapses.

The Exploit: Hijacking the Conversation

So, how do we exploit this? The CVSS score is relatively low (4.2) primarily because of Attack Complexity: High. Chainlit uses UUIDs for thread IDs, which are effectively impossible to brute-force. However, "impossible to brute-force" does not mean "impossible to find."

Scenario: Imagine a shared workspace or a leaked screenshot where a URL containing ?thread=... is visible. Or perhaps the application logs or API responses leak these IDs in a side channel. Once an attacker has a target threadId, the exploit is trivial.

Step 1: Intercept the Handshake Using a proxy like Burp Suite, capture the WebSocket upgrade request or the initial Socket.IO handshake packet.

Step 2: Modify the Payload You will see a JSON payload that looks like this:

{
  "token": "eyJhbGciOi...",
  "threadId": "[YOUR_OWN_THREAD_ID]"
}

Step 3: Swap and Replay Replace your thread ID with the victim's UUID:

{
  "token": "eyJhbGciOi...",
  "threadId": "[VICTIM_THREAD_UUID]"
}

Result: The server accepts the connection. The UI refreshes, and suddenly you are looking at the victim's chat history. You can see what they asked the AI, the AI's responses (which might include PII or code secrets), and you can even continue the conversation as if you were them.

The Impact: Why It Matters

In standard web apps, an IDOR might leak a profile picture or an invoice. In the context of AI applications, the stakes are often higher.

Users treat LLM interfaces like confessionals. They paste buggy code containing API keys. They analyze confidential legal documents. They draft sensitive emails. If an attacker gains access to a thread, they aren't just seeing metadata; they are seeing the raw thought process and sensitive inputs of the user.

Furthermore, this isn't read-only access. By connecting to the socket with the hijacked thread ID, the attacker essentially becomes the user for that session context. They could potentially inject prompts that poison the user's long-term memory context or extract further data.

Fix Analysis (1)

Technical Appendix

CVSS Score
4.2/ 10
CVSS:3.0/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N
EPSS Probability
0.03%
Top 93% most exploited

Affected Systems

Chainlit Framework < 2.8.5

Affected Versions Detail

Product
Affected Versions
Fixed Version
Chainlit
Chainlit
< 2.8.52.8.5
AttributeDetail
CWECWE-639: Authorization Bypass Through User-Controlled Key
CVSS v3.14.2 (Medium)
CVSS v4.02.3 (Low)
Attack VectorNetwork (Socket.IO)
Attack ComplexityHigh (Requires guessing/stealing UUID)
Privileges RequiredLow (Authenticated User)
ImpactConfidentiality & Integrity (Partial)
CWE-639
Insecure Direct Object Reference (IDOR)

The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying a key value identifying the data.

Vulnerability Timeline

Fix committed to GitHub
2025-11-07
Version 2.8.5 released
2025-11-08
CVE Published
2026-01-14

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.