Skip to content

Commit 165bbb6

Browse files
committed
chore(tests): improve test coverage and apply minor heuristic changes
Signed-off-by: Amine <amine.raouane@enim.ac.ma>
1 parent 5c25599 commit 165bbb6

File tree

10 files changed

+166
-230
lines changed

10 files changed

+166
-230
lines changed

src/macaron/config/defaults.ini

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -639,14 +639,14 @@ disabled_custom_rulesets =
639639
[llm]
640640
# The LLM configuration for Macaron.
641641
# If enabled, the LLM will be used to analyze the results and provide insights.
642-
enabled = False
642+
enabled = True
643643
# The provider for the LLM service.
644644
# Supported providers :
645645
# - openai: OpenAI's GPT models.
646-
provider =
646+
provider = openai
647647
# The API key for the LLM service.
648-
api_key =
648+
api_key = lm-studio
649649
# The API endpoint for the LLM service.
650-
api_endpoint =
650+
api_endpoint = http://127.0.0.1:1234/v1/chat/completions
651651
# The model to use for the LLM service.
652-
model =
652+
model = openai/gpt-oss-20b

src/macaron/malware_analyzer/pypi_heuristics/metadata/inconsistent_description.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes
107107
user_prompt=description,
108108
response_format=self.RESPONSE_FORMAT,
109109
)
110+
if not analysis_result:
111+
logger.error("LLM returned invalid response, skipping the analysis.")
112+
return HeuristicResult.SKIP, {}
110113

