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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- classify unavailable ZIP traversal, member, manifest-less TorchServe handler, and Keras artifact scan coverage as inconclusive while preserving archive-depth security findings
- classify unavailable TAR traversal and member scan coverage as inconclusive while preserving depth-limit security findings
- classify unavailable Skops member and schema coverage as inconclusive rather than security findings
- preserve Skops nested Python-member detection for high-risk calls reached through `__getattribute__`
- classify bounded, unreadable, or unparseable TorchServe MAR analysis gaps as inconclusive rather than security findings
- detect manifest-declared TorchServe extra files and PyTorch ZIP members disguised with executable content
- classify incomplete SevenZip coverage as inconclusive and avoid caching temporary extracted members
Expand Down
36 changes: 36 additions & 0 deletions tests/scanners/test_skops_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,42 @@ def test_oversized_executable_member_is_still_checked_by_nested_scanner(self, tm
)
assert determine_exit_code(result) == 1

def test_python_member_getattribute_high_risk_call_is_reported(self, tmp_path: Path) -> None:
"""Skops nested ZIP analysis must catch active code recovered through __getattribute__."""
skops_file = tmp_path / "getattribute_payload.skops"
source = "import os\nresolve = os.__getattribute__\nrunner = resolve('sys' + 'tem')\nrunner('echo hidden')\n"
with zipfile.ZipFile(skops_file, "w", compression=zipfile.ZIP_DEFLATED) as zf:
zf.writestr("schema.json", '{"version": "1.0"}')
zf.writestr("handler.py", source)

result = scan_model_directory_or_file(str(skops_file), cache_enabled=False)

python_issues = [
issue
for issue in result.issues
if issue.message == "High-risk Python code found in ZIP member handler.py: high-risk calls: os.system"
and issue.details.get("entry") == "handler.py"
and issue.details.get("reason") == "high-risk calls: os.system"
]
assert len(python_issues) == 1
assert python_issues[0].severity == IssueSeverity.WARNING
assert determine_exit_code(result) == 1

def test_python_member_benign_getattribute_remains_quiet(self, tmp_path: Path) -> None:
"""Benign attribute retrieval in Skops Python members should not become a finding."""
skops_file = tmp_path / "benign_getattribute.skops"
source = "import os\nresolve = os.__getattribute__\ncurrent_dir = resolve('getcwd')\ncurrent_dir()\n"
with zipfile.ZipFile(skops_file, "w", compression=zipfile.ZIP_DEFLATED) as zf:
zf.writestr("schema.json", '{"version": "1.0"}')
zf.writestr("handler.py", source)

result = scan_model_directory_or_file(str(skops_file), cache_enabled=False)

assert determine_exit_code(result) == 0
assert not any(
issue.message.startswith("High-risk Python code found in ZIP member handler.py") for issue in result.issues
)

@pytest.mark.parametrize(
("exception_type", "message"),
[
Expand Down
Loading