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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

"""Define and initialize the base analyzer."""
Expand Down Expand Up @@ -40,4 +40,9 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes
-------
tuple[HeuristicResult, dict[str, JsonType]]:
The result and related information collected during the analysis.

Raises
------
HeuristicAnalyzerValueError
If a heuristic analysis fails due to malformed package information.
"""
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import logging
import os

from macaron.errors import SourceCodeError
from macaron.json_tools import JsonType
from macaron.malware_analyzer.pypi_heuristics.base_analyzer import BaseHeuristicAnalyzer
from macaron.malware_analyzer.pypi_heuristics.heuristics import HeuristicResult, Heuristics
Expand Down Expand Up @@ -40,11 +39,12 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes
tuple[HeuristicResult, dict[str, JsonType]]:
The result and related information collected during the analysis.
"""
# TODO: .pyi stub files may be present in both source distributions (sdist) and wheels.
# Currently, we only check the sdist, which can lead to false positives in this heuristic.
# To improve accuracy, we should also check for stub files in the wheel distribution.
result = pypi_package_json.download_sourcecode()
if not result:
error_msg = "No source code files have been downloaded"
logger.debug(error_msg)
raise SourceCodeError(error_msg)
return HeuristicResult.SKIP, {"message": "No source code files have been downloaded.", "pyi_files": 0}

file_count = sum(
sum(1 for f in files if f.endswith(".pyi"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def analyze_source(
return {analyzer.heuristic: result}, detail_info

except SourceCodeError as error:
error_msg = f"Unable to perform analysis, source code not available: {error}"
error_msg = f"Unable to perform source code analysis: {error}"
logger.debug(error_msg)
raise HeuristicAnalyzerValueError(error_msg) from error

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. */
/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */

#include "prelude.dl"

Policy("check-malicious-package", component_id, "Check the malicious package.") :-
check_passed(component_id, "mcn_detect_malicious_metadata_1").

apply_policy_to("check-malicious-package", component_id) :-
is_component(component_id, "pkg:pypi/[email protected]").
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

description: |
Analyzing a Python package that is distributed as a wheel, with no source (sdist) available.

tags:
- macaron-python-package

steps:
- name: Run macaron analyze
kind: analyze
options:
command_args:
- -purl
- pkg:pypi/[email protected]
- name: Run macaron verify-policy to verify that the malicious metadata check passes.
kind: verify
options:
policy: policy.dl
13 changes: 5 additions & 8 deletions tests/malware_analyzer/pypi/test_type_stub_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import pytest

from macaron.errors import SourceCodeError
from macaron.malware_analyzer.pypi_heuristics.heuristics import HeuristicResult
from macaron.malware_analyzer.pypi_heuristics.metadata.type_stub_file import TypeStubFileAnalyzer

Expand Down Expand Up @@ -64,14 +63,12 @@ def test_analyze_no_files_fail(analyzer: TypeStubFileAnalyzer, pypi_package_json


def test_analyze_download_failed_raises_error(analyzer: TypeStubFileAnalyzer, pypi_package_json: MagicMock) -> None:
"""Test the analyzer raises SourceCodeError when source code download fails."""
"""Test the analyzer when source code download fails."""
pypi_package_json.download_sourcecode.return_value = False

with pytest.raises(SourceCodeError) as exc_info:
analyzer.analyze(pypi_package_json)

assert "No source code files have been downloaded" in str(exc_info.value)
pypi_package_json.download_sourcecode.assert_called_once()
assert (
HeuristicResult.SKIP,
{"message": "No source code files have been downloaded.", "pyi_files": 0},
) == analyzer.analyze(pypi_package_json)


@pytest.mark.parametrize(
Expand Down
Loading