From c3a35b64edac1ba7c6dfd7d773de11e2f309c720 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Thu, 9 Apr 2026 15:03:38 -0700 Subject: [PATCH 1/3] test(zip): cover malformed manifestless MAR handler parse errors --- tests/scanners/test_zip_scanner.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/scanners/test_zip_scanner.py b/tests/scanners/test_zip_scanner.py index 559f5818e..b9f6d95d2 100644 --- a/tests/scanners/test_zip_scanner.py +++ b/tests/scanners/test_zip_scanner.py @@ -334,6 +334,25 @@ def test_scan_manifestless_mar_skips_oversized_python_handler_analysis(self, tmp assert handler_failures[0].details.get("size_limit") == 16 assert handler_failures[0].location == f"{mar_path}:handler.py" + def test_scan_manifestless_mar_reports_malformed_python_handler(self, tmp_path: Path) -> None: + """Manifest-less .mar handlers with invalid syntax should emit parse-error analysis checks.""" + mar_path = tmp_path / "malformed_handler.mar" + with zipfile.ZipFile(mar_path, "w") as archive: + archive.writestr("handler.py", "def handle(data, context)\n return data\n") + + result = self.scanner.scan(str(mar_path)) + + handler_failures = [ + check + for check in result.checks + if check.name == "TorchServe Handler Static Analysis" and check.status == CheckStatus.FAILED + ] + assert len(handler_failures) == 1 + assert handler_failures[0].severity == IssueSeverity.WARNING + assert "unable to parse python entry for static analysis" in handler_failures[0].message.lower() + assert handler_failures[0].details.get("entry") == "handler.py" + assert handler_failures[0].location == f"{mar_path}:handler.py" + def test_scan_extensionless_nested_zip_recurses(self, tmp_path: Path) -> None: """Extensionless ZIP members should be recursively scanned by content.""" inner_zip = io.BytesIO() From 92cab7f3136a922ef956e468cac8e8087dc58f78 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Thu, 9 Apr 2026 15:26:15 -0700 Subject: [PATCH 2/3] fix(zip): fail closed on MAR handler parse errors --- modelaudit/scanners/zip_scanner.py | 2 ++ tests/scanners/test_zip_scanner.py | 1 + 2 files changed, 3 insertions(+) diff --git a/modelaudit/scanners/zip_scanner.py b/modelaudit/scanners/zip_scanner.py index 96eabef83..43b96fe0f 100644 --- a/modelaudit/scanners/zip_scanner.py +++ b/modelaudit/scanners/zip_scanner.py @@ -419,6 +419,8 @@ def _scan_zip_file(self, path: str, depth: int = 0) -> ScanResult: mar_python_result = self._scan_mar_python_entry(path, name, tmp_path, total_size) if mar_python_result is not None: result.merge(mar_python_result) + if not mar_python_result.success: + scan_complete = False nested_config = dict(self.config) nested_config["_archive_depth"] = depth + 1 diff --git a/tests/scanners/test_zip_scanner.py b/tests/scanners/test_zip_scanner.py index b9f6d95d2..4126ded8c 100644 --- a/tests/scanners/test_zip_scanner.py +++ b/tests/scanners/test_zip_scanner.py @@ -341,6 +341,7 @@ def test_scan_manifestless_mar_reports_malformed_python_handler(self, tmp_path: archive.writestr("handler.py", "def handle(data, context)\n return data\n") result = self.scanner.scan(str(mar_path)) + assert result.success is False handler_failures = [ check From 5651ffdeff2ea5b00ac2a8bcdef3348ecce338a6 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Fri, 10 Apr 2026 00:12:39 -0700 Subject: [PATCH 3/3] test: clarify mar handler parse failures --- modelaudit/scanners/zip_scanner.py | 2 +- tests/scanners/test_zip_scanner.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/modelaudit/scanners/zip_scanner.py b/modelaudit/scanners/zip_scanner.py index 43b96fe0f..26e3048ea 100644 --- a/modelaudit/scanners/zip_scanner.py +++ b/modelaudit/scanners/zip_scanner.py @@ -527,7 +527,7 @@ def _scan_mar_python_entry( message=f"Unable to parse Python entry for static analysis: {parse_error}", severity=IssueSeverity.WARNING, location=f"{archive_path}:{entry_name}", - details={"entry": entry_name}, + details={"entry": entry_name, "analysis_kind": "syntax", "parse_error": parse_error}, ) else: result.add_check( diff --git a/tests/scanners/test_zip_scanner.py b/tests/scanners/test_zip_scanner.py index 4126ded8c..3904cfc00 100644 --- a/tests/scanners/test_zip_scanner.py +++ b/tests/scanners/test_zip_scanner.py @@ -342,6 +342,8 @@ def test_scan_manifestless_mar_reports_malformed_python_handler(self, tmp_path: result = self.scanner.scan(str(mar_path)) assert result.success is False + assert result.has_warnings is True + assert result.has_errors is False handler_failures = [ check @@ -352,6 +354,8 @@ def test_scan_manifestless_mar_reports_malformed_python_handler(self, tmp_path: assert handler_failures[0].severity == IssueSeverity.WARNING assert "unable to parse python entry for static analysis" in handler_failures[0].message.lower() assert handler_failures[0].details.get("entry") == "handler.py" + assert handler_failures[0].details.get("analysis_kind") == "syntax" + assert "expected ':'" in str(handler_failures[0].details.get("parse_error")).lower() assert handler_failures[0].location == f"{mar_path}:handler.py" def test_scan_extensionless_nested_zip_recurses(self, tmp_path: Path) -> None: