diff --git a/antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc b/antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc index 09e489840..c3ee9072d 100644 --- a/antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc +++ b/antora/docs/modules/ROOT/pages/packages/release_sbom_cyclonedx.adoc @@ -84,6 +84,19 @@ Confirm the CycloneDX SBOM contains only packages without disallowed external re * Effective from: `2024-07-31T00:00:00Z` * https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/sbom_cyclonedx/sbom_cyclonedx.rego#L197[Source, window="_blank"] +[#sbom_cyclonedx__experimental_hermeto_backend] +=== link:#sbom_cyclonedx__experimental_hermeto_backend[Experimental Hermeto backend] + +Verify that no components in the CycloneDX SBOM were fetched using an experimental Hermeto backend. Experimental backends are identified by top-level annotations whose text starts with "hermeto:backend:experimental:". + +*Solution*: Use a supported, non-experimental package manager backend in your build process, or request a policy exception. + +* Rule type: [rule-type-indicator failure]#FAILURE# +* FAILURE message: `Package %s was fetched using experimental Hermeto backend %q` +* Code: `sbom_cyclonedx.experimental_hermeto_backend` +* Effective from: `2026-08-01T00:00:00Z` +* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/sbom_cyclonedx/sbom_cyclonedx.rego#L366[Source, window="_blank"] + [#sbom_cyclonedx__proxy_metadata_required] === link:#sbom_cyclonedx__proxy_metadata_required[Proxy metadata required] diff --git a/antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc b/antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc index fcd3245f8..e66d11e94 100644 --- a/antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc +++ b/antora/docs/modules/ROOT/pages/packages/release_sbom_spdx.adoc @@ -108,6 +108,19 @@ Confirm the SPDX SBOM contains only packages without disallowed external referen * Effective from: `2024-07-31T00:00:00Z` * https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/sbom_spdx/sbom_spdx.rego#L106[Source, window="_blank"] +[#sbom_spdx__experimental_hermeto_backend] +=== link:#sbom_spdx__experimental_hermeto_backend[Experimental Hermeto backend] + +Verify that no packages in the SPDX SBOM were fetched using an experimental Hermeto backend. Experimental backends are identified by annotations with annotator "Tool: hermeto:backend" whose comment starts with "hermeto:backend:experimental:". + +*Solution*: Use a supported, non-experimental package manager backend in your build process, or request a policy exception. + +* Rule type: [rule-type-indicator failure]#FAILURE# +* FAILURE message: `Package %s was fetched using experimental Hermeto backend %q` +* Code: `sbom_spdx.experimental_hermeto_backend` +* Effective from: `2026-08-01T00:00:00Z` +* https://github.com/conforma/policy/blob/{page-origin-refhash}/policy/release/sbom_spdx/sbom_spdx.rego#L359[Source, window="_blank"] + [#sbom_spdx__matches_image] === link:#sbom_spdx__matches_image[Matches image] diff --git a/antora/docs/modules/ROOT/pages/release_policy.adoc b/antora/docs/modules/ROOT/pages/release_policy.adoc index c0b1814e1..3eaf94f36 100644 --- a/antora/docs/modules/ROOT/pages/release_policy.adoc +++ b/antora/docs/modules/ROOT/pages/release_policy.adoc @@ -43,6 +43,7 @@ Rules included: * xref:packages/release_cve.adoc#cve__rule_data_provided[CVE checks: Rule data provided] * xref:packages/release_provenance_materials.adoc#provenance_materials__git_clone_source_matches_provenance[Provenance Materials: Git clone source matches materials provenance] * xref:packages/release_provenance_materials.adoc#provenance_materials__git_clone_task_found[Provenance Materials: Git clone task found] +* xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__experimental_hermeto_backend[SBOM CycloneDX: Experimental Hermeto backend] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__cdx_supported_version[SBOM CycloneDX: Supported Version] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__valid_cdx_1_4[SBOM CycloneDX: Valid 1.4] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__valid_cdx_1_5[SBOM CycloneDX: Valid 1.5] @@ -57,6 +58,7 @@ Rules included: * xref:packages/release_slsa_source_correlated.adoc#slsa_source_correlated__rule_data_provided[SLSA - Verification model - Source: Rule data provided] * xref:packages/release_slsa_source_correlated.adoc#slsa_source_correlated__source_code_reference_provided[SLSA - Verification model - Source: Source code reference provided] * xref:packages/release_slsa_source_correlated.adoc#slsa_source_correlated__attested_source_code_reference[SLSA - Verification model - Source: Source reference] +* xref:packages/release_sbom_spdx.adoc#sbom_spdx__experimental_hermeto_backend[SPDX SBOM: Experimental Hermeto backend] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__valid[SPDX SBOM: Valid] * xref:packages/release_tasks.adoc#tasks__pipeline_has_tasks[Tasks: Pipeline run includes at least one task] * xref:packages/release_tasks.adoc#tasks__successful_pipeline_tasks[Tasks: Successful pipeline tasks] @@ -171,6 +173,7 @@ Rules included: * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__allowed_proxy_urls[SBOM CycloneDX: Allowed proxy URLs] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__disallowed_package_attributes[SBOM CycloneDX: Disallowed package attributes] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__disallowed_package_external_references[SBOM CycloneDX: Disallowed package external references] +* xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__experimental_hermeto_backend[SBOM CycloneDX: Experimental Hermeto backend] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__proxy_metadata_required[SBOM CycloneDX: Proxy metadata required] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__cdx_supported_version[SBOM CycloneDX: Supported Version] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__valid_cdx_1_4[SBOM CycloneDX: Valid 1.4] @@ -200,6 +203,7 @@ Rules included: * xref:packages/release_sbom_spdx.adoc#sbom_spdx__allowed_proxy_urls[SPDX SBOM: Allowed proxy URLs] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__disallowed_package_attributes[SPDX SBOM: Disallowed package attributes] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__disallowed_package_external_references[SPDX SBOM: Disallowed package external references] +* xref:packages/release_sbom_spdx.adoc#sbom_spdx__experimental_hermeto_backend[SPDX SBOM: Experimental Hermeto backend] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__proxy_metadata_required[SPDX SBOM: Proxy metadata required] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__valid[SPDX SBOM: Valid] * xref:packages/release_schedule.adoc#schedule__date_restriction[Schedule related checks: Date Restriction] @@ -280,6 +284,7 @@ Rules included: * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__allowed_package_sources[SBOM CycloneDX: Allowed package sources] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__disallowed_package_attributes[SBOM CycloneDX: Disallowed package attributes] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__disallowed_package_external_references[SBOM CycloneDX: Disallowed package external references] +* xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__experimental_hermeto_backend[SBOM CycloneDX: Experimental Hermeto backend] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__cdx_supported_version[SBOM CycloneDX: Supported Version] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__valid_cdx_1_4[SBOM CycloneDX: Valid 1.4] * xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__valid_cdx_1_5[SBOM CycloneDX: Valid 1.5] @@ -302,6 +307,7 @@ Rules included: * xref:packages/release_sbom_spdx.adoc#sbom_spdx__allowed_package_sources[SPDX SBOM: Allowed package sources] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__disallowed_package_attributes[SPDX SBOM: Disallowed package attributes] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__disallowed_package_external_references[SPDX SBOM: Disallowed package external references] +* xref:packages/release_sbom_spdx.adoc#sbom_spdx__experimental_hermeto_backend[SPDX SBOM: Experimental Hermeto backend] * xref:packages/release_sbom_spdx.adoc#sbom_spdx__valid[SPDX SBOM: Valid] * xref:packages/release_tasks.adoc#tasks__required_untrusted_task_found[Tasks: All required tasks are from trusted tasks] * xref:packages/release_tasks.adoc#tasks__data_provided[Tasks: Data provided] diff --git a/antora/docs/modules/ROOT/partials/release_policy_nav.adoc b/antora/docs/modules/ROOT/partials/release_policy_nav.adoc index 73ea29b79..4f4365e1b 100644 --- a/antora/docs/modules/ROOT/partials/release_policy_nav.adoc +++ b/antora/docs/modules/ROOT/partials/release_policy_nav.adoc @@ -113,6 +113,7 @@ **** xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__allowed_proxy_urls[Allowed proxy URLs] **** xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__disallowed_package_attributes[Disallowed package attributes] **** xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__disallowed_package_external_references[Disallowed package external references] +**** xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__experimental_hermeto_backend[Experimental Hermeto backend] **** xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__proxy_metadata_required[Proxy metadata required] **** xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__cdx_supported_version[Supported Version] **** xref:packages/release_sbom_cyclonedx.adoc#sbom_cyclonedx__valid_cdx_1_4[Valid 1.4] @@ -148,6 +149,7 @@ **** xref:packages/release_sbom_spdx.adoc#sbom_spdx__contains_packages[Contains packages] **** xref:packages/release_sbom_spdx.adoc#sbom_spdx__disallowed_package_attributes[Disallowed package attributes] **** xref:packages/release_sbom_spdx.adoc#sbom_spdx__disallowed_package_external_references[Disallowed package external references] +**** xref:packages/release_sbom_spdx.adoc#sbom_spdx__experimental_hermeto_backend[Experimental Hermeto backend] **** xref:packages/release_sbom_spdx.adoc#sbom_spdx__matches_image[Matches image] **** xref:packages/release_sbom_spdx.adoc#sbom_spdx__proxy_metadata_required[Proxy metadata required] **** xref:packages/release_sbom_spdx.adoc#sbom_spdx__valid[Valid] diff --git a/policy/release/sbom_cyclonedx/sbom_cyclonedx.rego b/policy/release/sbom_cyclonedx/sbom_cyclonedx.rego index b45111b7b..3141bc37e 100644 --- a/policy/release/sbom_cyclonedx/sbom_cyclonedx.rego +++ b/policy/release/sbom_cyclonedx/sbom_cyclonedx.rego @@ -363,6 +363,41 @@ deny contains result if { ) } +# METADATA +# title: Experimental Hermeto backend +# description: >- +# Verify that no components in the CycloneDX SBOM were fetched using an +# experimental Hermeto backend. Experimental backends are identified by +# top-level annotations whose text starts with "hermeto:backend:experimental:". +# custom: +# short_name: experimental_hermeto_backend +# failure_msg: Package %s was fetched using experimental Hermeto backend %q +# solution: >- +# Use a supported, non-experimental package manager backend in your build +# process, or request a policy exception. +# collections: +# - minimal +# - redhat +# - redhat_rpms +# effective_on: 2026-08-01T00:00:00Z +deny contains result if { + some s in sbom.cyclonedx_sboms + some annotation in s.annotations + startswith(annotation.text, "hermeto:backend:experimental:") + + some subject in annotation.subjects + some component in s.components + component["bom-ref"] == subject + + id := object.get(component, "purl", component.name) + + result := metadata.result_helper_with_term( + rego.metadata.chain(), + [id, annotation.text], + id, + ) +} + _has_distribution_reference(component) if { some reference in component.externalReferences reference.type == "distribution" diff --git a/policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego b/policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego index ebb1e7d9f..7df31d41c 100644 --- a/policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego +++ b/policy/release/sbom_cyclonedx/sbom_cyclonedx_test.rego @@ -1195,3 +1195,131 @@ test_proxy_metadata_required_cdx_vcs_url_passes if { with ec.oci.image_tag_refs as [] with data.rule_data as _proxy_rule_data } + +# experimental_hermeto_backend tests + +test_experimental_hermeto_backend_cdx_denied if { + expected := {{ + "code": "sbom_cyclonedx.experimental_hermeto_backend", + "term": "pkg:golang/example.com/foo@1.0.0", + # regal ignore:line-length + "msg": `Package pkg:golang/example.com/foo@1.0.0 was fetched using experimental Hermeto backend "hermeto:backend:experimental:x-pnpm"`, + }} + + att := json.patch(_sbom_1_5_attestation, [ + { + "op": "add", + "path": "/statement/predicate/components/-", + "value": _cdx_backend_component("pkg:golang/example.com/foo@1.0.0"), + }, + { + "op": "add", + "path": "/statement/predicate/annotations", + "value": [_cdx_backend_annotation("pkg:golang/example.com/foo@1.0.0", "hermeto:backend:experimental:x-pnpm")], + }, + ]) + + assertions.assert_equal_results(expected, sbom_cyclonedx.deny) with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] +} + +test_experimental_hermeto_backend_cdx_stable_passes if { + att := json.patch(_sbom_1_5_attestation, [ + { + "op": "add", + "path": "/statement/predicate/components/-", + "value": _cdx_backend_component("pkg:golang/example.com/foo@1.0.0"), + }, + { + "op": "add", + "path": "/statement/predicate/annotations", + "value": [_cdx_backend_annotation("pkg:golang/example.com/foo@1.0.0", "hermeto:backend:gomod")], + }, + ]) + + results := sbom_cyclonedx.deny with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] + + count({r | some r in results; r.code == "sbom_cyclonedx.experimental_hermeto_backend"}) == 0 +} + +test_experimental_hermeto_backend_cdx_no_purl_denied if { + expected := {{ + "code": "sbom_cyclonedx.experimental_hermeto_backend", + "term": "component-no-purl", + # regal ignore:line-length + "msg": `Package component-no-purl was fetched using experimental Hermeto backend "hermeto:backend:experimental:x-pnpm"`, + }} + + att := json.patch(_sbom_1_5_attestation, [ + { + "op": "add", + "path": "/statement/predicate/components/-", + "value": _cdx_backend_component_no_purl, + }, + { + "op": "add", + "path": "/statement/predicate/annotations", + "value": [_cdx_backend_annotation("component-no-purl-ref", "hermeto:backend:experimental:x-pnpm")], + }, + ]) + + assertions.assert_equal_results(expected, sbom_cyclonedx.deny) with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] +} + +test_experimental_hermeto_backend_cdx_mixed_annotations if { + expected := {{ + "code": "sbom_cyclonedx.experimental_hermeto_backend", + "term": "pkg:golang/example.com/foo@1.0.0", + # regal ignore:line-length + "msg": `Package pkg:golang/example.com/foo@1.0.0 was fetched using experimental Hermeto backend "hermeto:backend:experimental:x-pnpm"`, + }} + + att := json.patch(_sbom_1_5_attestation, [ + { + "op": "add", + "path": "/statement/predicate/components/-", + "value": _cdx_backend_component("pkg:golang/example.com/foo@1.0.0"), + }, + { + "op": "add", + "path": "/statement/predicate/annotations", + "value": [ + _cdx_backend_annotation("pkg:golang/example.com/foo@1.0.0", "hermeto:backend:gomod"), + _cdx_backend_annotation("pkg:golang/example.com/foo@1.0.0", "hermeto:backend:experimental:x-pnpm"), + ], + }, + ]) + + assertions.assert_equal_results(expected, sbom_cyclonedx.deny) with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] +} + +_cdx_backend_component(purl) := { + "bom-ref": purl, + "type": "library", + "name": "component", + "purl": purl, +} + +_cdx_backend_component_no_purl := { + "bom-ref": "component-no-purl-ref", + "type": "library", + "name": "component-no-purl", +} + +_cdx_backend_annotation(subject, text) := { + "subjects": [subject], + "annotator": {"organization": {"name": "red hat"}}, + "timestamp": "2026-05-01T12:00:00Z", + "text": text, +} diff --git a/policy/release/sbom_spdx/sbom_spdx.rego b/policy/release/sbom_spdx/sbom_spdx.rego index 66b6dd7ca..e44d0f154 100644 --- a/policy/release/sbom_spdx/sbom_spdx.rego +++ b/policy/release/sbom_spdx/sbom_spdx.rego @@ -356,11 +356,51 @@ deny contains result if { ) } +# METADATA +# title: Experimental Hermeto backend +# description: >- +# Verify that no packages in the SPDX SBOM were fetched using an experimental +# Hermeto backend. Experimental backends are identified by annotations with +# annotator "Tool: hermeto:backend" whose comment starts with +# "hermeto:backend:experimental:". +# custom: +# short_name: experimental_hermeto_backend +# failure_msg: Package %s was fetched using experimental Hermeto backend %q +# solution: >- +# Use a supported, non-experimental package manager backend in your build +# process, or request a policy exception. +# collections: +# - minimal +# - redhat +# - redhat_rpms +# effective_on: 2026-08-01T00:00:00Z +deny contains result if { + some s in sbom.spdx_sboms + some pkg in s.packages + + some annotation in pkg.annotations + annotation.annotator == "Tool: hermeto:backend" + startswith(annotation.comment, "hermeto:backend:experimental:") + + id := _spdx_package_id(pkg) + + result := metadata.result_helper_with_term( + rego.metadata.chain(), + [id, annotation.comment], + id, + ) +} + _package_purl(pkg) := purl if { purls := [ref.referenceLocator | some ref in pkg.externalRefs; ref.referenceType == "purl"] purl := purls[0] } else := "" +_spdx_package_id(pkg) := purl if { + purl := _package_purl(pkg) + purl != "" +} else := pkg.name + # _with_effective_on annotates the result with the item's effective_on attribute. If the item does # not have the attribute, result is returned unmodified. _with_effective_on(result, item) := new_result if { diff --git a/policy/release/sbom_spdx/sbom_spdx_test.rego b/policy/release/sbom_spdx/sbom_spdx_test.rego index 6d047351e..853e2378b 100644 --- a/policy/release/sbom_spdx/sbom_spdx_test.rego +++ b/policy/release/sbom_spdx/sbom_spdx_test.rego @@ -1021,3 +1021,137 @@ test_proxy_metadata_required_spdx_vcs_url_passes if { with ec.oci.image_tag_refs as [] with data.rule_data as _spdx_proxy_rule_data } + +# experimental_hermeto_backend tests + +test_experimental_hermeto_backend_spdx_denied if { + expected := {{ + "code": "sbom_spdx.experimental_hermeto_backend", + "term": "pkg:golang/example.com/foo@1.0.0", + # regal ignore:line-length + "msg": `Package pkg:golang/example.com/foo@1.0.0 was fetched using experimental Hermeto backend "hermeto:backend:experimental:x-pnpm"`, + }} + + att := json.patch(_sbom_attestation, [{ + "op": "add", + "path": "/statement/predicate/packages/-", + "value": _spdx_backend_package("pkg:golang/example.com/foo@1.0.0", "hermeto:backend:experimental:x-pnpm"), + }]) + + assertions.assert_equal_results(expected, sbom_spdx.deny) with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] +} + +test_experimental_hermeto_backend_spdx_stable_passes if { + att := json.patch(_sbom_attestation, [{ + "op": "add", + "path": "/statement/predicate/packages/-", + "value": _spdx_backend_package("pkg:golang/example.com/foo@1.0.0", "hermeto:backend:gomod"), + }]) + + results := sbom_spdx.deny with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] + + count({r | some r in results; r.code == "sbom_spdx.experimental_hermeto_backend"}) == 0 +} + +test_experimental_hermeto_backend_spdx_no_purl_denied if { + expected := {{ + "code": "sbom_spdx.experimental_hermeto_backend", + "term": "backend-package", + # regal ignore:line-length + "msg": `Package backend-package was fetched using experimental Hermeto backend "hermeto:backend:experimental:x-pnpm"`, + }} + + att := json.patch(_sbom_attestation, [{ + "op": "add", + "path": "/statement/predicate/packages/-", + "value": _spdx_backend_package_no_purl("hermeto:backend:experimental:x-pnpm"), + }]) + + assertions.assert_equal_results(expected, sbom_spdx.deny) with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] +} + +test_experimental_hermeto_backend_spdx_mixed_annotations if { + expected := {{ + "code": "sbom_spdx.experimental_hermeto_backend", + "term": "pkg:golang/example.com/foo@1.0.0", + # regal ignore:line-length + "msg": `Package pkg:golang/example.com/foo@1.0.0 was fetched using experimental Hermeto backend "hermeto:backend:experimental:x-pnpm"`, + }} + + att := json.patch(_sbom_attestation, [{ + "op": "add", + "path": "/statement/predicate/packages/-", + "value": _spdx_backend_package_mixed("pkg:golang/example.com/foo@1.0.0"), + }]) + + assertions.assert_equal_results(expected, sbom_spdx.deny) with input.attestations as [att] + with input.image.ref as "registry.local/spam@sha256:1230000000000000000000000000000000000000000000000000000000000123" + with ec.oci.image_referrers as [] + with ec.oci.image_tag_refs as [] +} + +_spdx_backend_package_mixed(purl) := { + "name": "backend-package", + "SPDXID": "SPDXRef-backend-package-mixed", + "downloadLocation": "NOASSERTION", + "externalRefs": [{ + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": purl, + }], + "annotations": [ + { + "annotator": "Tool: hermeto:backend", + "comment": "hermeto:backend:gomod", + "annotationDate": "2026-05-01T12:00:00Z", + "annotationType": "OTHER", + }, + { + "annotator": "Tool: hermeto:backend", + "comment": "hermeto:backend:experimental:x-pnpm", + "annotationDate": "2026-05-01T12:00:00Z", + "annotationType": "OTHER", + }, + ], + "checksums": [{"algorithm": "SHA256", "checksumValue": "abc123"}], +} + +_spdx_backend_package(purl, backend_annotation) := { + "name": "backend-package", + "SPDXID": "SPDXRef-backend-package", + "downloadLocation": "NOASSERTION", + "externalRefs": [{ + "referenceCategory": "PACKAGE-MANAGER", + "referenceType": "purl", + "referenceLocator": purl, + }], + "annotations": [{ + "annotator": "Tool: hermeto:backend", + "comment": backend_annotation, + "annotationDate": "2026-05-01T12:00:00Z", + "annotationType": "OTHER", + }], + "checksums": [{"algorithm": "SHA256", "checksumValue": "abc123"}], +} + +_spdx_backend_package_no_purl(backend_annotation) := { + "name": "backend-package", + "SPDXID": "SPDXRef-backend-package-no-purl", + "downloadLocation": "NOASSERTION", + "annotations": [{ + "annotator": "Tool: hermeto:backend", + "comment": backend_annotation, + "annotationDate": "2026-05-01T12:00:00Z", + "annotationType": "OTHER", + }], + "checksums": [{"algorithm": "SHA256", "checksumValue": "abc123"}], +}