---
id: "GHSA-m9fg-w9qh-67pc"
page: "https://infrastack.ai/live/GHSA-m9fg-w9qh-67pc"
kind: "vulnerability"
aliases: ["GHSA-m9fg-w9qh-67pc"]
title: "sanitize_message() address regex is lowercase-only and does not match PIL's uppercase __repr__, leaking live heap addresses to unauthenticated clients"
severity: "medium"
cvss: 5.3
epss: null
exploited: false
status: "open"
category: "ai-tooling"
products: [{"name": "vllm", "fixed": ["0.31.0"], "affected": ">= 0.14.0rc1, < 0.31.0", "ecosystem": "pypi"}]
ecosystems: ["pypi"]
tags: ["ai-tooling", "cwe-532", "multimodal", "python", "self-hosted", "vllm"]
not_affected_if: ["no-multimodal-inputs", "not-network-exposed", "version-0.31.0-or-later"]
published_at: "2026-10-27T22:14:15+00:00"
first_seen_at: "2026-11-09T22:53:23+00:00"
updated_at: "2026-11-09T22:56:44+00:00"
sources: [{"source": "ghsa-repo", "url": "https://github.com/vllm-project/vllm/security/advisories/GHSA-m9fg-w9qh-67pc", "first_seen_at": "2026-11-09T22:53:23+00:00"}]
exploit_sources: []
---

# sanitize_message() address regex is lowercase-only and does not match PIL's uppercase __repr__, leaking live heap addresses to unauthenticated clients

Link for people: https://infrastack.ai/live/GHSA-m9fg-w9qh-67pc (this .md is for agents)

vLLM's error-message scrubber only removes lowercase hexadecimal memory addresses, but Pillow's image repr prints them in uppercase, so a crafted small PNG sent to an unauthenticated endpoint returns a live heap address of the server process. The attacker gets an address, not memory contents, which weakens ASLR protection for a later exploit chain.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- pypi:vllm affected >= 0.14.0rc1, < 0.31.0 fixed 0.31.0

## How to check

- Check the installed vLLM version with `pip show vllm` and treat versions `>= 0.14.0rc1` and `< 0.31.0` as affected.
- Check whether multimodal inputs are served, since the sink is media processing in the frontend.
- Check whether `--api-key` is set; the advisory says `/tokenize` stays unauthenticated even when it is.

## Fix

- `vllm`: upgrade to `0.31.0` or later.

## Timeline

- 2026-11-09T22:53:23+00:00 ghsa-repo https://github.com/vllm-project/vllm/security/advisories/GHSA-m9fg-w9qh-67pc

## Documents

- [advisory] Maintainer's advisory: github.com https://github.com/vllm-project/vllm/security/advisories/GHSA-m9fg-w9qh-67pc

## From the sources

### Summary

`sanitize_message()` scrubs heap addresses from error messages with the regex `r" at 0x[0-9a-f]+>"` - lowercase only, no `IGNORECASE`. `PIL.Image.Image.__repr__` formats its address as `0x{id(self):X}` - uppercase. The scrubber structurally cannot match the one object class both parent advisories are about.

A 564-byte PNG sent to an unauthenticated endpoint returns a live heap address of the running server process.

This is not a new hardening proposal. The CVE-2026-54236 advisory explicitly recommended widening this exact regex to `[0-9a-fA-F]`; three months on it is byte-identical.

### Details

#### The mismatch

```python
# the scrubber
r" at 0x[0-9a-f]+>"

# what PIL actually emits
# PIL/Image.py — Image.__repr__
"<%s.%s image mode=%s size=%dx%d at 0x%X>" % (..., id(self))
```

The parent CVE's own lowercase `BytesIO` shape is scrubbed correctly today. PIL's uppercase form is not.

#### The sink

`vllm/multimodal/processing/context.py:347` f-strings the PIL objects into any HF-processor failure message, which is returned to the client.

#### Why the recommendation was dropped - the in-tree answer

`test_error_sanitization.py` already contains a case with the id `pil-image-repr`, asserting that the PIL form is scrubbed. Its input is a hand-written **lowercase** literal that PIL never emits. I ran both halves against the same object class: the test passes, and a real `repr()` leaks.

That is why a documented, accepted recommendation looked already-covered and was never implemented. Any fix must include a test built from a real `repr()`, not from a literal.

#### Related PRs inspected at diff level (neither closes this)

* **PR #35492** adds `sanitize_message` to a new Responses-API WebSocket path, continuing the CVE-2026-54236 remediation - but does not widen the regex, and its new test also uses a lowercase literal.
* **PR #52120** patches a stale path (`serve/utils/api_utils.py`) and only touches the path regex's backtracking.

### Reachability - default configuration, no authentication

* `api_key` defaults to `None`, and `AuthenticationMiddleware` is installed only when a key is configured - a default `vllm serve` is entirely unauthenticated.
* Even with `--api-key` set, `GUARDED_PREFIX = ("/v1", "/v2", "/inference", "/cohere")`, so `/tokenize` remains unauthenticated.
* Default-on for any multimodal deployment.

### Impact

Information disclosure: a live heap address of the API-server process, to an unauthenticated remote client. CWE-532.

**Scope, stated plainly.** This is information disclosure, not memory corruption. It is Stage 1 of the chain CVE-2026-22778 was rated 9.8 for - an ASLR defeat that made the second stage reliable. **Stage 2 is patched at the current OpenCV floor, so I am not claiming RCE.** The value of the fix is that the disclosure primitive the chain depended on is still live, and the advisory that recommended closing it was never acted on.

**Proposed CVSS v3.1:** `AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N` (5.3, Medium). Severity is proposed, not insisted on.

### Suggested fix

1. Widen the regex to `[0-9a-fA-F]` and add `re.IGNORECASE`, as CVE-2026-54236's advisory recommended.
2. Replace the `pil-image-repr` test case's hand-written literal with a string produced by calling `repr()` on a real `PIL.Image.Image`, so the case cannot pass while the production path leaks. Apply the same to PR #35492's new test.

Item 2 matters as much as item 1 here: the test is the reason the first recommendation was lost.

### Weaknesses of this report, stated upfront

* No GPU was used. Results come from the in-tree GPU-less render server (`vllm launch render`), which runs the real frontend, the real request model and the real `/tokenize` route. Media processing happens entirely in the frontend, so I do not believe the engine's presence changes the result - but I have not confirmed it under a full `vllm serve`.
* The disclosed value is an address, not memory contents. No read primitive is claimed.
* I did not attempt to chain this to anything.

---

*Prepared with AI assistance (Claude), per the repository's contributing guidance on disclosing AI-assisted contributions. All findings were verified by executing vLLM's own code at the commits cited; the analysis and the claims are my own.*

*Reported by Eva Crystal / 0xiviel (XSource Security).*

## Affected versions

- Package: `vllm` (PyPI).
- Affected versions: `>= 0.14.0rc1, < 0.31.0`.
- First stable patched version: `0.31.0`.

Advisory fixed in [#58832](https://github.com/vllm-project/vllm/pull/58832).

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
