diff --git a/index.json b/index.json index 1f03e0a..422ba74 100644 --- a/index.json +++ b/index.json @@ -1,11 +1,16 @@ { - "updated_at": "2026-06-18T11:53:07Z", + "updated_at": "2026-06-18T14:55:20Z", "packages": [ { "id": "pkg:maven/org.eclipse.jetty/jetty-http", "location": "pkg/maven/org.eclipse.jetty/jetty-http/scan.openvex.json", "format": "openvex" }, + { + "id": "pkg:oci/opentelemetry-target-allocator", + "location": "pkg/oci/opentelemetry-target-allocator/scan.openvex.json", + "format": "openvex" + }, { "id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io%2Fstackstate%2Fopentelemetry-target-allocator", "location": "pkg/oci/opentelemetry-target-allocator/scan.openvex.json", @@ -16,6 +21,11 @@ "location": "pkg/oci/opentelemetry-target-allocator/scan.openvex.json", "format": "openvex" }, + { + "id": "pkg:oci/stackstate-k8s-agent", + "location": "pkg/oci/stackstate-k8s-agent/scan.openvex.json", + "format": "openvex" + }, { "id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", "location": "pkg/oci/stackstate-k8s-agent/scan.openvex.json", diff --git a/pkg/oci/opentelemetry-target-allocator/scan.openvex.json b/pkg/oci/opentelemetry-target-allocator/scan.openvex.json index 3256910..e14a915 100644 --- a/pkg/oci/opentelemetry-target-allocator/scan.openvex.json +++ b/pkg/oci/opentelemetry-target-allocator/scan.openvex.json @@ -8,23 +8,32 @@ "vulnerability": { "name": "CVE-2026-34040", "aliases": [ - "GHSA-x744-4wpc-v9h2" + "GHSA-x744-4wpc-v9h2", + "GO-2026-4887" ] }, "products": [ { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io%2Fstackstate%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io/stackstate/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] }, { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com%2Fsuse-observability%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com/suse-observability/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" + } + ] + }, + { + "@id": "pkg:oci/opentelemetry-target-allocator", + "subcomponents": [ + { + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] } @@ -43,18 +52,26 @@ }, "products": [ { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io%2Fstackstate%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io/stackstate/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] }, { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com%2Fsuse-observability%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com/suse-observability/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" + } + ] + }, + { + "@id": "pkg:oci/opentelemetry-target-allocator", + "subcomponents": [ + { + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] } @@ -73,18 +90,26 @@ }, "products": [ { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io%2Fstackstate%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io/stackstate/opentelemetry-target-allocator", + "subcomponents": [ + { + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" + } + ] + }, + { + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com/suse-observability/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] }, { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com%2Fsuse-observability%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] } @@ -98,23 +123,32 @@ "vulnerability": { "name": "CVE-2026-33997", "aliases": [ - "GHSA-pxq6-2prw-chj9" + "GHSA-pxq6-2prw-chj9", + "GO-2026-4883" ] }, "products": [ { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io%2Fstackstate%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io/stackstate/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] }, { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com%2Fsuse-observability%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com/suse-observability/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" + } + ] + }, + { + "@id": "pkg:oci/opentelemetry-target-allocator", + "subcomponents": [ + { + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] } @@ -133,18 +167,26 @@ }, "products": [ { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io%2Fstackstate%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=quay.io/stackstate/opentelemetry-target-allocator", + "subcomponents": [ + { + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" + } + ] + }, + { + "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com/suse-observability/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] }, { - "@id": "pkg:oci/opentelemetry-target-allocator?repository_url=registry.rancher.com%2Fsuse-observability%2Fopentelemetry-target-allocator", + "@id": "pkg:oci/opentelemetry-target-allocator", "subcomponents": [ { - "@id": "pkg:golang/github.com/docker/docker@v28.5.2%2Bincompatible" + "@id": "pkg:golang/github.com/docker/docker@v28.5.2+incompatible" } ] } diff --git a/pkg/oci/stackstate-k8s-agent/scan.openvex.json b/pkg/oci/stackstate-k8s-agent/scan.openvex.json index 667ddf7..92a83fd 100644 --- a/pkg/oci/stackstate-k8s-agent/scan.openvex.json +++ b/pkg/oci/stackstate-k8s-agent/scan.openvex.json @@ -10,7 +10,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -18,7 +18,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -38,7 +46,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -46,7 +54,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -67,7 +83,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -75,7 +91,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -92,7 +116,15 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -100,7 +132,7 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -118,7 +150,15 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -126,7 +166,7 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -144,7 +184,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -152,7 +192,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -169,7 +217,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -177,7 +225,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -195,7 +251,15 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -203,7 +267,7 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -221,7 +285,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -229,7 +293,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -247,7 +319,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -255,7 +327,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -274,7 +354,7 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -282,7 +362,15 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -303,7 +391,15 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -311,7 +407,7 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -330,7 +426,15 @@ }, "products": [ { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io%2Fstackstate%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=quay.io/stackstate/stackstate-k8s-agent", + "subcomponents": [ + { + "@id": "pkg:generic/python@3.13.13" + } + ] + }, + { + "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com/suse-observability/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" @@ -338,7 +442,7 @@ ] }, { - "@id": "pkg:oci/stackstate-k8s-agent?repository_url=registry.rancher.com%2Fsuse-observability%2Fstackstate-k8s-agent", + "@id": "pkg:oci/stackstate-k8s-agent", "subcomponents": [ { "@id": "pkg:generic/python@3.13.13" diff --git a/tools/build_index.py b/tools/build_index.py index 3427a43..65670e6 100644 --- a/tools/build_index.py +++ b/tools/build_index.py @@ -33,34 +33,22 @@ def _where(source: Path | None) -> str: return f" in {source}" if source else "" -def _ensure_percent_encoded(value: str, key: str, purl: str, source: Path | None) -> None: - """Reject qualifier values that are not fully percent-encoded. - - PURL qualifier values must percent-encode anything outside the RFC 3986 - unreserved set (``[A-Za-z0-9-._~]``). For ``repository_url`` that means - every ``/`` must appear as ``%2F`` (and every ``:`` in a non-default - port as ``%3A``). We normalise by decoding then re-encoding with - ``quote(..., safe="")``; if the result differs from the input (case - insensitively, since ``%2f`` and ``%2F`` are equivalent), the input - was not properly encoded. - """ - canonical = quote(unquote(value), safe="") - if canonical.lower() != value.lower(): - sys.exit( - f"PURL qualifier {key}={value!r} in {purl!r}{_where(source)} " - f"is not properly percent-encoded. Expected {key}={canonical}." - ) - - def _parse_purl(purl: str, source: Path | None) -> tuple[str, dict[str, str]]: """Parse a PURL into (head, qualifiers). ``head`` is ``pkg://`` with version and subpath - stripped. ``qualifiers`` preserves the original (validated) encoding so - callers can re-emit byte-identical strings. - - Grammar enforced: ``pkg:type/namespace/name@version?qualifiers#subpath`` - with qualifier values percent-encoded per RFC 3986. + stripped. Qualifier values are returned **decoded** so callers can + canonicalise the on-wire encoding when emitting an index id. + + Authors may write qualifier values in either form inside + ``scan.openvex.json`` -- ``repository_url=quay.io/stackstate/foo`` or + ``repository_url=quay.io%2Fstackstate%2Ffoo``. OpenVEX consumers + (Trivy's statement matcher, Grype, vexctl) normalise PURLs before + comparing, so both forms are equivalent at match time; we follow the + convention used by other public vexhubs and recommend the unencoded + form in VEX documents for readability. The ``index.json`` lookup is + string-based, so this script always emits the canonical percent- + encoded form there. """ if not purl.startswith("pkg:"): sys.exit(f"{purl!r}{_where(source)} is not a PURL (must start with 'pkg:').") @@ -85,8 +73,7 @@ def _parse_purl(purl: str, source: Path | None) -> tuple[str, dict[str, str]]: f"PURL qualifier key {key!r} in {purl!r}{_where(source)} " "is invalid (must be lowercase ASCII identifier)." ) - _ensure_percent_encoded(value, key, purl, source) - qualifiers[key] = value + qualifiers[key] = unquote(value) if "@" in head: head, _ = head.split("@", 1) return head, qualifiers @@ -95,29 +82,26 @@ def _parse_purl(purl: str, source: Path | None) -> tuple[str, dict[str, str]]: def index_id_for_purl(purl: str, source: Path | None = None) -> str: """Return the canonical index id for a PURL. - Per the VEX Repository Specification, version, subpath, and qualifiers - are stripped from the index id; for ``pkg:oci/*`` PURLs the - ``repository_url`` qualifier MUST be preserved (and must be - percent-encoded) so Trivy can match the entry against the image PURL it - generates at scan time. + Per the VEX Repository Specification, version and subpath are stripped + from the index id. For ``pkg:oci/*`` PURLs the ``repository_url`` + qualifier is preserved when present (and percent-encoded) so Trivy + can match the entry against the image PURL it generates at scan time; + when absent the index id is the bare ``pkg:oci/`` form. Bare + OCI products are how Grype matches: its image PURL has no + ``repository_url`` qualifier, so a bare product entry in the VEX file + is what lets a Grype scan apply the statement. Statements that need + to land in both scanners should list both the qualified and the bare + form in ``products[]``. """ head, qualifiers = _parse_purl(purl, source) - if head.startswith("pkg:oci/"): - if "repository_url" not in qualifiers: - sys.exit( - f"OCI PURL {purl!r}{_where(source)} is missing the required " - "'repository_url' qualifier. Per the VEX Repository " - "Specification, OCI product @id values must include " - "?repository_url=// with " - "slashes percent-encoded as %2F." - ) + if head.startswith("pkg:oci/") and "repository_url" in qualifiers: repo = qualifiers["repository_url"] if not repo: sys.exit( f"OCI PURL {purl!r}{_where(source)} has an empty " "'repository_url' qualifier." ) - return f"{head}?repository_url={repo}" + return f"{head}?repository_url={quote(repo, safe='')}" return head