111114
if analysis_result["score"] < self.threshold:
112115
return HeuristicResult.FAIL, {

src/macaron/malware_analyzer/pypi_heuristics/sourcecode/matching_docstrings.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes
9393
logger.warning("No source code found for the package, skipping the matching docstrings analysis.")
9494
return HeuristicResult.SKIP, {}
9595

96+
none_attempts = 5
9697
for file, content in pypi_package_json.iter_sourcecode():
9798
if file.endswith(".py"):
9899
time.sleep(self.REQUEST_INTERVAL) # Respect the request interval to avoid rate limiting.
@@ -101,6 +102,13 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes
101102
user_prompt=code_str,
102103
response_format=self.RESPONSE_FORMAT,
103104
)
105+
if not analysis_result:
106+
none_attempts -= 1
107+
if none_attempts == 0:
108+
logger.error("LLM returned None multiple times, skipping the analysis.")
109+
return HeuristicResult.SKIP, {}
110+
continue
111+
104112
if analysis_result["decision"] == "inconsistent":
105113
return HeuristicResult.FAIL, {
106114
"file": file,

src/macaron/slsa_analyzer/build_tool/gradle.py

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved.
2-
# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved.
32
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.
43

54
"""This module contains the Gradle class which inherits BaseBuildTool.
@@ -70,94 +69,6 @@ def is_detected(self, repo_path: str) -> bool:
7069
gradle_config_files = self.build_configs + self.entry_conf
7170
return any(file_exists(repo_path, file) for file in gradle_config_files)
7271

73-
def prepare_config_files(self, wrapper_path: str, build_dir: str) -> bool:
74-
"""Prepare the necessary wrapper files for running the build.
75-
76-
This method will return False if there is any errors happened during operation.
77-
78-
Parameters
79-
----------
80-
wrapper_path : str
81-
The path where all necessary wrapper files are located.
82-
build_dir : str
83-
The path of the build dir. This is where all files are copied to.
84-
85-
Returns
86-
-------
87-
bool
88-
True if succeed else False.
89-
"""
90-
# The path of the needed wrapper files
91-
wrapper_files = self.wrapper_files
92-
93-
if copy_file_bulk(wrapper_files, wrapper_path, build_dir):
94-
# Ensure that gradlew is executable.
95-
file_path = os.path.join(build_dir, "gradlew")
96-
status = os.stat(file_path)
97-
if oct(status.st_mode)[-3:] != "744":
98-
logger.debug("%s does not have 744 permission. Changing it to 744.")
99-
os.chmod(file_path, 0o744)
100-
return True
101-
102-
return False
103-
104-
def get_dep_analyzer(self) -> CycloneDxGradle:
105-
"""Create a DependencyAnalyzer for the Gradle build tool.
106-
107-
Returns
108-
-------
109-
CycloneDxGradle
110-
The CycloneDxGradle object.
111-
112-
Raises
113-
------
114-
DependencyAnalyzerError
115-
"""
116-
if "dependency.resolver" not in defaults or "dep_tool_gradle" not in defaults["dependency.resolver"]:
117-
raise DependencyAnalyzerError("No default dependency analyzer is found.")
118-
if not DependencyAnalyzer.tool_valid(defaults.get("dependency.resolver", "dep_tool_gradle")):
119-
raise DependencyAnalyzerError(
120-
f"Dependency analyzer {defaults.get('dependency.resolver', 'dep_tool_gradle')} is not valid.",
121-
)
122-
123-
tool_name, tool_version = tuple(
124-
defaults.get(
125-
"dependency.resolver",
126-
"dep_tool_gradle",
127-
fallback="cyclonedx-gradle:1.7.3",
128-
).split(":")
129-
)
130-
if tool_name == DependencyTools.CYCLONEDX_GRADLE:
131-
return CycloneDxGradle(
132-
resources_path=global_config.resources_path,
133-
file_name="bom.json",
134-
tool_name=tool_name,
135-
tool_version=tool_version,
136-
)
137-
138-
raise DependencyAnalyzerError(f"Unsupported SBOM generator for Gradle: {tool_name}.")
139-
140-
def get_gradle_exec(self, repo_path: str) -> str:
141-
"""Get the Gradle executable for the repo.
142-
143-
Parameters
144-
----------
145-
repo_path: str
146-
The absolute path to a repository containing Gradle projects.
147-
148-
Returns
149-
-------
150-
str
151-
The absolute path to the Gradle executable.
152-
"""
153-
# We try to use the gradlew that comes with the repository first.
154-
repo_gradlew = os.path.join(repo_path, "gradlew")
155-
if os.path.isfile(repo_gradlew) and os.access(repo_gradlew, os.X_OK):
156-
return repo_gradlew
157-
158-
# We use Macaron's built-in gradlew as a fallback option.
159-
return os.path.join(os.path.join(macaron.MACARON_PATH, "resources"), "gradlew")
160-
16172
def get_group_id(self, gradle_exec: str, project_path: str) -> str | None:
16273
"""Get the group id of a Gradle project.
16374

src/macaron/slsa_analyzer/build_tool/maven.py

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -64,71 +64,3 @@ def is_detected(self, repo_path: str) -> bool:
6464
return False
6565
maven_config_files = self.build_configs
6666
return any(file_exists(repo_path, file) for file in maven_config_files)
67-
68-
def prepare_config_files(self, wrapper_path: str, build_dir: str) -> bool:
69-
"""Prepare the necessary wrapper files for running the build.
70-
71-
This method will return False if there is any errors happened during operation.
72-
73-
Parameters
74-
----------
75-
wrapper_path : str
76-
The path where all necessary wrapper files are located.
77-
build_dir : str
78-
The path of the build dir. This is where all files are copied to.
79-
80-
Returns
81-
-------
82-
bool
83-
True if succeed else False.
84-
"""
85-
# The path of the needed wrapper files
86-
wrapper_files = self.wrapper_files
87-
88-
if copy_file_bulk(wrapper_files, wrapper_path, build_dir):
89-
# Ensure that mvnw is executable.
90-
file_path = os.path.join(build_dir, "mvnw")
91-
status = os.stat(file_path)
92-
if oct(status.st_mode)[-3:] != "744":
93-
logger.debug("%s does not have 744 permission. Changing it to 744.")
94-
os.chmod(file_path, 0o744)
95-
return True
96-
97-
return False
98-
99-
def get_dep_analyzer(self) -> CycloneDxMaven:
100-
"""
101-
Create a DependencyAnalyzer for the Maven build tool.
102-
103-
Returns
104-
-------
105-
CycloneDxMaven
106-
The CycloneDxMaven object.
107-
108-
Raises
109-
------
110-
DependencyAnalyzerError
111-
"""
112-
if "dependency.resolver" not in defaults or "dep_tool_maven" not in defaults["dependency.resolver"]:
113-
raise DependencyAnalyzerError("No default dependency analyzer is found.")
114-
if not DependencyAnalyzer.tool_valid(defaults.get("dependency.resolver", "dep_tool_maven")):
115-
raise DependencyAnalyzerError(
116-
f"Dependency analyzer {defaults.get('dependency.resolver', 'dep_tool_maven')} is not valid.",
117-
)
118-
119-
tool_name, tool_version = tuple(
120-
defaults.get(
121-
"dependency.resolver",
122-
"dep_tool_maven",
123-
fallback="cyclonedx-maven:2.6.2",
124-
).split(":")
125-
)
126-
if tool_name == DependencyTools.CYCLONEDX_MAVEN:
127-
return CycloneDxMaven(
128-
resources_path=global_config.resources_path,
129-
file_name="bom.json",
130-
tool_name=tool_name,
131-
tool_version=tool_version,
132-
)
133-
134-
raise DependencyAnalyzerError(f"Unsupported SBOM generator for Maven: {tool_name}.")

src/macaron/slsa_analyzer/build_tool/pip.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,6 @@ def get_dep_analyzer(self) -> DependencyAnalyzer:
6464
DependencyAnalyzer
6565
The DependencyAnalyzer object.
6666
"""
67-
tool_name = "cyclonedx_py"
68-
if not DependencyAnalyzer.tool_valid(f"{tool_name}:{cyclonedx_version}"):
69-
raise DependencyAnalyzerError(
70-
f"Dependency analyzer {defaults.get('dependency.resolver', 'dep_tool_gradle')} is not valid.",
71-
)
7267
return CycloneDxPython(
7368
resources_path=global_config.resources_path,
7469
file_name="python_sbom.json",

src/macaron/slsa_analyzer/build_tool/poetry.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,6 @@ def get_dep_analyzer(self) -> DependencyAnalyzer:
102102
DependencyAnalyzer
103103
The DependencyAnalyzer object.
104104
"""
105-
tool_name = "cyclonedx_py"
106-
if not DependencyAnalyzer.tool_valid(f"{tool_name}:{cyclonedx_version}"):
107-
raise DependencyAnalyzerError(
108-
f"Dependency analyzer {defaults.get('dependency.resolver', 'dep_tool_gradle')} is not valid.",
109-
)
110105
return CycloneDxPython(
111106
resources_path=global_config.resources_path,
112107
file_name="python_sbom.json",

src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,9 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
450450
failed({Heuristics.SIMILAR_PROJECTS.value}),
451451
failed({Heuristics.HIGH_RELEASE_FREQUENCY.value}),
452452
failed({Heuristics.FAKE_EMAIL.value}).
453+
453454
% Package released with a name similar to a popular package.
454-
{Confidence.MEDIUM.value}::trigger(malware_medium_confidence_3) :-
455+
{Confidence.MEDIUM.value}::trigger(malware_medium_confidence_5) :-
455456
quickUndetailed, forceSetup, failed({Heuristics.MATCHING_DOCSTRINGS.value}).
456457
457458
% ----- Evaluation -----
@@ -461,6 +462,7 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData:
461462
{problog_result_access} :- trigger(malware_high_confidence_2).
462463
{problog_result_access} :- trigger(malware_high_confidence_3).
463464
{problog_result_access} :- trigger(malware_high_confidence_4).
465+
{problog_result_access} :- trigger(malware_medium_confidence_5).
464466
{problog_result_access} :- trigger(malware_medium_confidence_4).
465467
{problog_result_access} :- trigger(malware_medium_confidence_3).
466468
{problog_result_access} :- trigger(malware_medium_confidence_2).

0 commit comments

Comments
 (0)