Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions modelaudit/scanners/xgboost_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
- Known CVE patterns and exploit signatures
"""

import importlib.util
import json
import os
import re
import subprocess
import sys
import tempfile
from typing import Any, ClassVar

from .base import BaseScanner, IssueSeverity, ScanResult
Expand All @@ -42,12 +44,7 @@

def _check_xgboost_available() -> bool:
"""Check if XGBoost package is available."""
try:
import xgboost # noqa: F401

return True
except ImportError:
return False
return importlib.util.find_spec("xgboost") is not None


def _check_ubjson_available() -> bool:
Expand Down Expand Up @@ -610,7 +607,19 @@ def _safe_xgboost_load(self, path: str, result: ScanResult) -> None:
path,
]

proc = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=os.getcwd())
cmd.insert(1, "-I")
env = os.environ.copy()
env.pop("PYTHONPATH", None)

with tempfile.TemporaryDirectory(prefix="modelaudit-xgb-") as safe_cwd:
proc = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout,
cwd=safe_cwd,
env=env,
)

if proc.returncode == 0 and "SUCCESS" in proc.stdout:
result.add_check(
Expand Down
9 changes: 6 additions & 3 deletions tests/scanners/test_xgboost_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -379,12 +379,15 @@ def test_xgboost_loading_windows_path_passed_via_argv(self, mock_subprocess: Moc

loading_scanner._safe_xgboost_load(windows_path, result)

run_args, _ = mock_subprocess.run.call_args
run_args, run_kwargs = mock_subprocess.run.call_args
cmd = run_args[0]
script = cmd[2]
script = cmd[3]
assert cmd[1] == "-I"
assert "sys.argv[1]" in script
assert windows_path not in script
assert cmd[3] == windows_path
assert cmd[4] == windows_path
assert run_kwargs["cwd"] != str(Path.cwd())
assert "PYTHONPATH" not in run_kwargs["env"]
Comment on lines +382 to +390
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add an explicit malicious import-hijack regression test.

These assertions verify invocation wiring, but they don’t prove a local attacker-controlled xgboost.py is not executed during scanning. Please add a test that places a malicious xgboost.py in the current directory, runs scan with loading enabled, and asserts no attacker side effect occurs.

Suggested test addition
+    `@patch`("modelaudit.scanners.xgboost_scanner._check_xgboost_available", return_value=True)
+    def test_xgboost_loading_ignores_local_module_hijack(
+        self,
+        _mock_check_xgb: Mock,
+        monkeypatch: pytest.MonkeyPatch,
+        tmp_path: Path,
+    ) -> None:
+        """Ensure local xgboost.py in cwd is not imported/executed."""
+        hijack_dir = tmp_path / "hijack"
+        hijack_dir.mkdir()
+        (hijack_dir / "xgboost.py").write_text(
+            "from pathlib import Path\n"
+            "Path('hijacked.txt').write_text('pwned')\n"
+            "raise RuntimeError('should not execute')\n",
+            encoding="utf-8",
+        )
+
+        model_path = tmp_path / "model.bst"
+        model_path.write_bytes(b"dummy_xgboost_data")
+        monkeypatch.chdir(hijack_dir)
+
+        scanner = XGBoostScanner({"enable_xgb_loading": True})
+        scanner.scan(str(model_path))
+
+        assert not (hijack_dir / "hijacked.txt").exists()

As per coding guidelines: "Preserve or strengthen security detections; test both benign and malicious samples when adding scanner/feature changes."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/scanners/test_xgboost_scanner.py` around lines 382 - 390, Add a
regression test that ensures a local attacker-controlled module named
"xgboost.py" in the current directory is not executed during scanning: create a
temporary "xgboost.py" in the test CWD that performs a detectable side effect,
run the scanner with loading enabled (reusing the existing mock_subprocess.run
setup), and assert the side effect did not occur while still verifying the
existing call expectations (use mock_subprocess.run.call_args, cmd/script checks
and windows_path, and Path.cwd() comparisons to locate where the scanner was
invoked); remove the temp file after the test to avoid polluting other tests.

assert any("loaded successfully" in str(check.message) for check in result.checks)

@patch("modelaudit.scanners.xgboost_scanner._check_xgboost_available")
Expand Down
Loading