AI and data stack advisories

Severe, 6 weeks1496Projects326

1496 severe, 6 weeks · 326 projects

RayGHSA-hhrp-gw25-jr43

Ray: unsafe deserialization

Ray

CVE-2026-57516 · Published Jul 24, 2026 · updated Sep 10, 2026

High8.8
Fix: upgrade to 2.56.0 or later
GitHub advisory

Summary

ray.data.read_webdataset(paths=...) is a @PublicAPI(stability="alpha")

reader for WebDataset-format TAR files. Its default decoder=True invokes

_default_decoder on every sample's keys, which routes file extension to a

decoder by extension. Two of those branches deserialize attacker-controlled

bytes with no validation:

  • .pickle / .pkl -> pickle.loads(value)
  • .pt / .pth -> torch.load(io.BytesIO(value), weights_only=False)

Both fire during a standard ray.data.read_webdataset(...).take_all() /

.iter_batches() call. No flags, no opt-in, no environment variable.

An attacker who can supply a TAR (via S3 share, HuggingFace Hub mirror,

email attachment, model-zoo, or any HTTP URL the user passes to

read_webdataset) achieves arbitrary code execution in the calling

Ray process at schema-sample time, before row data is consumed.

This is the same class of bug as GHSA-mw35-8rx3-xf9r (Parquet Arrow

Extension Type cloudpickle deserialization, patched in 2.55.0): standard

data-loading API, attacker-controlled file format, deserialization gadget

invoked transparently. The 2.55.0 patch addressed

tensor_extensions/arrow.py:_deserialize_with_fallback and made cloudpickle

opt-in via RAY_DATA_AUTOLOAD_CLOUDPICKLE_TENSOR_METADATA=1. The

WebDataset path is a different code site and was not touched.

Vulnerable code (HEAD a157d4d)...

Affected versions

PackageAffectedFixed in
ray
PyPI
< 2.56.02.56.0
Details and references

## Summary `ray.data.read_webdataset(paths=...)` is a `@PublicAPI(stability="alpha")` reader for WebDataset-format TAR files. Its default `decoder=True` invokes `_default_decoder` on every sample's keys, which routes file extension to a decoder by extension. Two of those branches deserialize attacker-controlled bytes with no validation: - `.pickle` / `.pkl` -> `pickle.loads(value)` - `.pt` / `.pth` -> `torch.load(io.BytesIO(value), weights_only=False)` Both fire during a standard `ray.data.read_webdataset(...).take_all()` / `.iter_batches()` call. No flags, no opt-in, no environment variable. An attacker who can supply a TAR (via S3 share, HuggingFace Hub mirror, email attachment, model-zoo, or any HTTP URL the user passes to `read_webdataset`) achieves arbitrary code execution in the calling Ray process at schema-sample time, before row data is consumed. This is the same class of bug as GHSA-mw35-8rx3-xf9r (Parquet Arrow Extension Type cloudpickle deserialization, patched in 2.55.0): standard data-loading API, attacker-controlled file format, deserialization gadget invoked transparently. The 2.55.0 patch addressed `tensor_extensions/arrow.py:_deserialize_with_fallback` and made cloudpickle opt-in via `RAY_DATA_AUTOLOAD_CLOUDPICKLE_TENSOR_METADATA=1`. The WebDataset path is a different code site and was not touched. ## Vulnerable code (HEAD `a157d4d`) `python/ray/data/_internal/datasource/webdataset_datasource.py` lines 175-225, the `_default_decoder` function: ```python def _default_decoder(sample, format=True): sample = dict(sample) for key, value in sample.items(): extension = key.split(".")[-1] ... elif extension in ["pt", "pth"]: import torch # PyTorch 2.6 changed torch.load default weights_only=True, which # breaks loading general Python objects previously serialized for # WebDataset .pt payloads. sample[key] = torch.load(io.BytesIO(value), weights_only=False) # line 219 elif extension in ["pickle", "pkl"]: import pickle sample[key] = pickle.loads(value) # line 223 return sample ``` The comment for the `.pt/.pth` branch is itself a security smell: it documents that the maintainer chose `weights_only=False` *to override* PyTorch 2.6's safer default. The comment treats this as a compatibility fix; it functionally re-enables an arbitrary-code-execution path that upstream PyTorch closed. ## Reachability and default-on confirmation `python/ray/data/read_api.py:2289` defines `read_webdataset` with default `decoder=True`: ```python @PublicAPI(stability="alpha") def read_webdataset( paths, *, ... decoder: Optional[Union[bool, str, callable, list]] = True, ... ) -> Dataset: ... datasource = WebDatasetDatasource(paths, decoder=decoder, ...) ``` `WebDatasetDatasource._read_stream` (line 367) calls the decoder unconditionally when not None: ```python for sample in samples: if self.decoder is not None: sample = _apply_list(self.decoder, sample, default=_default_decoder) ``` `True is not None` evaluates True, so the default decoder fires for every invocation that doesn't explicitly pass `decoder=None` (or a custom safe decoder). The documentation does not warn about the behavior. ## End-to-end reproduction Tested on a fresh venv (`pip install ray[data]`) on Linux x86_64. Ray reports `__version__ == "2.55.1"` (the patched-against-GHSA-mw35 release): ```python import io, os, pickle, subprocess, tarfile, tempfile, sys MARKER = "/tmp/ray_webdataset_poc_rce_marker" class Gadget: def __reduce__(self): cmd = (f"/bin/sh -c \"printf 'RCE via ray.data.read_webdataset\\n" f"pid=%s\\nuser=%s\\n' \"$\" \"$(whoami)\" > {MARKER}\"") return (os.system, (cmd,)) with tempfile.NamedTemporaryFile(suffix=".tar", delete=False) as f: tar_path = f.name with tarfile.open(tar_path, "w") as tar:

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
Severity from
GitHub (reviewed advisory)
Weakness
CWE-502, CWE-94
Also known as
CVE-2026-57516, PYSEC-2026-2273

More Ray advisories

All Ray