CVE
CVE-2026-112346, CVE-2026-93328, CVE-2026-85297, CVE-2026-115915, CVE-2026-115912, CVE-2026-115918
CWE
CWE-502, CWE-306, CWE-22, CWE-862, CWE-706
Affected Surface
- PyPI `vllm` (`vllm-project/vllm`) versions `< 0.32.0` (`CVE-2026-115915`, CVSS 8.1; `CVE-2026-115912`, CVSS 7.5; `CVE-2026-115918`, CVSS 6.5)
- OpenHands Software Agent SDK (`OpenHands/software-agent-sdk`, PyPI `openhands-workspace` and `openhands-sdk`) versions `< 1.52.0` (`CVE-2026-112346`, CVSS 9.8 Critical)
- Google Cloud MCP Toolbox for Databases (`googleapis/mcp-toolbox`) versions `1.2.0` through `1.4.0` (`CVE-2026-93328`, CVSS 9.3 Critical)
- Google Cloud Run MCP (`GoogleCloudPlatform/cloud-run-mcp`, npm `@google-cloud/cloud-run-mcp`) versions `< 1.11.0` (`CVE-2026-85297`, CVSS 7.7 High)
TL;DR
On 26 October 2026, six vulnerabilities were published across three widely deployed AI inference and agent-tooling stacks: vLLM (v0.32.0), OpenHands Software Agent SDK (v1.52.0), and two Google Cloud MCP servers (googleapis/mcp-toolbox and GoogleCloudPlatform/cloud-run-mcp):
- vLLM
< 0.32.0(CVE-2026-115912,CVE-2026-115915,CVE-2026-115918):CVE-2026-115912(CVSS 7.5 High, RCE): In scale-out deployments with shared-memory multimodal caching (ShmObjectStoreReceiverCache) enabled, worker processes trusted rawaddressandmonotonic_idintegers inside caller-suppliedfeatures.kwargs_dataand passed the pointed-to shared-memory slice directly topickle.loads(), enabling arbitrary Python code execution even when insecure serialization was disabled.CVE-2026-115915(CVSS 8.1 High):ImageMediaIOpassed untrusted image bytes toPIL.Image.open(BytesIO(data))without aformatsallowlist (ignoring the declared MIME label), allowing an attacker to submit an Encapsulated PostScript (%!PS-Adobe-3.0) payload that Pillow dispatches to externalghostscript(CVE-2024-29510RCE) or vulnerable Pillow decoders (CVE-2026-59203).CVE-2026-115918(CVSS 6.5):/v1/responses(_create_responses) skippedvalidate_chat_template(), allowing callers to passchat_template_kwargs["chat_template"]even whentrust_request_chat_templatewasFalseand lock up render workers with unbounded Jinja loops.
- OpenHands Software Agent SDK
< 1.52.0(CVE-2026-112346, CVSS 9.8 Critical):DockerWorkspace._start_container()published the sandbox agent server and VS Code ports on all network interfaces (-p {host_port}:8000and-p {host_port + 1}:8001) while hardcodingself.api_key = Noneinstead of readingOH_SESSION_API_KEYS_0/SESSION_API_KEY. Any attacker on a reachable network could send unauthenticated HTTP or WebSocket requests to execute shell commands asrootinside the workspace container and read secrets from the mounted workspace. - Google Cloud MCP servers (
CVE-2026-93328, CVSS 9.3 Critical, andCVE-2026-85297, CVSS 7.7 High):- In
googleapis/mcp-toolbox(1.2.0through1.4.0),DownloadObjectandUploadObjectininternal/sources/cloudstorage/cloudstorage.goaccepted any local filesystem path (source/destination) and any Google Cloud Storagebucketname in tool parameters, letting an unauthenticated caller or prompt-injected agent exfiltrate host files (such as/etc/passwdor credentials) to an external GCS bucket or overwrite local files. Crucially, the newallowedBucketsandallowedLocalRootsfields default to allow-all if omitted fromtools.yaml, so upgrading without updating your YAML configuration leaves the server exposed. - In
GoogleCloudPlatform/cloud-run-mcp(< 1.11.0),ensureStorageBucketExists()inlib/cloud-api/storage.jsused a predictable<projectId>_cloudbuildGCS bucket name without verifyingmetadata.projectNumberagainst the caller’s GCP project number, allowing an attacker who pre-claimed that bucket name to intercept uploaded source tarballs or swap code before Cloud Build executed it.
- In
Affected packages and projects
| Ecosystem | Package / repository | CVE / Advisory | Severity | Affected versions | Fixed version | Vulnerable component |
|---|---|---|---|---|---|---|
| PyPI | openhands-workspace, openhands-sdk (OpenHands/software-agent-sdk) | CVE-2026-112346 | CVSS 9.8 Critical | < 1.52.0 | 1.52.0 (1.58.2 current) | openhands-workspace/openhands/workspace/docker/workspace.py (_start_container) |
| Go / GitHub | github.com/googleapis/mcp-toolbox | CVE-2026-93328 | CVSS 9.3 Critical | >= 1.2.0, <= 1.4.0 | PR #3454 + set allowedBuckets & allowedLocalRoots | internal/sources/cloudstorage/cloudstorage.go (DownloadObject, UploadObject) |
| npm / GitHub | @google-cloud/cloud-run-mcp (GoogleCloudPlatform/cloud-run-mcp) | CVE-2026-85297 | CVSS 7.7 High | < 1.11.0 | 1.11.0 | lib/cloud-api/storage.js (ensureStorageBucketExists) |
| PyPI | vllm (vllm-project/vllm) | CVE-2026-115915 (GHSA-h3rc-j82p-2c3h) | CVSS 8.1 High | < 0.32.0 | 0.32.0 | vllm/multimodal/image.py (ImageMediaIO / open_image) |
| PyPI | vllm (vllm-project/vllm) | CVE-2026-115912 (GHSA-6757-hggm-hp2w) | CVSS 7.5 High | < 0.32.0 | 0.32.0 | vllm/distributed/device_communicators/shm_object_storage.py and vllm/multimodal/cache/shm.py |
| PyPI | vllm (vllm-project/vllm) | CVE-2026-115918 (GHSA-x567-49g5-p4cf) | CVSS 6.5 Medium | >= 0.16.0, < 0.32.0 | 0.32.0 | vllm/entrypoints/openai/responses/serving.py and vllm/renderers/hf.py |
Technical analysis
1. vLLM 0.32.0: forged shared-memory pickle.loads handles (CVE-2026-115912) and Pillow EPS dispatch (CVE-2026-115915)
In scale-out vLLM deployments (such as disaggregated prefill/decode or token-in-token-out serving with shared-memory multimodal caching enabled), the sender process (P0) writes preprocessed multimodal tensors into a SingleWriterShmRingBuffer (vllm/distributed/device_communicators/shm_object_storage.py) and passes a handle { "address": addr_elem, "monotonic_id": id_elem } inside MultiModalKwargsItem.
Before commit 3d5625cc3c, ShmObjectStoreReceiverCache.get_and_update_item() in vllm/multimodal/cache/shm.py accepted any address and monotonic_id present in the request’s features.kwargs_data without verifying that the writer had actually issued that handle:
# vllm/multimodal/cache/shm.py (vulnerable, < 0.32.0)
if "address" in mm_item:
address = cast(int, mm_item["address"].data)
monotonic_id = cast(int, mm_item["monotonic_id"].data)
return self._shm_cache.get(address, monotonic_id)
Inside SingleWriterShmObjectStorage.get(address, monotonic_id), the reader sliced self.shared_memory.buf[buf_idx + self.MD_SIZE : buf_idx + size], checked only that the 4-byte monotonic_id at buf_idx matched the caller’s monotonic_id, and passed data_view[self.flag_bytes :] to self.ser_de.deserialize() (pickle.loads()). Furthermore, SingleWriterShmRingBuffer.clear() reset monotonic_id_start and monotonic_id_end back to 0. An authenticated caller who placed controlled bytes inside the shared-memory ring buffer (for example, inside an image or tensor payload) could forge an address and monotonic_id pointing into the middle of their own payload and make worker processes deserialize arbitrary pickle opcodes.
The fix in 3d5625cc3c reserves the first 32 bytes of the shared memory segment (AUTH_SECRET_BYTES = 32) for a random secrets.token_bytes(32) key generated by the writer, signs every issued (key, address, monotonic_id) tuple with HMAC-SHA256 (_make_signature), and verifies the signature using hmac.compare_digest in both serve_tokens() (_validate_mm_cache_handles) and SingleWriterShmObjectStorage.get() before dereferencing address.
In parallel, commit 710ac56e69 fixes CVE-2026-115915 in vllm/multimodal/image.py. Previously, ImageMediaIO called Image.open(BytesIO(data)) without a formats restriction, so submitting %!PS-Adobe-3.0 EPSF-3.0 bytes (even labeled as image/png in a base64 data URI) caused Pillow to invoke its EPS plugin and spawn gs (Ghostscript). v0.32.0 routes image loading through open_image() with an explicit ALLOWED_IMAGE_FORMATS set that excludes EPS, WMF, BUFR, GRIB, HDF5, and MPEG. Finally, commit cff08b461e fixes CVE-2026-115918 by calling self.online_renderer.validate_chat_template() in vllm/entrypoints/openai/responses/serving.py, adding a VLLM_CHAT_TEMPLATE_RENDER_TIMEOUT (default 30.0s) with a _SwappableExecutor thread-pool replacement in Python, and setting env.set_fuel(Some(500_000)) in the Rust minijinja chat renderer.
2. CVE-2026-112346: unauthenticated 0.0.0.0 Docker port binding in OpenHands Software Agent SDK
When DockerWorkspace._start_container() in openhands-workspace/openhands/workspace/docker/workspace.py launched a sandbox agent container, it published container port 8000 (the agent HTTP/WebSocket control server) and port 8001 (VS Code) on all host interfaces without 127.0.0.1: and set self.api_key to None:
--- a/openhands-workspace/openhands/workspace/docker/workspace.py
+++ b/openhands-workspace/openhands/workspace/docker/workspace.py
@@ -215,11 +215,11 @@ def _start_container(self, image: str, context: Any) -> None:
- ports = ["-p", f"{self.host_port}:8000"]
+ ports = ["-p", f"127.0.0.1:{self.host_port}:8000"]
if self.extra_ports:
ports += [
"-p",
- f"{self.host_port + 1}:8001", # VSCode
+ f"127.0.0.1:{self.host_port + 1}:8001", # VSCode
]
flags += ports
...
if not self.host:
object.__setattr__(self, "host", f"http://127.0.0.1:{self.host_port}")
- object.__setattr__(self, "api_key", None)
+ session_api_key = os.environ.get(
+ "OH_SESSION_API_KEYS_0", os.environ.get("SESSION_API_KEY")
+ )
+ object.__setattr__(self, "api_key", session_api_key)
Because Docker’s -p <host_port>:<container_port> syntax binds 0.0.0.0:<host_port> and inserts DOCKER iptables rules that bypass ufw / firewalld input chains on Linux hosts, any peer on the local network or VPC could connect to http://<host>:<host_port> and execute shell commands as root inside the agent workspace container. Commit 7b66584ff8 (v1.52.0) binds the published ports to 127.0.0.1 and forwards OH_SESSION_API_KEYS_0 / SESSION_API_KEY.
3. CVE-2026-93328 and CVE-2026-85297 in Google Cloud MCP servers
Two advisories published on 26 October highlight how MCP tool servers can turn cloud storage APIs into local file-read/write or build-hijack primitives:
- Unrestricted local path and bucket access in
googleapis/mcp-toolbox(CVE-2026-93328, CVSS 9.3 Critical): Ininternal/sources/cloudstorage/cloudstorage.go,UploadObject(ctx, bucket, object, source, contentType)calledos.Open(source)and uploaded the file tos.client.Bucket(bucket), whileDownloadObject(ctx, bucket, object, destination, overwrite)wrote the GCS object todestination. Neither method checked which GCS bucket was targeted or restrictedsource/destinationto a workspace directory. PR#3454addsvalidateBucket()andvalidateLocalPath():
Notice that if// internal/sources/cloudstorage/cloudstorage.go (PR #3454) func (s *Source) validateBucket(bucket string) error { if len(s.AllowedBuckets) == 0 { return nil } for _, b := range s.AllowedBuckets { if b == bucket { return nil } } return fmt.Errorf("bucket %q is not allowed by source %q configuration", bucket, s.Name) }allowedBucketsorallowedLocalRootsis left empty (len == 0), validation returnsnil(allow all). Every deployment using thecloud-storagesource must explicitly configure both fields in its YAML configuration:kind: source name: my-gcs-source type: "cloud-storage" project: "my-project-id" allowedBuckets: - "my-app-bucket" allowedLocalRoots: - "/workspace" - Cloud Build GCS bucket squatting in
GoogleCloudPlatform/cloud-run-mcp(CVE-2026-85297, CVSS 7.7 High): When deploying source code to Cloud Run via Cloud Build (deployWithZip,deployWithBuild,composeBuild),@google-cloud/cloud-run-mcpcalledensureStorageBucketExists(projectId, "<projectId>_cloudbuild", region, ...)inlib/cloud-api/storage.js. Because GCS bucket names live in a single global namespace, an attacker who guessed or knew a target’sprojectIdcould pre-create<projectId>_cloudbuildin their own GCP project with permissive write/read IAM bindings. Beforev1.11.0(PR#300),ensureStorageBucketExists()only checkedbucket.exists()and immediately uploaded the user’s proprietary source tarball to that bucket before triggering Cloud Build.v1.11.0queriesgetProjectNumber(projectId)and compares it againstbucket.getMetadata().projectNumber, falling back to a randomized<bucketName>-<8-char-hex>bucket (crypto.randomBytes(4).toString('hex')) if ownership does not match or ifbucket.exists()returns403.
Detection and remediation
# 1. Check Python environments and lockfiles for vulnerable vllm (< 0.32.0) and openhands (< 1.52.0)
pip show vllm openhands-sdk openhands-workspace 2>/dev/null || true
rg -n '"?(vllm|openhands-sdk|openhands-workspace)"?\s*([~=!<>]=|:)' \
requirements*.txt pyproject.toml uv.lock poetry.lock Pipfile.lock 2>/dev/null
# 2. Check running Docker containers for OpenHands sandbox ports bound to 0.0.0.0:8000/8001
docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' | grep -E '0\.0\.0\.0:[0-9]+->800[01]/tcp' || true
# 3. Audit MCP configs and YAML files for cloud-run-mcp and mcp-toolbox cloud-storage sources
rg -n 'cloud-run-mcp|mcp-toolbox|type:\s*"?cloud-storage"?' \
.cursor/mcp.json claude_desktop_config.json package.json tools.yaml *.y*ml 2>/dev/null
- Upgrade
vllmto0.32.0or later (pip install -U "vllm>=0.32.0"). - Upgrade
openhands-sdkandopenhands-workspaceto1.52.0or later (1.58.2is current on PyPI), set a non-emptyOH_SESSION_API_KEYS_0secret, and stop any running OpenHands workspace containers that publish0.0.0.0:<port>->8000/tcp. - Upgrade
@google-cloud/cloud-run-mcpto1.11.0or later, verify in the GCP Console that your<projectId>_cloudbuildbucket is owned by your ownprojectNumber, and ingoogleapis/mcp-toolboxconfigurations usingtype: "cloud-storage", add explicitallowedBucketsandallowedLocalRootslists.
Timeline
- 2026-06-18:
googleapis/mcp-toolboxmerges PR#3454addingallowedBucketsandallowedLocalRootsto thecloud-storagesource. - 2026-08-06 to 2026-09-28:
GoogleCloudPlatform/cloud-run-mcpmerges PR#300(“fix: check bucket ownership”) and releasesv1.11.0on 28 September 2026. - 2026-09-30 to 2026-10-05:
vllm-project/vllmmerges commits3d5625cc3c(CVE-2026-115912),cff08b461e(CVE-2026-115918), and710ac56e69(CVE-2026-115915) ahead of thev0.32.0release. - 2026-10-03:
OpenHands/software-agent-sdkmerges commit7b66584ff8(CVE-2026-112346) inv1.52.0. - 2026-10-26:
CVE-2026-112346(CVSS 9.8),CVE-2026-93328(CVSS 9.3),CVE-2026-85297(CVSS 7.7),CVE-2026-115915(CVSS 8.1),CVE-2026-115912(CVSS 7.5), andCVE-2026-115918(CVSS 6.5) are published to NVD.
From research to remediation
Check whether this pattern exists in your codebase
Turn this research into a remediation workflow. Scan dependencies and package manifests for similar supply-chain risk, then prioritize fixes with reachability context.
References
- vllm-project/vllm commit 3d5625cc3c: HMAC-sign shared-memory multimodal cache handles (CVE-2026-115912)
- vllm-project/vllm commit 710ac56e69: restrict ImageMediaIO Pillow decoder formats (CVE-2026-115915)
- vllm-project/vllm commit cff08b461e: enforce trust_request_chat_template on /v1/responses and add render fuel/timeout (CVE-2026-115918)
- OpenHands/software-agent-sdk commit 7b66584ff8: bind DockerWorkspace ports to 127.0.0.1 and resolve OH_SESSION_API_KEYS_0 (CVE-2026-112346)
- googleapis/mcp-toolbox PR #3454: add allowedBuckets and allowedLocalRoots to cloud-storage source (CVE-2026-93328)
- GoogleCloudPlatform/cloud-run-mcp PR #300: check GCS bucket ownership before uploading Cloud Build source (CVE-2026-85297)
- NVD: CVE-2026-112346, CVE-2026-93328, CVE-2026-85297, CVE-2026-115915, and CVE-2026-115912