Skip to content

Commit 7fe245f

Browse files
test: cover Skops getattribute member scanning
Adds regression coverage for Skops nested Python members that resolve high-risk calls through __getattribute__, with benign getattribute coverage to guard false positives.
1 parent 0ddbb93 commit 7fe245f

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
104104
- classify unavailable ZIP traversal, member, manifest-less TorchServe handler, and Keras artifact scan coverage as inconclusive while preserving archive-depth security findings
105105
- classify unavailable TAR traversal and member scan coverage as inconclusive while preserving depth-limit security findings
106106
- classify unavailable Skops member and schema coverage as inconclusive rather than security findings
107+
- preserve Skops nested Python-member detection for high-risk calls reached through `__getattribute__`
107108
- classify bounded, unreadable, or unparseable TorchServe MAR analysis gaps as inconclusive rather than security findings
108109
- detect manifest-declared TorchServe extra files and PyTorch ZIP members disguised with executable content
109110
- classify incomplete SevenZip coverage as inconclusive and avoid caching temporary extracted members

tests/scanners/test_skops_scanner.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,42 @@ def test_oversized_executable_member_is_still_checked_by_nested_scanner(self, tm
715715
)
716716
assert determine_exit_code(result) == 1
717717

718+
def test_python_member_getattribute_high_risk_call_is_reported(self, tmp_path: Path) -> None:
719+
"""Skops nested ZIP analysis must catch active code recovered through __getattribute__."""
720+
skops_file = tmp_path / "getattribute_payload.skops"
721+
source = "import os\nresolve = os.__getattribute__\nrunner = resolve('sys' + 'tem')\nrunner('echo hidden')\n"
722+
with zipfile.ZipFile(skops_file, "w", compression=zipfile.ZIP_DEFLATED) as zf:
723+
zf.writestr("schema.json", '{"version": "1.0"}')
724+
zf.writestr("handler.py", source)
725+
726+
result = scan_model_directory_or_file(str(skops_file), cache_enabled=False)
727+
728+
python_issues = [
729+
issue
730+
for issue in result.issues
731+
if issue.message == "High-risk Python code found in ZIP member handler.py: high-risk calls: os.system"
732+
and issue.details.get("entry") == "handler.py"
733+
and issue.details.get("reason") == "high-risk calls: os.system"
734+
]
735+
assert len(python_issues) == 1
736+
assert python_issues[0].severity == IssueSeverity.WARNING
737+
assert determine_exit_code(result) == 1
738+
739+
def test_python_member_benign_getattribute_remains_quiet(self, tmp_path: Path) -> None:
740+
"""Benign attribute retrieval in Skops Python members should not become a finding."""
741+
skops_file = tmp_path / "benign_getattribute.skops"
742+
source = "import os\nresolve = os.__getattribute__\ncurrent_dir = resolve('getcwd')\ncurrent_dir()\n"
743+
with zipfile.ZipFile(skops_file, "w", compression=zipfile.ZIP_DEFLATED) as zf:
744+
zf.writestr("schema.json", '{"version": "1.0"}')
745+
zf.writestr("handler.py", source)
746+
747+
result = scan_model_directory_or_file(str(skops_file), cache_enabled=False)
748+
749+
assert determine_exit_code(result) == 0
750+
assert not any(
751+
issue.message.startswith("High-risk Python code found in ZIP member handler.py") for issue in result.issues
752+
)
753+
718754
@pytest.mark.parametrize(
719755
("exception_type", "message"),
720756
[

0 commit comments

Comments
 (0)