critical

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):

  1. 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 raw address and monotonic_id integers inside caller-supplied features.kwargs_data and passed the pointed-to shared-memory slice directly to pickle.loads(), enabling arbitrary Python code execution even when insecure serialization was disabled.
    • CVE-2026-115915 (CVSS 8.1 High): ImageMediaIO passed untrusted image bytes to PIL.Image.open(BytesIO(data)) without a formats allowlist (ignoring the declared MIME label), allowing an attacker to submit an Encapsulated PostScript (%!PS-Adobe-3.0) payload that Pillow dispatches to external ghostscript (CVE-2024-29510 RCE) or vulnerable Pillow decoders (CVE-2026-59203).
    • CVE-2026-115918 (CVSS 6.5): /v1/responses (_create_responses) skipped validate_chat_template(), allowing callers to pass chat_template_kwargs["chat_template"] even when trust_request_chat_template was False and lock up render workers with unbounded Jinja loops.
  2. 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}:8000 and -p {host_port + 1}:8001) while hardcoding self.api_key = None instead of reading OH_SESSION_API_KEYS_0 / SESSION_API_KEY. Any attacker on a reachable network could send unauthenticated HTTP or WebSocket requests to execute shell commands as root inside the workspace container and read secrets from the mounted workspace.
  3. Google Cloud MCP servers (CVE-2026-93328, CVSS 9.3 Critical, and CVE-2026-85297, CVSS 7.7 High):
    • In googleapis/mcp-toolbox (1.2.0 through 1.4.0), DownloadObject and UploadObject in internal/sources/cloudstorage/cloudstorage.go accepted any local filesystem path (source / destination) and any Google Cloud Storage bucket name in tool parameters, letting an unauthenticated caller or prompt-injected agent exfiltrate host files (such as /etc/passwd or credentials) to an external GCS bucket or overwrite local files. Crucially, the new allowedBuckets and allowedLocalRoots fields default to allow-all if omitted from tools.yaml, so upgrading without updating your YAML configuration leaves the server exposed.
    • In GoogleCloudPlatform/cloud-run-mcp (< 1.11.0), ensureStorageBucketExists() in lib/cloud-api/storage.js used a predictable <projectId>_cloudbuild GCS bucket name without verifying metadata.projectNumber against 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.

Affected packages and projects

EcosystemPackage / repositoryCVE / AdvisorySeverityAffected versionsFixed versionVulnerable component
PyPIopenhands-workspace, openhands-sdk (OpenHands/software-agent-sdk)CVE-2026-112346CVSS 9.8 Critical< 1.52.01.52.0 (1.58.2 current)openhands-workspace/openhands/workspace/docker/workspace.py (_start_container)
Go / GitHubgithub.com/googleapis/mcp-toolboxCVE-2026-93328CVSS 9.3 Critical>= 1.2.0, <= 1.4.0PR #3454 + set allowedBuckets & allowedLocalRootsinternal/sources/cloudstorage/cloudstorage.go (DownloadObject, UploadObject)
npm / GitHub@google-cloud/cloud-run-mcp (GoogleCloudPlatform/cloud-run-mcp)CVE-2026-85297CVSS 7.7 High< 1.11.01.11.0lib/cloud-api/storage.js (ensureStorageBucketExists)
PyPIvllm (vllm-project/vllm)CVE-2026-115915 (GHSA-h3rc-j82p-2c3h)CVSS 8.1 High< 0.32.00.32.0vllm/multimodal/image.py (ImageMediaIO / open_image)
PyPIvllm (vllm-project/vllm)CVE-2026-115912 (GHSA-6757-hggm-hp2w)CVSS 7.5 High< 0.32.00.32.0vllm/distributed/device_communicators/shm_object_storage.py and vllm/multimodal/cache/shm.py
PyPIvllm (vllm-project/vllm)CVE-2026-115918 (GHSA-x567-49g5-p4cf)CVSS 6.5 Medium>= 0.16.0, < 0.32.00.32.0vllm/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:

  1. Unrestricted local path and bucket access in googleapis/mcp-toolbox (CVE-2026-93328, CVSS 9.3 Critical): In internal/sources/cloudstorage/cloudstorage.go, UploadObject(ctx, bucket, object, source, contentType) called os.Open(source) and uploaded the file to s.client.Bucket(bucket), while DownloadObject(ctx, bucket, object, destination, overwrite) wrote the GCS object to destination. Neither method checked which GCS bucket was targeted or restricted source / destination to a workspace directory. PR #3454 adds validateBucket() and validateLocalPath():
    // 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)
    }
    Notice that if allowedBuckets or allowedLocalRoots is left empty (len == 0), validation returns nil (allow all). Every deployment using the cloud-storage source 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"
  2. 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-mcp called ensureStorageBucketExists(projectId, "<projectId>_cloudbuild", region, ...) in lib/cloud-api/storage.js. Because GCS bucket names live in a single global namespace, an attacker who guessed or knew a target’s projectId could pre-create <projectId>_cloudbuild in their own GCP project with permissive write/read IAM bindings. Before v1.11.0 (PR #300), ensureStorageBucketExists() only checked bucket.exists() and immediately uploaded the user’s proprietary source tarball to that bucket before triggering Cloud Build. v1.11.0 queries getProjectNumber(projectId) and compares it against bucket.getMetadata().projectNumber, falling back to a randomized <bucketName>-<8-char-hex> bucket (crypto.randomBytes(4).toString('hex')) if ownership does not match or if bucket.exists() returns 403.

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
  1. Upgrade vllm to 0.32.0 or later (pip install -U "vllm>=0.32.0").
  2. Upgrade openhands-sdk and openhands-workspace to 1.52.0 or later (1.58.2 is current on PyPI), set a non-empty OH_SESSION_API_KEYS_0 secret, and stop any running OpenHands workspace containers that publish 0.0.0.0:<port>->8000/tcp.
  3. Upgrade @google-cloud/cloud-run-mcp to 1.11.0 or later, verify in the GCP Console that your <projectId>_cloudbuild bucket is owned by your own projectNumber, and in googleapis/mcp-toolbox configurations using type: "cloud-storage", add explicit allowedBuckets and allowedLocalRoots lists.

Timeline

  • 2026-06-18: googleapis/mcp-toolbox merges PR #3454 adding allowedBuckets and allowedLocalRoots to the cloud-storage source.
  • 2026-08-06 to 2026-09-28: GoogleCloudPlatform/cloud-run-mcp merges PR #300 (“fix: check bucket ownership”) and releases v1.11.0 on 28 September 2026.
  • 2026-09-30 to 2026-10-05: vllm-project/vllm merges commits 3d5625cc3c (CVE-2026-115912), cff08b461e (CVE-2026-115918), and 710ac56e69 (CVE-2026-115915) ahead of the v0.32.0 release.
  • 2026-10-03: OpenHands/software-agent-sdk merges commit 7b66584ff8 (CVE-2026-112346) in v1.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), and CVE-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