Skip to content

Commit 97843f8

Browse files
garciadiasericspod
andauthored
Warn before instantiating _target_ from algo_object.json (#9085)
### Description `algo_from_json` resolves the `_target_` value from an `algo_object.json` to an importable callable and invokes it, and adds file-influenced directories to `sys.path`. Emit a trust-boundary warning before instantiation so users only load trusted files (GHSA-2wx3-8x3w-r8qv). ### Types of changes - [x] Non-breaking change - [x] New tests added to cover the changes. --------- Signed-off-by: R. Garcia-Dias <rafaelagd@gmail.com> Co-authored-by: Eric Kerfoot <17726042+ericspod@users.noreply.github.com>
1 parent 9ea04d4 commit 97843f8

2 files changed

Lines changed: 43 additions & 0 deletions

File tree

monai/auto3dseg/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,14 @@ def algo_from_json(filename: str, template_path: PathLike | None = None, **kwarg
493493
if state_template_path:
494494
algo_config["template_path"] = state_template_path
495495

496+
warnings.warn(
497+
f"Loading {filename}: the file's `_target_` value is resolved to an imported callable and "
498+
"invoked, and template directories from the file may be added to `sys.path`; only load "
499+
"algo_object.json files from a source you trust "
500+
"(see https://github.com/Project-MONAI/MONAI/security/advisories/GHSA-2wx3-8x3w-r8qv).",
501+
stacklevel=2,
502+
)
503+
496504
parser = ConfigParser(algo_config)
497505
algo = parser.get_parsed_content()
498506
used_template_path = path

tests/apps/test_auto3dseg.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111

1212
from __future__ import annotations
1313

14+
import json
1415
import os
1516
import tempfile
1617
import unittest
18+
import warnings
1719
from copy import deepcopy
1820
from numbers import Number
1921

@@ -36,6 +38,7 @@
3638
SampleOperations,
3739
SegSummarizer,
3840
SummaryOperations,
41+
algo_from_json,
3942
datafold_read,
4043
verify_report_format,
4144
)
@@ -177,6 +180,20 @@ def __call__(self, data):
177180
return d
178181

179182

183+
class _DummyAlgo:
184+
"""Minimal stand-in for an Auto3DSeg Algo object used in warning tests."""
185+
186+
def __init__(self) -> None:
187+
self.template_path: str | None = None
188+
self.output_path = os.getcwd()
189+
190+
def load_state_dict(self, state: dict) -> None:
191+
pass
192+
193+
def get_output_path(self) -> str:
194+
return self.output_path
195+
196+
180197
class TestDataAnalyzer(unittest.TestCase):
181198
def setUp(self):
182199
self.test_dir = tempfile.TemporaryDirectory()
@@ -619,5 +636,23 @@ def tearDown(self) -> None:
619636
self.test_dir.cleanup()
620637

621638

639+
class TestAlgoFromJsonSecurityWarning(unittest.TestCase):
640+
def test_warns_about_untrusted_target(self) -> None:
641+
with tempfile.TemporaryDirectory() as tmpdir:
642+
algo_file = os.path.join(tmpdir, "algo_object.json")
643+
with open(algo_file, "w", encoding="utf-8") as f:
644+
json.dump({"_target_": f"{__name__}._DummyAlgo"}, f)
645+
646+
with warnings.catch_warnings(record=True) as caught:
647+
warnings.simplefilter("always")
648+
algo_from_json(algo_file)
649+
650+
messages = [str(w.message) for w in caught]
651+
self.assertTrue(
652+
any("algo_object.json" in msg and "trust" in msg for msg in messages),
653+
f"Keywords 'algo_object.json' and 'trust' not found in warning messages: {messages}",
654+
)
655+
656+
622657
if __name__ == "__main__":
623658
unittest.main()

0 commit comments

Comments
 (0)