diff --git a/docs/source/_static/images/tutorial_django_5.0.6_detect_malicious_metadata_check.png b/docs/source/_static/images/tutorial_django_5.0.6_detect_malicious_metadata_check.png new file mode 100644 index 000000000..aa6aedf11 Binary files /dev/null and b/docs/source/_static/images/tutorial_django_5.0.6_detect_malicious_metadata_check.png differ diff --git a/docs/source/assets/er-diagram.svg b/docs/source/assets/er-diagram.svg index 9c0269548..c9cbfb5aa 100644 --- a/docs/source/assets/er-diagram.svg +++ b/docs/source/assets/er-diagram.svg @@ -4,201 +4,226 @@ - - + + %3 - + _analysis - -_analysis - -id - [INTEGER] - NOT NULL - -analysis_time - [VARCHAR] - NOT NULL - -macaron_version - [VARCHAR] - NOT NULL + +_analysis + +id + [INTEGER] + NOT NULL + +analysis_time + [VARCHAR] + NOT NULL + +macaron_version + [VARCHAR] + NOT NULL _component - -_component - -id - [INTEGER] - NOT NULL - -analysis_id - [INTEGER] - NOT NULL - -name - [VARCHAR(100)] - NOT NULL - -namespace - [VARCHAR(255)] - -purl - [VARCHAR] - NOT NULL - -qualifiers - [VARCHAR(1024)] - -subpath - [VARCHAR(200)] - -type - [VARCHAR(16)] - NOT NULL - -version - [VARCHAR(100)] + +_component + +id + [INTEGER] + NOT NULL + +analysis_id + [INTEGER] + NOT NULL + +name + [VARCHAR(100)] + NOT NULL + +namespace + [VARCHAR(255)] + +purl + [VARCHAR] + NOT NULL + +qualifiers + [VARCHAR(1024)] + +subpath + [VARCHAR(200)] + +type + [VARCHAR(16)] + NOT NULL + +version + [VARCHAR(100)] _analysis--_component - -0..N -1 + +0..N +1 _check_facts - -_check_facts - -id - [INTEGER] - NOT NULL - -check_result_id - [VARCHAR] - NOT NULL - -check_type - [VARCHAR] - NOT NULL - -component_id - [INTEGER] - NOT NULL - -confidence - [FLOAT] - NOT NULL + +_check_facts + +id + [INTEGER] + NOT NULL + +check_result_id + [VARCHAR] + NOT NULL + +check_type + [VARCHAR] + NOT NULL + +component_id + [INTEGER] + NOT NULL + +confidence + [FLOAT] + NOT NULL - + _component--_check_facts - -0..N -1 + +0..N +1 - + _check_result - -_check_result - -id - [INTEGER] - NOT NULL - -check_id - [VARCHAR] - NOT NULL - -component_id - [INTEGER] - NOT NULL - -passed - [BOOLEAN] - NOT NULL + +_check_result + +id + [INTEGER] + NOT NULL + +check_id + [VARCHAR] + NOT NULL + +component_id + [INTEGER] + NOT NULL + +passed + [BOOLEAN] + NOT NULL - + _component--_check_result - -0..N -1 + +0..N +1 - + _dependency - -_dependency - -child_component - [INTEGER] - NOT NULL - -parent_component - [INTEGER] - NOT NULL + +_dependency + +child_component + [INTEGER] + NOT NULL + +parent_component + [INTEGER] + NOT NULL - + _component--_dependency - -1 -1 + +1 +1 - + _component--_dependency - -1 -1 + +1 +1 - + _provenance - -_provenance - -id - [INTEGER] - NOT NULL - -component_id - [INTEGER] - NOT NULL - -provenance_json - [VARCHAR] - NOT NULL - -release_commit_sha - [VARCHAR] - -release_tag - [VARCHAR] - -version - [VARCHAR] - NOT NULL + +_provenance + +id + [INTEGER] + NOT NULL + +component_id + [INTEGER] + NOT NULL + +provenance_json + [VARCHAR] + NOT NULL + +release_commit_sha + [VARCHAR] + +release_tag + [VARCHAR] + +version + [VARCHAR] + NOT NULL - + _component--_provenance - -0..N -1 + +0..N +1 + + + +_provenance_subject + +_provenance_subject + +id + [INTEGER] + NOT NULL + +component_id + [INTEGER] + NOT NULL + +sha256 + [VARCHAR] + NOT NULL + + + +_component--_provenance_subject + +0..N +1 - + _repository _repository @@ -253,14 +278,14 @@ NOT NULL - + _component--_repository - -0..N -1 + +0..N +1 - + _slsa_level _slsa_level @@ -278,14 +303,14 @@ NOT NULL - + _component--_slsa_level - -1 -1 + +1 +1 - + _slsa_requirement _slsa_requirement @@ -309,389 +334,481 @@ [VARCHAR] - + _component--_slsa_requirement - -0..N -1 + +0..N +1 _build_as_code_check - -_build_as_code_check - -id - [INTEGER] - NOT NULL - -build_tool_name - [VARCHAR] - NOT NULL - -build_trigger - [VARCHAR] - -ci_service_name - [VARCHAR] - NOT NULL - -deploy_command - [VARCHAR] - -language - [VARCHAR] - NOT NULL - -language_distributions - [VARCHAR] - -language_url - [VARCHAR] - -language_versions - [VARCHAR] + +_build_as_code_check + +id + [INTEGER] + NOT NULL + +build_tool_name + [VARCHAR] + NOT NULL + +build_trigger + [VARCHAR] + +ci_service_name + [VARCHAR] + NOT NULL + +deploy_command + [VARCHAR] + +language + [VARCHAR] + NOT NULL + +language_distributions + [VARCHAR] + +language_url + [VARCHAR] + +language_versions + [VARCHAR] _check_facts--_build_as_code_check - -1 -1 + +1 +1 _build_script_check - -_build_script_check - -id - [INTEGER] - NOT NULL - -build_tool_command - [VARCHAR] - -build_tool_name - [VARCHAR] - NOT NULL - -caller_ci - [VARCHAR] - -language - [VARCHAR] - NOT NULL - -language_distributions - [VARCHAR] - -language_url - [VARCHAR] - -language_versions - [VARCHAR] + +_build_script_check + +id + [INTEGER] + NOT NULL + +build_tool_command + [VARCHAR] + +build_tool_name + [VARCHAR] + NOT NULL + +build_trigger + [VARCHAR] + +ci_service_name + [VARCHAR] + NOT NULL + +language + [VARCHAR] + NOT NULL + +language_distributions + [VARCHAR] + +language_url + [VARCHAR] + +language_versions + [VARCHAR] _check_facts--_build_script_check - -1 -1 + +1 +1 _build_service_check - -_build_service_check - -id - [INTEGER] - NOT NULL - -build_command - [VARCHAR] - -build_tool_name - [VARCHAR] - NOT NULL - -build_trigger - [VARCHAR] - -ci_service_name - [VARCHAR] - NOT NULL - -language - [VARCHAR] - NOT NULL - -language_distributions - [VARCHAR] - -language_url - [VARCHAR] - -language_versions - [VARCHAR] + +_build_service_check + +id + [INTEGER] + NOT NULL + +build_command + [VARCHAR] + +build_tool_name + [VARCHAR] + NOT NULL + +build_trigger + [VARCHAR] + +ci_service_name + [VARCHAR] + NOT NULL + +language + [VARCHAR] + NOT NULL + +language_distributions + [VARCHAR] + +language_url + [VARCHAR] + +language_versions + [VARCHAR] _check_facts--_build_service_check - -1 -1 + +1 +1 _cue_expectation - -_cue_expectation - -id - [INTEGER] - NOT NULL - -asset_url - [VARCHAR] - -description - [VARCHAR] - NOT NULL - -expectation_type - [VARCHAR] - NOT NULL - -path - [VARCHAR] - NOT NULL - -sha - [VARCHAR] - -target - [VARCHAR] - NOT NULL - -text - [VARCHAR] + +_cue_expectation + +id + [INTEGER] + NOT NULL + +asset_url + [VARCHAR] + +description + [VARCHAR] + NOT NULL + +expectation_type + [VARCHAR] + NOT NULL + +path + [VARCHAR] + NOT NULL + +sha + [VARCHAR] + +target + [VARCHAR] + NOT NULL + +text + [VARCHAR] _check_facts--_cue_expectation - -1 -1 + +1 +1 - + +_detect_malicious_metadata_check + +_detect_malicious_metadata_check + +id + [INTEGER] + NOT NULL + +detail_information + [JSON] + NOT NULL + +result + [JSON] + NOT NULL + + + +_check_facts--_detect_malicious_metadata_check + +1 +1 + + + _infer_artifact_pipeline_check - -_infer_artifact_pipeline_check - -id - [INTEGER] - NOT NULL - -deploy_job - [VARCHAR] - NOT NULL - -deploy_step - [VARCHAR] - NOT NULL - -run_url - [VARCHAR] - NOT NULL + +_infer_artifact_pipeline_check + +id + [INTEGER] + NOT NULL + +deploy_job + [VARCHAR] + NOT NULL + +deploy_step + [VARCHAR] + NOT NULL + +run_url + [VARCHAR] + NOT NULL - + _check_facts--_infer_artifact_pipeline_check - -1 -1 + +1 +1 - + _provenance_available_check - -_provenance_available_check - -id - [INTEGER] - NOT NULL - -asset_name - [VARCHAR] - NOT NULL - -asset_url - [VARCHAR] + +_provenance_available_check + +id + [INTEGER] + NOT NULL + +asset_name + [VARCHAR] + +asset_url + [VARCHAR] - + _check_facts--_provenance_available_check - -1 -1 + +1 +1 + + + +_provenance_derived_commit_check + +_provenance_derived_commit_check + +id + [INTEGER] + NOT NULL + +commit_info + [VARCHAR] + + + +_check_facts--_provenance_derived_commit_check + +1 +1 + + + +_provenance_derived_repo_check + +_provenance_derived_repo_check + +id + [INTEGER] + NOT NULL + +repository_info + [VARCHAR] + + + +_check_facts--_provenance_derived_repo_check + +1 +1 - + _provenance_l3_check - -_provenance_l3_check - -id - [INTEGER] - NOT NULL + +_provenance_l3_check + +id + [INTEGER] + NOT NULL - + _check_facts--_provenance_l3_check - -1 -1 + +1 +1 + + + +_provenance_verified_check + +_provenance_verified_check + +id + [INTEGER] + NOT NULL + +build_level + [INTEGER] + NOT NULL + +build_type + [VARCHAR] + + + +_check_facts--_provenance_verified_check + +1 +1 - + _provenance_witness_l1_check - -_provenance_witness_l1_check - -id - [INTEGER] - NOT NULL - -artifact_url - [VARCHAR] - -provenance_name - [VARCHAR] - NOT NULL - -provenance_url - [VARCHAR] + +_provenance_witness_l1_check + +id + [INTEGER] + NOT NULL + +artifact_url + [VARCHAR] + +provenance_name + [VARCHAR] + NOT NULL + +provenance_url + [VARCHAR] - + _check_facts--_provenance_witness_l1_check - -1 -1 + +1 +1 - + _trusted_builder_check - -_trusted_builder_check - -id - [INTEGER] - NOT NULL - -build_tool_name - [VARCHAR] - NOT NULL - -build_trigger - [VARCHAR] - -ci_service_name - [VARCHAR] - NOT NULL + +_trusted_builder_check + +id + [INTEGER] + NOT NULL + +build_tool_name + [VARCHAR] + NOT NULL + +build_trigger + [VARCHAR] + +ci_service_name + [VARCHAR] + NOT NULL - + _check_facts--_trusted_builder_check - -1 -1 + +1 +1 - + _vcs_check - -_vcs_check - -id - [INTEGER] - NOT NULL - -git_repo - [VARCHAR] + +_vcs_check + +id + [INTEGER] + NOT NULL + +git_repo + [VARCHAR] - + _check_facts--_vcs_check - -1 -1 + +1 +1 - + _check_result--_check_facts - -0..N -1 + +0..N +1 - + _release_artifact - -_release_artifact - -id - [INTEGER] - NOT NULL - -name - [VARCHAR] - NOT NULL - -provenance_id - [INTEGER] - -slsa_verified - [BOOLEAN] + +_release_artifact + +id + [INTEGER] + NOT NULL + +name + [VARCHAR] + NOT NULL + +provenance_id + [INTEGER] + +slsa_verified + [BOOLEAN] - + _provenance--_release_artifact - -0..N -{0,1} + +0..N +{0,1} - + _hash_digest - -_hash_digest - -id - [INTEGER] - NOT NULL - -artifact_id - [INTEGER] - NOT NULL - -digest - [VARCHAR] - NOT NULL - -digest_algorithm - [VARCHAR] - NOT NULL + +_hash_digest + +id + [INTEGER] + NOT NULL + +artifact_id + [INTEGER] + NOT NULL + +digest + [VARCHAR] + NOT NULL + +digest_algorithm + [VARCHAR] + NOT NULL - + _release_artifact--_hash_digest - -0..N -1 + +0..N +1 diff --git a/docs/source/index.rst b/docs/source/index.rst index cee3db504..c4d4e5aa7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -55,9 +55,6 @@ the requirements that are currently supported by Macaron. * - SLSA level - SLSA spec v0.1 - Concrete check - * - 0+ - - **Provenance verified** - Provenance is available and verified. - - See :doc:`SLSA Build Levels ` * - 1 - **Scripted build** - All build steps were fully defined in a “build script”. - Identify and validate build script(s). @@ -70,6 +67,9 @@ the requirements that are currently supported by Macaron. * - 2 - **Build service** - All build steps are run using some build service (e.g. GitHub Actions) - Identify and validate the CI service(s) used for the build process. + * - 2+ + - **Provenance verified** - Provenance is available and verified. + - See :doc:`SLSA Build Levels ` * - 3 - **Trusted builders** - Guarantees the identification of the top-level build configuration used to initiate the build. The build is verified to be hermetic, isolated, parameterless, and executed in an ephemeral environment. - Identify and validate that the builder used in the CI pipeline is a trusted one. @@ -92,6 +92,19 @@ the requirements that are currently supported by Macaron. - **Provenance derived commit** - Check if the analysis target's commit matches the commit in the provenance. - If there is no commit, this check will fail. +**************************************************************************************** +Macaron checks that report integrity issues but do not map to SLSA requirements directly +**************************************************************************************** + +.. list-table:: + :widths: 20 40 + :header-rows: 1 + + * - Check name + - Description + * - Detect malicious metadata + - This check analyzes the metadata of a package and reports malicious behavior. This check currently supports PyPI packages. + ---------------------- How does Macaron work? ---------------------- diff --git a/docs/source/pages/supported_technologies/index.rst b/docs/source/pages/supported_technologies/index.rst index e2c490378..670b2df06 100644 --- a/docs/source/pages/supported_technologies/index.rst +++ b/docs/source/pages/supported_technologies/index.rst @@ -79,6 +79,9 @@ Package Registries * - `npm Registry `_ - Projects built with npm or Yarn and published on the npm registry. - :doc:`page ` + * - `Python Package Index (PyPI) `_ + - Projects built with Pip or Poetry and published on the PyPI registry. + - :doc:`page ` ----------- Provenances @@ -115,3 +118,4 @@ See also witness maven_central npm_registry + pypi_registry diff --git a/docs/source/pages/supported_technologies/pypi_registry.rst b/docs/source/pages/supported_technologies/pypi_registry.rst new file mode 100644 index 000000000..86db042c9 --- /dev/null +++ b/docs/source/pages/supported_technologies/pypi_registry.rst @@ -0,0 +1,6 @@ +.. Copyright (c) 2024 - 2024, 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/. + +=========================== +Python Package Index (PyPI) +=========================== diff --git a/docs/source/pages/tutorials/detect_malicious_package.rst b/docs/source/pages/tutorials/detect_malicious_package.rst new file mode 100644 index 000000000..a5d1781de --- /dev/null +++ b/docs/source/pages/tutorials/detect_malicious_package.rst @@ -0,0 +1,243 @@ +.. Copyright (c) 2024 - 2024, 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/. + +.. _detect-malicious-package: + +---------------------------- +Detecting malicious packages +---------------------------- + +In this tutorial we show how to use Macaron to find malicious packages. Imagine you’ve discovered a Python package you want to add as a dependency to your project, but you’re unsure whether you can trust its maintainers. In this case, you can run Macaron to see if it can detect any malicious behavior. Note that Macaron is an analysis tool and can either miss a malicious behavior or report a false positive. + +.. list-table:: + :widths: 25 + :header-rows: 1 + + * - Supported packages + * - Python packages (PyPI) + +.. contents:: :local: + + +********** +Background +********** + +Detecting malicious behavior in open-source software has been a focus for the `Open Source Security Foundation `_ (OpenSSF) community in recent years. One significant initiative is :term:`SLSA`, which offers practical recommendations to enhance the integrity of software packages and infrastructure. Macaron is designed to detect poorly maintained or malicious packages by implementing checks inspired by the SLSA specification. However, some forms of attacks currently fall outside the scope of SLSA version 1—notably, SLSA doesn't address the issue of malicious maintainers. Our primary goal is to make it more difficult for malicious actors to compromise critical supply chains and infrastructure. To achieve this, we're developing new methods to detect when maintainers of open source projects are untrustworthy and deliberately spreading malware. + +****************************** +Installation and Prerequisites +****************************** + +Skip this section if you already know how to install Macaron. + +.. toggle:: + + Please follow the instructions :ref:`here `. In summary, you need: + + * Docker + * the ``run_macaron.sh`` script to run the Macaron image. + + .. note:: At the moment, Docker alternatives (e.g. podman) are not supported. + + + You also need to provide Macaron with a GitHub token through the ``GITHUB_TOKEN`` environment variable. + + To obtain a GitHub Token: + + * Go to ``GitHub settings`` → ``Developer Settings`` (at the bottom of the left side pane) → ``Personal Access Tokens`` → ``Fine-grained personal access tokens`` → ``Generate new token``. Give your token a name and an expiry period. + * Under ``"Repository access"``, choosing ``"Public Repositories (read-only)"`` should be good enough in most cases. + + Now you should be good to run Macaron. For more details, see the documentation :ref:`here `. + +*********** +Run Macaron +*********** + +In this tutorial, we run Macaron on the ``django`` Python package as an example with and without its dependencies to check malicious behavior and apply a policy to fail if the corresponding check fails. + + +''''''''''''''''''''''''''''''''''''' +Analyzing django without dependencies +''''''''''''''''''''''''''''''''''''' + +First, we need to run the ``analyze`` command of Macaron to run a number of :ref:`checks ` on the ``django`` package. In this tutorial, we are interested in the results of the ``mcn_detect_malicious_metadata_1`` check. Check :ref:`this tutorial ` if you would like to exclude other checks. + +.. code-block:: shell + + ./run_macaron.sh analyze -purl pkg:pypi/django@5.0.6 --skip-deps + +.. note:: By default, Macaron clones the repositories and creates output files under the ``output`` directory. To understand the structure of this directory please see :ref:`Output Files Guide `. + +.. code-block:: shell + + open output/reports/pypi/django/django.html + +.. _fig_django-malware-check: + +.. figure:: ../../_static/images/tutorial_django_5.0.6_detect_malicious_metadata_check.png + :alt: Check ``mcn_detect_malicious_metadata_1`` result for ``django@5.0.6`` + :align: center + +The image above shows the result of the ``mcn_detect_malicious_metadata_1`` check for ``django@5.0.6``. The check has passed, which means this package is not malicious. If a package is malicious, this check fails. If the ecosystem is not supported, the check returns ``UNKNOWN``. You can also see the result of individual heuristics applied in this check under the ``Justification`` column. + +Now we can write a policy to ensure that all versions of ``django`` pass the ``mcn_detect_malicious_metadata_1`` check. The policy will be enforced against the output of the ``analyze`` command that is cached in the local database at ``output/macaron.db``. + +.. code-block:: shell + + ./run_macaron.sh verify-policy --database output/macaron.db --file policy.dl + +Where the policy looks like below: + +.. code-block:: prolog + + #include "prelude.dl" + + Policy("check-django", component_id, "Check django artifacts.") :- + check_passed(component_id, "mcn_detect_malicious_metadata_1"). + + + apply_policy_to("check-django", component_id) :- + is_component(component_id, purl), + match("pkg:pypi/django@.*", purl). + +The ``match`` constraint in this policy allows us to apply the policy on all versions of ``django``. The result of this command should show that the policy succeeds with a zero exit code (if a policy fails to pass, Macaron returns a none-zero error code): + +.. code-block:: javascript + + passed_policies + ['check-django'] + component_satisfies_policy + ['1', 'pkg:pypi/django@5.0.6', 'check-django'] + failed_policies + component_violates_policy + +Note that the ``match`` constraint applies a regex pattern and can be expanded to ensure the ``mcn_detect_malicious_metadata_1`` check passes on all Python packages analyzed so far by Macaron: + +.. code-block:: prolog + + apply_policy_to("check-django", component_id) :- + is_component(component_id, purl), + match("pkg:pypi.*", purl). + ++++++++++++++++++++++++++++++++++++++++ +Verification Summary Attestation report ++++++++++++++++++++++++++++++++++++++++ + +Additionally, Macaron generates a Verification Summary Attestation (:term:`VSA`) report that contains the policy, and information about the analyzed artifact. See :ref:`this page ` for more details. For instance, the VSA report for the ``check-django`` policy shown above can be viewed by running this command: + +.. toggle:: + + .. code-block:: shell + + cat output/vsa.intoto.jsonl | jq -r '.payload' | base64 -d | jq + + .. code-block:: json + + { + "_type": "https://in-toto.io/Statement/v1", + "subject": [ + { + "uri": "pkg:pypi/django@5.0.6" + } + ], + "predicateType": "https://slsa.dev/verification_summary/v1", + "predicate": { + "verifier": { + "id": "https://github.com/oracle/macaron", + "version": { + "macaron": "0.11.0" + } + }, + "timeVerified": "2024-08-09T02:28:41.968492+00:00", + "resourceUri": "pkg:pypi/django@5.0.6", + "policy": { + "content": " #include \"prelude.dl\"\n\n Policy(\"check-django\", component_id, \"Check django artifacts.\") :-\n check_passed(component_id, \"mcn_detect_malicious_metadata_1\").\n\n\n apply_policy_to(\"check-django\", component_id) :-\n is_component(component_id, purl),\n match(\"pkg:pypi/django@.*\", purl)." + }, + "verificationResult": "PASSED", + "verifiedLevels": [] + } + } + +.. _django_with_deps: + +'''''''''''''''''''''''''''''''''' +Analyzing django with dependencies +'''''''''''''''''''''''''''''''''' + +Macaron supports analyzing a package's dependencies and performs the same set of checks on them as it does on the main target package. To analyze the dependencies of ``django@5.0.6`` Python package, you can either :ref:`generate an SBOM ` yourself or :ref:`point Macaron to a virtual environment ` where ``django`` is installed. + + +Let's assume ``/tmp/.django_venv`` is the virtual environment where ``django@5.0.6`` is installed. Run Macaron as follows to analyze ``django`` and its dependencies. + +.. code-block:: shell + + ./run_macaron.sh analyze -purl pkg:pypi/django@5.0.6 --python-venv "/tmp/.django_venv" + + +By default Macaron only checks the direct dependencies. To turn on recursive dependency analysis, add the following to the ``configurations.ini`` file: + +.. code-block:: ini + + [dependency.resolver] + recursive = True + +And pass that to the ``analyze`` command: + +.. code-block:: shell + + ./run_macaron.sh --defaults-path configurations.ini analyze -purl pkg:pypi/django@5.0.6 --python-venv "/tmp/.django_venv" + +To learn more about changing configurations see :ref:`here `. + +Now we can enforce the policy below to ensure that the ``mcn_detect_malicious_metadata_1`` check always passes on ``django`` and its dependencies, indicating that none of the dependencies have malicious behavior. + +.. code-block:: prolog + + #include "prelude.dl" + + Policy("check-dependencies", component_id, "Check the dependencies of django.") :- + transitive_dependency(component_id, dependency), + check_passed(component_id, "mcn_detect_malicious_metadata_1"), + check_passed(dependency, "mcn_detect_malicious_metadata_1"). + + apply_policy_to("check-dependencies", component_id) :- + is_component(component_id, purl), + match("pkg:pypi/django@.*", purl). + +As you can see below, the policy passes because Macaron doesn't detect malicious behavior for ``django`` or any of its transitive dependencies. + +.. code-block:: javascript + + passed_policies + ['check-dependencies'] + component_satisfies_policy + ['1', 'pkg:pypi/django@5.0.6', 'check-dependencies'] + failed_policies + component_violates_policy + +'''''''''''''''''''''''''''''''''''''''' +Require a confidence level in the policy +'''''''''''''''''''''''''''''''''''''''' + +Macaron also provides a confidence score for each check result, represented as a value ranging from ``0`` to ``1`` (inclusive). You can incorporate this score into your policy to ensure checks meet a required level of confidence. Currently, Macaron :class:`has these confidence levels `. For instance, you might adjust the :ref:`check-dependencies policy shown earlier ` to require that the ``mcn_detect_malicious_metadata_1`` check passes with a high confidence, i.e., ``1``: + +.. code-block:: prolog + + #include "prelude.dl" + + Policy("check-dependencies", component_id, "Check the dependencies of django with high confidence.") :- + transitive_dependency(component_id, dependency), + check_passed_with_confidence(component_id, "mcn_detect_malicious_metadata_1", confidence), + check_passed_with_confidence(dependency, "mcn_detect_malicious_metadata_1", confidence), + confidence = 1. + + apply_policy_to("check-dependencies", component_id) :- + is_component(component_id, purl), + match("pkg:pypi/django@.*", purl). + +*********** +Future Work +*********** + +We are actively working on the malware detection analysis check in Macaron — to improve precision, support more ecosystems, and in particular, perform more advanced source code analysis. Stay tuned and feel free to contribute to improve this check. diff --git a/docs/source/pages/tutorials/exclude_include_checks.rst b/docs/source/pages/tutorials/exclude_include_checks.rst index 98a39252e..3d7a86851 100644 --- a/docs/source/pages/tutorials/exclude_include_checks.rst +++ b/docs/source/pages/tutorials/exclude_include_checks.rst @@ -1,6 +1,8 @@ .. Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. .. Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. +.. _include_exclude_checks: + ===================================== Exclude and include checks in Macaron ===================================== diff --git a/docs/source/pages/tutorials/index.rst b/docs/source/pages/tutorials/index.rst index 05e52055a..2563fc95e 100644 --- a/docs/source/pages/tutorials/index.rst +++ b/docs/source/pages/tutorials/index.rst @@ -17,8 +17,9 @@ For the full list of supported technologies, such as CI services, registries, an .. toctree:: :maxdepth: 1 - detect_malicious_java_dep commit_finder - exclude_include_checks - generate_verification_summary_attestation + detect_malicious_package npm_provenance + detect_malicious_java_dep + generate_verification_summary_attestation + exclude_include_checks diff --git a/docs/source/pages/using.rst b/docs/source/pages/using.rst index 0e511a20b..be302378e 100644 --- a/docs/source/pages/using.rst +++ b/docs/source/pages/using.rst @@ -13,6 +13,98 @@ Using Macaron .. contents:: :local: +---------------------------------------- +Analyzing an artifact with a PURL string +---------------------------------------- + +Macaron can analyze an artifact (and its dependencies) to determine its supply chain security posture. To analyze an artifact, you need to provide the PURL identifier of the artifact: + + .. code-block:: + + pkg:/ + +Where ``artifact_details`` varies based on the provided ``package_type``. Examples for those currently supported by Macaron are as follows: + +.. list-table:: Examples of PURL strings for artifacts. + :widths: 50 50 + :header-rows: 1 + + * - Package Type + - PURL String + * - Maven (Java) + - ``pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1`` + * - PyPi (Python) + - ``pkg:pypi/django@1.11.1`` + * - Cargo (Rust) + - ``pkg:cargo/rand@0.7.2`` + * - NuGet (.Net) + - ``pkg:nuget/EnterpriseLibrary.Common@6.0.1304`` + * - NPM (NodeJS) + - ``pkg:npm/@angular/animation@12.3.1`` + +For more detailed information on converting a given artifact into a PURL, see `PURL Specification `_ and `PURL Types `_ + + +To run Macaron on an artifact, we use the following command: + +.. code-block:: shell + + ./run_macaron.sh analyze -purl + + +'''''''''''''''''''''''''''''''''''''' +Automated repository and commit finder +'''''''''''''''''''''''''''''''''''''' + +Macaron is capable of automatically determining the repository and exact commit that match a given artifact. For repository URLs, this is achieved through examination of SCM meta data found within artifact POM files (for Java), or use of Google's Open Source Insights API (for other languages). For commits, Macaron will attempt to match repository tags with the artifact version being sought, thereby requiring that the repository supports and uses tags on commits that were used for releases. + +By default, Macaron will try to discover the corresponding repository of an artifact unless it is already provided as input (as shown `later <#analyze-repo>`_). To disable or otherwise configure this behavior, or others, a custom ``.ini`` file should be passed to Macaron during execution. See `How to change the default configuration <#change-config>`_ for more details. + +For example, under the ``repofinder`` header, three options exist: ``find_repos``, ``use_open_source_insights``, and ``redirect_urls``: + +- ``find_repos`` (Values: True or False) - Enables or disables the Repository Finding feature. +- ``use_open_source_insights`` (Values: True or False) - Enables or disables use of Google's Open Source Insights API. +- ``redirect_urls`` (Values: List of URLs) - These are URLs that are known to redirect to actual repository URLs. + +To turn off the automatic source repo finding feature, change the following section in the configuration ``ini`` file: + +.. code-block:: ini + + [repofinder] + find_repos = False + +Within the configuration file under the ``repofinder.java`` header, three options exist: ``artifact_repositories``, ``repo_pom_paths``, ``find_parents``. These options behave as follows: + +- ``artifact_repositories`` (Values: List of URLs) - Determines the remote artifact repositories to attempt to retrieve dependency information from. +- ``repo_pom_paths`` (Values: List of POM tags) - Determines where to search for repository information in the POM files. E.g. scm.url. +- ``find_parents`` (Values: True or False) - When enabled, the Repository Finding feature will also search for repository URLs in parents POM files of the current dependency. + + +.. note:: Finding repositories requires at least one remote call, adding some additional overhead to an analysis run. + +.. note:: Google's Open Source Insights API is currently used to find repositories for: Python, Rust, .Net, NodeJS + +An example configuration file for utilising this feature: + +.. code-block:: ini + + [repofinder] + find_repos = True + use_open_source_insights = True + redirect_urls = + gitbox.apache.org + git-wip-us.apache.org + + [repofinder.java] + artifact_repositories = https://repo.maven.apache.org/maven2 + repo_pom_paths = + scm.url + scm.connection + scm.developerConnection + find_parents = True + +.. _analyze-repo: + ---------------------------------- Analyzing a source code repository ---------------------------------- @@ -21,7 +113,7 @@ Analyzing a source code repository Analyzing a public GitHub repository '''''''''''''''''''''''''''''''''''' -Macaron can analyze a GitHub public repository (and potentially the repositories of it dependencies) to determine its SLSA posture following the specification of `SLSA v0.1 `_. +Macaron can also analyze a public GitHub repository (and potentially the repositories of its dependencies). To run Macaron on a GitHub public repository, we use the following command: @@ -139,43 +231,6 @@ Analyzing a PURL (without an included version) and a repository path (with a dig ./run_macaron.sh analyze -purl -rp -b -d -'''''''''''''''''''''''''''''''''''''' -Providing an artifact as a PURL string -'''''''''''''''''''''''''''''''''''''' - -The PURL format supports artifacts as well as repositories, and Macaron supports (some of) these too. - -.. code-block:: - - pkg:/ - -Where ``artifact_details`` varies based on the provided ``package_type``. Examples for those currently supported by Macaron are as follows: - -.. list-table:: Examples of PURL strings for artifacts. - :widths: 50 50 - :header-rows: 1 - - * - Package Type - - PURL String - * - Maven (Java) - - ``pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1`` - * - PyPi (Python) - - ``pkg:pypi/django@1.11.1`` - * - Cargo (Rust) - - ``pkg:cargo/rand@0.7.2`` - * - NuGet (.Net) - - ``pkg:nuget/EnterpriseLibrary.Common@6.0.1304`` - * - NPM (NodeJS) - - ``pkg:npm/%40angular/animation@12.3.1`` - -For more detailed information on converting a given artifact into a PURL, see `PURL Specification `_ and `PURL Types `_ - -.. note:: If a repository is not also provided, Macaron will try to discover it based on the artifact purl. For this to work, ``find_repos`` in the configuration file **must be enabled**\. See `Analyzing more dependencies <#more-deps>`_ for more information about the configuration options of the Repository Finding feature. - -.. note:: If no repository is provided, but the PURL contains a version (as with all of the above examples), Macaron will attempt to find the exact commit that matches the provided version. For this to work, the discovered repository must support and make use of tags to denote commits relating to released artifacts. - - - ------------------------------------------------- Verifying provenance expectations in CUE language ------------------------------------------------- @@ -232,6 +287,8 @@ To run the analysis against that SBOM, run this command: Where ``path_to_sbom`` is the path to the SBOM you want to use. +.. _python-sbom: + '''''''''''''''''''''''' SBOM for Python projects '''''''''''''''''''''''' @@ -267,6 +324,8 @@ Then the analysis can be run as follows: Where ``path_to_sbom`` is the path to the SBOM you want to use. +.. _python-venv-deps: + ------------------------------------------------------- Analyzing dependencies using Python virtual environment ------------------------------------------------------- @@ -293,54 +352,6 @@ Alternatively, you can create an SBOM for the python package and provide it to M .. note:: We only support Python 3.11 for this feature of Macaron. Please make sure to install the package using this version of Python. -.. _more-deps: - -''''''''''''''''''''''''''' -Analyzing more dependencies -''''''''''''''''''''''''''' - -In some cases the dependencies that Macaron discovers lack a direct connection to a repository for it to analyze. To improve results in these instances, Macaron is capable of automatically determining the repository and exact commit that matches the given dependency. For repositories, this is achieved through examination of SCM meta data found within artifact POM files (for Java), or use of Google's Open Source Insights API (for other languages). For commits, Macaron will attempt to match repository tags with the artifact version being sought, thereby requiring that the repository supports and uses tags on commits that were used for releases. - -This feature is enabled by default. To disable, or configure its behaviour in other ways, a custom ``defaults.ini`` should be passed to Macaron during execution. - -See :ref:`dump-defaults `, the CLI command to dump the default configurations in ``defaults.ini``. After making changes, see :ref:`analyze ` CLI command for the option to pass the modified ``defaults.ini`` file. - -Within the configuration file under the ``repofinder.java`` header, three options exist: ``artifact_repositories``, ``repo_pom_paths``, ``find_parents``. These options behave as follows: - -- ``artifact_repositories`` (Values: List of URLs) - Determines the remote artifact repositories to attempt to retrieve dependency information from. -- ``repo_pom_paths`` (Values: List of POM tags) - Determines where to search for repository information in the POM files. E.g. scm.url. -- ``find_parents`` (Values: True or False) - When enabled, the Repository Finding feature will also search for repository URLs in parents POM files of the current dependency. - -Under the related header ``repofinder``, three more options exist: ``find_repos``, ``use_open_source_insights``, and ``redirect_urls``: - -- ``find_repos`` (Values: True or False) - Enables or disables the Repository Finding feature. -- ``use_open_source_insights`` (Values: True or False) - Enables or disables use of Google's Open Source Insights API. -- ``redirect_urls`` (Values: List of URLs) - These are URLs that are known to redirect to actual repository URLs. - -.. note:: Finding repositories requires at least one remote call, adding some additional overhead to an analysis run. - -.. note:: Google's Open Source Insights API is currently used to find repositories for: Python, Rust, .Net, NodeJS - -An example configuration file for utilising this feature: - -.. code-block:: ini - - [repofinder] - find_repos = True - use_open_source_insights = True - redirect_urls = - gitbox.apache.org - git-wip-us.apache.org - - [repofinder.java] - artifact_repositories = https://repo.maven.apache.org/maven2 - repo_pom_paths = - scm.url - scm.connection - scm.developerConnection - find_parents = True - - ----------------------------------------------- Analyzing a repository on the local file system @@ -480,3 +491,24 @@ Thanks to Datalog's expressive language model, it's easy to add exception rules requirement. For example, `the Mysql Connector/J `_ dependency in the Micronaut MuShop project does not pass the ``build_service`` check, but can be manually investigated and exempted if trusted. Overall, policies expressed in Datalog can be enforced by Macaron as part of your CI/CD pipeline to detect regressions or unexpected behavior. + +.. _change-config: + +----------------------------------- +Modifying the default configuration +----------------------------------- + +See :ref:`dump-defaults `, the CLI command to dump the default configurations in ``defaults.ini``. After making changes, see :ref:`analyze ` CLI command for the option to pass the modified ``defaults.ini`` file. + +For example, to turn off the automatic source repo finding feature, change the following section in the configuration ``ini`` file: + +.. code-block:: ini + + [repofinder] + find_repos = False + +Then run Macaron passing the modified configuration file: + +.. code-block:: shell + + ./run_macaron.sh -dp analyze -purl diff --git a/src/macaron/database/table_definitions.py b/src/macaron/database/table_definitions.py index fc010a74a..61c90da2e 100644 --- a/src/macaron/database/table_definitions.py +++ b/src/macaron/database/table_definitions.py @@ -449,7 +449,7 @@ class CheckFacts(ORMBase): component: Mapped["Component"] = relationship(back_populates="checkfacts") #: The foreign key to the check result. - check_result_id: Mapped[int] = mapped_column(String, ForeignKey("_check_result.id"), nullable=False) + check_result_id: Mapped[int] = mapped_column(Integer, ForeignKey("_check_result.id"), nullable=False) #: The column used as a mapper argument for distinguishing checks in polymorphic inheritance. check_type: Mapped[str] diff --git a/src/macaron/policy_engine/prelude/helper_rules.dl b/src/macaron/policy_engine/prelude/helper_rules.dl index 6e2df2a11..311f53285 100644 --- a/src/macaron/policy_engine/prelude/helper_rules.dl +++ b/src/macaron/policy_engine/prelude/helper_rules.dl @@ -12,6 +12,38 @@ check_passed(component_id, check_name) :- check_result(_, check_name, 1, compone .decl check_failed(component_id: number, check_name: symbol) check_failed(component_id, check_name) :- check_result(_, check_name, 0, component_id). + +/** + * This relation provides the passed checks together with the confidence score for a component. + Parameters: + component_id: number + The target software component id. + check_name: symbol + The check name that matches the ``^mcn_([a-z]+_)+([0-9]+)$`` regular expression. + confidence: float + The confidence score computed for the check result. + */ +.decl check_passed_with_confidence(component_id: number, check_name: symbol, confidence: float) +check_passed_with_confidence(component_id, check_name, confidence) :- + check_result(check_result_id, check_name, 1, component_id), + check_facts(_, confidence, component_id, check_result_id, _). + +/** + * This relation provides the failed checks together with the confidence score for a component. + Parameters: + component_id: number + The target software component id. + check_name: symbol + The check name that matches the ``^mcn_([a-z]+_)+([0-9]+)$`` regular expression. + confidence: float + The confidence score computed for the check result. + */ +.decl check_failed_with_confidence(component_id: number, check_name: symbol, confidence: float) +check_failed_with_confidence(component_id, check_name, confidence) :- + check_result(check_result_id, check_name, 0, component_id), + check_facts(_, confidence, component_id ,check_result_id, _). + + /** * Check name is valid. */ diff --git a/src/macaron/slsa_analyzer/checks/check_result.py b/src/macaron/slsa_analyzer/checks/check_result.py index 971d8d718..5e7193099 100644 --- a/src/macaron/slsa_analyzer/checks/check_result.py +++ b/src/macaron/slsa_analyzer/checks/check_result.py @@ -2,6 +2,7 @@ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. """This module contains the CheckResult class for storing the result of a check.""" +import json from dataclasses import dataclass from enum import Enum from typing import TypedDict @@ -204,7 +205,10 @@ def justification_report(self) -> list[tuple[Confidence, list]]: if col.info.get("justification") == JustificationType.HREF: dict_elements[col.name] = column_value elif col.info.get("justification") == JustificationType.TEXT: - list_elements.append(f"{col.name}: {column_value}") + if isinstance(column_value, dict): + list_elements.append(f"{col.name}: {json.dumps(column_value)}") + else: + list_elements.append(f"{col.name}: {column_value}") # Add the dictionary elements to the list of justification elements. if dict_elements: 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 d8755cddf..7e387b52d 100644 --- a/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py +++ b/src/macaron/slsa_analyzer/checks/detect_malicious_metadata_check.py @@ -141,16 +141,15 @@ class MaliciousMetadataFacts(CheckFacts): class DetectMaliciousMetadataCheck(BaseCheck): - """This check analyzes the metadata of the pypi package based on seven heuristics.""" + """This check analyzes the metadata of a package for malicious behavior.""" def __init__(self) -> None: """Initialize a check instance.""" check_id = "mcn_detect_malicious_metadata_1" - description = "Check whether the features of package adhere to the heuristics." - super().__init__( - check_id=check_id, - description=description, - ) + description = """This check analyzes the metadata of a package based on reports malicious behavior. + Supported ecosystem: PyPI. + """ + super().__init__(check_id=check_id, description=description, eval_reqs=[]) def _should_skip( self, results: dict[Heuristics, HeuristicResult], depends_on: list[tuple[Heuristics, HeuristicResult]] diff --git a/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/configurations.ini b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/configurations.ini new file mode 100644 index 000000000..ffd372c7d --- /dev/null +++ b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/configurations.ini @@ -0,0 +1,5 @@ +# Copyright (c) 2024 - 2024, 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/. + +[dependency.resolver] +recursive = True diff --git a/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-all-pypi.dl b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-all-pypi.dl new file mode 100644 index 000000000..7c72696b7 --- /dev/null +++ b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-all-pypi.dl @@ -0,0 +1,11 @@ +/* Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. */ +/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */ + +#include "prelude.dl" + +Policy("all-pypi", component_id, "Check all the Python packages.") :- + check_passed(component_id, "mcn_detect_malicious_metadata_1"). + +apply_policy_to("all-pypi", component_id) :- + is_component(component_id, purl), + match("pkg:pypi.*", purl). diff --git a/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-recursive-deps-confidence.dl b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-recursive-deps-confidence.dl new file mode 100644 index 000000000..8f0599d6f --- /dev/null +++ b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-recursive-deps-confidence.dl @@ -0,0 +1,14 @@ +/* Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. */ +/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */ + +#include "prelude.dl" + +Policy("check-dependencies", component_id, "Check the dependencies of django with high confidence.") :- + transitive_dependency(component_id, dependency), + check_passed_with_confidence(component_id, "mcn_detect_malicious_metadata_1", confidence), + check_passed_with_confidence(dependency, "mcn_detect_malicious_metadata_1", confidence), + confidence = 1. + +apply_policy_to("check-dependencies", component_id) :- + is_component(component_id, purl), + match("pkg:pypi/django@.*", purl). diff --git a/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-recursive-deps.dl b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-recursive-deps.dl new file mode 100644 index 000000000..8a64bafe7 --- /dev/null +++ b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/policy-recursive-deps.dl @@ -0,0 +1,13 @@ +/* Copyright (c) 2024 - 2024, Oracle and/or its affiliates. All rights reserved. */ +/* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */ + +#include "prelude.dl" + +Policy("check-dependencies", component_id, "Check the dependencies of django.") :- + transitive_dependency(component_id, dependency), + check_passed(component_id, "mcn_detect_malicious_metadata_1"), + check_passed(dependency, "mcn_detect_malicious_metadata_1"). + +apply_policy_to("check-dependencies", component_id) :- + is_component(component_id, purl), + match("pkg:pypi/django@.*", purl). diff --git a/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/test.yaml b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/test.yaml index d40848ca3..d60c044cf 100644 --- a/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/test.yaml +++ b/tests/integration/cases/django_with_dep_resolution_virtual_env_as_input/test.yaml @@ -10,6 +10,10 @@ tags: - tutorial steps: +- name: Clean up the virtual environment if it exists. + kind: shell + options: + cmd: rm -rf ./django_venv - name: Create virtual environment. kind: shell options: @@ -36,7 +40,34 @@ steps: kind: policy_report result: output/policy_report.json expected: policy_report.json -- name: Clean up the virtual environment +- name: Clean up the database. + kind: shell + options: + cmd: rm -f output/macaron.db +- name: Run macaron analyze on deps recursively + kind: analyze + options: + main_args: + - --defaults-path + - configurations.ini + command_args: + - -purl + - pkg:pypi/django@5.0.6 + - --python-venv + - ./django_venv +- name: Run macaron verify-policy to check for all transitive dependencies. + kind: verify + options: + policy: policy-recursive-deps.dl +- name: Run macaron verify-policy to check for all transitive dependencies with high confidence. + kind: verify + options: + policy: policy-recursive-deps-confidence.dl +- name: Run macaron verify-policy to check for all Python packages. + kind: verify + options: + policy: policy-all-pypi.dl +- name: Clean up the virtual environment. kind: shell options: cmd: rm -rf ./django_venv diff --git a/tests/policy_engine/resources/facts/macaron.db.gz b/tests/policy_engine/resources/facts/macaron.db.gz index 8a36917cb..0da56da72 100644 Binary files a/tests/policy_engine/resources/facts/macaron.db.gz and b/tests/policy_engine/resources/facts/macaron.db.gz differ diff --git a/tests/policy_engine/resources/policies/valid/testpolicy.dl b/tests/policy_engine/resources/policies/valid/testpolicy.dl index 628d11ec9..48287de36 100644 --- a/tests/policy_engine/resources/policies/valid/testpolicy.dl +++ b/tests/policy_engine/resources/policies/valid/testpolicy.dl @@ -1,4 +1,4 @@ -/* Copyright (c) 2023 - 2023, Oracle and/or its affiliates. All rights reserved. */ +/* Copyright (c) 2023 - 2024, Oracle and/or its affiliates. All rights reserved. */ /* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. */ #include "prelude.dl" @@ -12,22 +12,23 @@ Policy("trusted_builder", repo_id, name) :- Policy("trusted_builder", repo_id, name) :- repository_analysis(_, component_id, repo_id, name), - check_facts(check_id, component_id, result_id, _), - build_service_check(check_id, build_tool_name, ci_service, build_trigger, - build_command, build_status_url), + check_facts(check_id, _, component_id, _, _), + check_result(check_id, _, passed, component_id), + build_service_check(check_id, build_tool_name, _, _, _, _, _, _, _), passed = 1, match("github-actions", build_tool_name). Policy("trusted_builder", repo_id, name) :- repository_analysis(_, component_id, repo_id, name), - check_facts(check_id, component_id, result_id, _), - build_script_check(check_id, build_tool_name), + check_facts(check_id, _, component_id, _, _), + check_result(check_id, _, passed, component_id), + build_script_check(check_id, build_tool_name, _, _, _, _, _, _, _), passed = 1, match("github-actions", build_tool_name). apply_policy_to("trusted_builder", repo_id) :- - repository_analysis(_, component_id, repo_id, name), + repository_analysis(_, component_id, repo_id, _), provenance(_, component_id, _, _, _, _). apply_policy_to("aggregate_l4", repo_id) :-