Skip to content

Fix GHSA-873f-pvrv-4x83: warn before executing a bundle's config in load()/run() - #9057

Open
garciadias wants to merge 2 commits into
Project-MONAI:devfrom
garciadias:fix/ghsa-873f-pvrv-4x83
Open

Fix GHSA-873f-pvrv-4x83: warn before executing a bundle's config in load()/run()#9057
garciadias wants to merge 2 commits into
Project-MONAI:devfrom
garciadias:fix/ghsa-873f-pvrv-4x83

Conversation

@garciadias

Copy link
Copy Markdown
Collaborator

Summary

Fixes GHSA-873f-pvrv-4x83: https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-873f-pvrv-4x83

monai.bundle.load(), with its default model=None, builds a bundle's network by parsing the bundle's own config through create_workflow(). That parsing resolves any "_target_" value to an importable callable with no allow list, and passes any "$"-prefixed value to Python eval(). monai.bundle.run() reaches the same code path via a caller-supplied config_file. Either way, this means loading or running a bundle whose config you haven't reviewed can execute arbitrary code.

Design

An earlier version of this fix added an opt-in trust_remote_code flag to load(). Per review discussion, that was dropped: MONAI has no mechanism to actually establish whether a bundle is trustworthy (unlike, say, a per-repo "has custom code" check), so a flag like that mostly teaches people to set it once and forget about it, without giving them a real basis to decide.

Instead:

  • create_workflow() — the shared path both load() and run() use to parse a config file — now raises a UserWarning immediately before doing so, spelling out exactly what "_target_"/"$"-expression content can do and linking this advisory.
  • No behavior is blocked. Default behavior is unchanged other than the added warning: load()/run() still parse and execute the config exactly as before.
  • The warning applies uniformly to every caller of create_workflow(), not just load().

Changes

  • monai/bundle/scripts.py: warning added in create_workflow(); docstrings on load(), run(), and create_workflow() updated to describe the risk and point at the advisory.
  • tests/bundle/test_bundle_download.py: TestLoadWarnsOnConfigExecution — default load() warns and still executes the config (no flag needed), explicit model= still skips config parsing entirely (and warns about nothing), and run() warns via the same create_workflow() path.

Test plan

  • python3 -m unittest tests.bundle.test_bundle_download.TestLoadWarnsOnConfigExecution -v
  • Full tests/bundle/test_bundle_download.py, tests/bundle/test_config_parser.py — no new failures vs. dev (remaining failures are pre-existing environment gaps: missing requests/nibabel, one pdb/bdb quirk)

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 674cf292-8024-4c02-83dd-3beba350431d

📥 Commits

Reviewing files that changed from the base of the PR and between 6b5fa64 and b079e79.

📒 Files selected for processing (1)
  • tests/bundle/test_bundle_download.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/bundle/test_bundle_download.py

📝 Walkthrough

Walkthrough

The bundle APIs document that configuration parsing can resolve importable callables and evaluate $ expressions with Python eval(). create_workflow() emits a UserWarning when it receives a configuration file. Tests cover warning emission, expression execution, explicit-model behavior, and run() processing.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the security advisory and the warning added to bundle configuration execution in load() and run().
Description check ✅ Passed The description explains the security issue, design, code changes, behavior, and test results, although it omits the repository template headings and checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/bundle/test_bundle_download.py (1)

509-550: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add docstrings for the new methods.

Document _stage_malicious_bundle with its arguments and return value. Document each new test method with its asserted behavior.

As per path instructions, “Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bundle/test_bundle_download.py` around lines 509 - 550, Add
Google-style docstrings to _stage_malicious_bundle describing tempdir, marker,
and the returned bundle name, and to test_default_warns_and_executes_config,
test_explicit_model_skips_config_parsing, and test_run_warns_on_config_execution
describing each test’s asserted behavior. Include raised-exception details where
applicable.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/bundle/test_bundle_download.py`:
- Around line 532-538: Update test_explicit_model_skips_config_parsing to import
warnings and wrap the load() call in warnings.catch_warnings() with UserWarning
configured as an error, ensuring the explicit-model path remains warning-free
while preserving the existing marker assertion.

---

Nitpick comments:
In `@tests/bundle/test_bundle_download.py`:
- Around line 509-550: Add Google-style docstrings to _stage_malicious_bundle
describing tempdir, marker, and the returned bundle name, and to
test_default_warns_and_executes_config,
test_explicit_model_skips_config_parsing, and test_run_warns_on_config_execution
describing each test’s asserted behavior. Include raised-exception details where
applicable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c43268e0-5488-4668-b5b2-04b26161a216

📥 Commits

Reviewing files that changed from the base of the PR and between 87060c4 and 35587e0.

📒 Files selected for processing (2)
  • monai/bundle/scripts.py
  • tests/bundle/test_bundle_download.py

Comment thread tests/bundle/test_bundle_download.py
@garciadias
garciadias force-pushed the fix/ghsa-873f-pvrv-4x83 branch 2 times, most recently from 4b1138e to 95c1dce Compare August 11, 2026 09:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/bundle/test_bundle_download.py (2)

509-518: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Google-style docstrings to the new definitions.

_stage_malicious_bundle() and the three test methods have no method docstrings. Document tempdir, marker, the returned bundle name, and each test's behavior.

As per path instructions, definitions in **/*.py should have Google-style docstrings that describe variables, return values, and raised exceptions.

Also applies to: 520-530, 532-538, 540-551

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bundle/test_bundle_download.py` around lines 509 - 518, Add
Google-style docstrings to _stage_malicious_bundle and the three newly added
test methods. Document tempdir and marker in Args, state that the helper returns
the staged bundle name, and describe each test’s behavior; include Returns or
Raises sections where applicable without changing test logic.

Source: Path instructions


524-529: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the warning message, not only its type.

assertWarns(UserWarning) can pass for an unrelated warning. Capture the warning and assert stable text for executable configuration and GHSA-873f-pvrv-4x83 in both paths.

Proposed fix
-            with self.assertWarns(UserWarning):
+            with self.assertWarns(UserWarning) as warning:
                 with self.assertRaises(AttributeError):
                     load(...)
+            self.assertIn("GHSA-873f-pvrv-4x83", str(warning.warning))

Also applies to: 546-550

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bundle/test_bundle_download.py` around lines 524 - 529, Update both
warning assertions around the malicious bundle load calls to capture the emitted
warning object, rather than checking only UserWarning. Assert that each warning
message contains stable references to executable configuration and
GHSA-873f-pvrv-4x83, while preserving the existing exception assertions and load
calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/bundle/test_bundle_download.py`:
- Around line 515-517: Update the malicious payload construction in the bundle
download tests at both occurrences to avoid shell parsing: embed the marker path
using a representation-safe escaping method and invoke a direct Python
file-write operation instead of os.system. Preserve the test’s expected marker
content while ensuring paths with spaces, shell characters, or Windows
backslashes remain valid.

---

Nitpick comments:
In `@tests/bundle/test_bundle_download.py`:
- Around line 509-518: Add Google-style docstrings to _stage_malicious_bundle
and the three newly added test methods. Document tempdir and marker in Args,
state that the helper returns the staged bundle name, and describe each test’s
behavior; include Returns or Raises sections where applicable without changing
test logic.
- Around line 524-529: Update both warning assertions around the malicious
bundle load calls to capture the emitted warning object, rather than checking
only UserWarning. Assert that each warning message contains stable references to
executable configuration and GHSA-873f-pvrv-4x83, while preserving the existing
exception assertions and load calls.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d296833d-0f0d-4e6e-a75b-4bd86a405b76

📥 Commits

Reviewing files that changed from the base of the PR and between 35587e0 and 4b1138e.

📒 Files selected for processing (1)
  • tests/bundle/test_bundle_download.py

Comment thread tests/bundle/test_bundle_download.py Outdated
Comment on lines +515 to +517
malicious_config = {"network_def": f"$__import__('os').system('echo pwned > {marker}')", "initialize": []}
with open(os.path.join(bundle_root, "configs", "train.json"), "w") as f:
json.dump(malicious_config, f)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the marker write independent of shell quoting.

Both payloads insert marker into an unquoted os.system() command. A temporary directory with spaces or shell characters can write to the wrong path. Windows backslashes can also make the generated Python expression invalid. Use a representation-escaped path and a direct Python write.

Proposed fix
-        malicious_config = {"network_def": f"$__import__('os').system('echo pwned > {marker}')", "initialize": []}
+        malicious_config = {
+            "network_def": f"$__import__('pathlib').Path({marker!r}).write_text('pwned')",
+            "initialize": [],
+        }
...
-                json.dump({"initialize": [f"$__import__('os').system('echo pwned > {marker}')"]}, f)
+                json.dump(
+                    {"initialize": [f"$__import__('pathlib').Path({marker!r}).write_text('pwned')"]},
+                    f,
+                )

Also applies to: 545-545

🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 515-515: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(os.path.join(bundle_root, "configs", "train.json"), "w")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bundle/test_bundle_download.py` around lines 515 - 517, Update the
malicious payload construction in the bundle download tests at both occurrences
to avoid shell parsing: embed the marker path using a representation-safe
escaping method and invoke a direct Python file-write operation instead of
os.system. Preserve the test’s expected marker content while ensuring paths with
spaces, shell characters, or Windows backslashes remain valid.

monai.bundle.load(), with its default model=None, builds a bundle's
network by parsing the bundle's own config through create_workflow().
That parsing resolves any "_target_" value to an importable callable
with no allow list, and passes any "$"-prefixed value to Python
eval(). monai.bundle.run() reaches the same path via a caller-supplied
config_file. Either way, loading or running a bundle whose config
hasn't been reviewed can execute arbitrary code.

create_workflow() -- the shared path both load() and run() use to
parse a config file -- now raises a UserWarning immediately before
doing so, describing what "_target_"/"$"-expression content can do
and linking the advisory. This applies uniformly to every caller of
create_workflow(), not just load(). No behavior is blocked: the config
is still parsed and executed exactly as before, just with a warning
first. An earlier version of this fix added an opt-in
trust_remote_code flag to load(), but that was dropped after review:
MONAI has no way to establish whether a bundle is actually
trustworthy, so a flag like that would only teach callers to set it
once and forget about it.

Update docstrings on load(), run(), and create_workflow() to describe
the risk and point at the advisory.

Add TestLoadWarnsOnConfigExecution to tests/bundle/test_bundle_download.py:
default load() warns and still executes the config, explicit model=
skips config parsing entirely and warns about nothing, and run() warns
via the same create_workflow() path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: R. Garcia-Dias <rafaelagd@gmail.com>
@garciadias
garciadias force-pushed the fix/ghsa-873f-pvrv-4x83 branch from 95c1dce to 6b5fa64 Compare August 11, 2026 10:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/bundle/test_bundle_download.py (1)

499-512: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add Google-style docstrings to the new methods.

Document _stage_malicious_bundle() parameters and return value. Add concise docstrings to each test method.

  • tests/bundle/test_bundle_download.py#L499-L512: document tempdir, marker, and the returned bundle name.
  • tests/bundle/test_bundle_download.py#L514-L524: document the default load() warning and execution test.
  • tests/bundle/test_bundle_download.py#L526-L532: document the explicit-model parsing bypass test.
  • tests/bundle/test_bundle_download.py#L534-L546: document the run() warning and execution test.

As per path instructions, “Docstrings should be present for all definition which describe each variable, return value, and raised exception in the appropriate section of the Google-style of docstrings.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bundle/test_bundle_download.py` around lines 499 - 512, Add concise
Google-style docstrings to _stage_malicious_bundle and each affected test method
in tests/bundle/test_bundle_download.py:499-512, 514-524, 526-532, and 534-546.
Document tempdir, marker, and the returned bundle name with Args and Returns
sections; document each test’s covered behavior, including the default load()
warning/execution, explicit-model parsing bypass, and run() warning/execution,
adding Raises only if applicable.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/bundle/test_bundle_download.py`:
- Around line 518-523: Update both warning assertions in
tests/bundle/test_bundle_download.py at lines 518-523 and 541-545 to use
assertWarnsRegex(UserWarning, r"GHSA-873f-pvrv-4x83") instead of
assertWarns(UserWarning), while preserving the existing exception assertions and
test flow.

---

Nitpick comments:
In `@tests/bundle/test_bundle_download.py`:
- Around line 499-512: Add concise Google-style docstrings to
_stage_malicious_bundle and each affected test method in
tests/bundle/test_bundle_download.py:499-512, 514-524, 526-532, and 534-546.
Document tempdir, marker, and the returned bundle name with Args and Returns
sections; document each test’s covered behavior, including the default load()
warning/execution, explicit-model parsing bypass, and run() warning/execution,
adding Raises only if applicable.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4df8d078-b465-4202-a7a2-86a016fb3a2c

📥 Commits

Reviewing files that changed from the base of the PR and between 4b1138e and 6b5fa64.

📒 Files selected for processing (2)
  • monai/bundle/scripts.py
  • tests/bundle/test_bundle_download.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • monai/bundle/scripts.py

Comment thread tests/bundle/test_bundle_download.py Outdated
ericspod
ericspod previously approved these changes Aug 11, 2026

@ericspod ericspod left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, the Coderabbit comments look worthwhile however. I can rereview if you want to address

garciadias added a commit to garciadias/MONAI that referenced this pull request Aug 11, 2026
- tests/bundle/test_bundle_download.py: assert the advisory-specific
  warning message (GHSA-873f-pvrv-4x83) instead of any UserWarning in
  test_default_warns_and_executes_config and
  test_run_warns_on_config_execution
- tests/bundle/test_bundle_download.py: fail
  test_explicit_model_skips_config_parsing if load() emits a
  UserWarning, enforcing that the explicit-model path never parses
  the bundle config
- tests/bundle/test_bundle_download.py: assert the advisory-specific
  warning message (GHSA-873f-pvrv-4x83) instead of any UserWarning in
  test_default_warns_and_executes_config and
  test_run_warns_on_config_execution
- tests/bundle/test_bundle_download.py: fail
  test_explicit_model_skips_config_parsing if load() emits a
  UserWarning, enforcing that the explicit-model path never parses
  the bundle config

Signed-off-by: R. Garcia-Dias <rafaelagd@gmail.com>
@garciadias
garciadias force-pushed the fix/ghsa-873f-pvrv-4x83 branch from b079e79 to 9b51a65 Compare August 11, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants