diff --git a/README.md b/README.md index 15f305bd..d2376e54 100644 --- a/README.md +++ b/README.md @@ -1042,13 +1042,15 @@ sbomify uses a plugin architecture for SBOM generation, automatically selecting Generators are tried in priority order. Native tools (optimized for specific ecosystems) are preferred over generic scanners. Each tool supports different ecosystems: -| Priority | Generator | Supported Ecosystems | Output Formats | -| -------- | ------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------- | -| 10 | **cyclonedx-py** | Python only | CycloneDX 1.0–1.7 | -| 10 | **cargo-cyclonedx** | Rust only | CycloneDX 1.4–1.6 | -| 20 | **cdxgen** | Python, JavaScript, **Java/Gradle**, Go, Rust, Ruby, Dart, C++, PHP, .NET, Swift, Elixir, Scala, Docker images | CycloneDX 1.4–1.7 | -| ~~30~~ | ~~**Trivy**~~ | ~~Temporarily disabled due to security vulnerabilities~~ | ~~CycloneDX 1.6, SPDX 2.3~~ | -| 35 | **Syft** | Python, JavaScript, Go, Rust, Ruby, Dart, C++, PHP, .NET, Swift, Elixir, Terraform, Docker images | CycloneDX 1.2–1.6, SPDX 2.2–2.3 | +| Priority | Generator | Supported Ecosystems | Output Formats | +| -------- | --------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------- | +| 10 | **cyclonedx-py** | Python only | CycloneDX 1.0–1.7 | +| 10 | **cargo-cyclonedx** | Rust only | CycloneDX 1.4–1.6 | +| 20 | **cdxgen** (lockfiles) | Python, JavaScript, **Java/Gradle**, Go, Rust, Ruby, Dart, C++, PHP, .NET, Swift, Elixir, Scala | CycloneDX 1.4–1.7 | +| 25 | **Syft** (images) | Docker images | CycloneDX 1.2–1.6, SPDX 2.2–2.3 | +| ~~30~~ | ~~**Trivy**~~ | ~~Temporarily disabled due to security vulnerabilities~~ | ~~CycloneDX 1.6, SPDX 2.3~~ | +| 35 | **Syft** (lockfiles) | Python, JavaScript, Go, Rust, Ruby, Dart, C++, PHP, .NET, Swift, Elixir, Terraform | CycloneDX 1.2–1.6, SPDX 2.2–2.3 | +| 40 | **cdxgen** (images) | Docker images (fallback: lacks the OS metadata vulnerability scanners need) | CycloneDX 1.4–1.7 | #### How It Works @@ -1057,7 +1059,7 @@ Generators are tried in priority order. Native tools (optimized for specific eco 3. **Java lockfiles** (pom.xml, build.gradle, gradle.lockfile) → cdxgen (best Java support) 4. **Dart lockfiles** (pubspec.lock) → cdxgen or Syft 5. **Other lockfiles** (package-lock.json, go.mod, etc.) → cdxgen (then Syft as fallback) -6. **Docker images** → cdxgen (then Syft as fallback) +6. **Docker images** → Syft (then cdxgen as fallback). Syft SBOMs include the `operating-system` component and distro PURL qualifiers that vulnerability scanners (Trivy, Grype, Dependency-Track) need to detect OS packages. If the primary generator fails or doesn't support the input, the next one in priority order is tried automatically. diff --git a/sbomify_action/_generation/generator.py b/sbomify_action/_generation/generator.py index e28c7924..5f076b0d 100644 --- a/sbomify_action/_generation/generator.py +++ b/sbomify_action/_generation/generator.py @@ -31,14 +31,19 @@ def create_default_registry() -> GeneratorRegistry: - Input: Rust lock files only (Cargo.lock) - Output: CycloneDX 1.4-1.6 - Priority 20 - Comprehensive Multi-Ecosystem (cdxgen): + Priority 20 - Comprehensive Multi-Ecosystem (cdxgen, lock files): - CdxgenFsGenerator: Filesystem/lock file scanning - Input: Python, JavaScript, Java/Gradle, Go, Rust, Ruby, Dart, C++, PHP, .NET, Swift, Elixir, Scala - Output: CycloneDX 1.4-1.7 (no SPDX support) - - CdxgenImageGenerator: Docker image scanning + + Priority 25 - Preferred Image Scanner (Syft): + - SyftImageGenerator: Docker image scanning - Input: Container images - - Output: CycloneDX 1.4-1.7 (no SPDX support) + - Output: CycloneDX 1.2-1.6, SPDX 2.2-2.3 + - Emits the operating-system component and distro PURL qualifiers + that vulnerability scanners (Trivy, Dependency-Track, Grype) + need to detect OS packages (see issue #264) Priority 30 - Generic Multi-Ecosystem (Trivy) [TEMPORARILY DISABLED]: - TrivyFsGenerator: Filesystem/lock file scanning @@ -50,14 +55,18 @@ def create_default_registry() -> GeneratorRegistry: - Output: CycloneDX 1.6, SPDX 2.3 NOTE: Trivy is temporarily disabled due to recurring security vulnerabilities. - Priority 35 - Generic Multi-Ecosystem (Syft): + Priority 35 - Generic Multi-Ecosystem (Syft, lock files): - SyftFsGenerator: Filesystem/lock file scanning - Input: Python, JavaScript, Go, Rust, Ruby, Dart, C++, PHP, .NET, Swift, Elixir, Terraform (NOT Java/Gradle lock files) - Output: CycloneDX 1.2-1.6, SPDX 2.2-2.3 - - SyftImageGenerator: Docker image scanning + + Priority 40 - Fallback Image Scanner (cdxgen): + - CdxgenImageGenerator: Docker image scanning - Input: Container images - - Output: CycloneDX 1.2-1.6, SPDX 2.2-2.3 + - Output: CycloneDX 1.4-1.7 (no SPDX support) + - Fallback only: its image SBOMs lack the operating-system + component, so Trivy/Dependency-Track can't scan them Generators are queried sequentially in priority order. The first generator that supports the input and requested format/version is used. @@ -71,9 +80,13 @@ def create_default_registry() -> GeneratorRegistry: registry.register(CycloneDXPyGenerator()) registry.register(CycloneDXCargoGenerator()) - # Priority 20: cdxgen generators (comprehensive multi-ecosystem) + # Priority 20: cdxgen lock file generator (comprehensive multi-ecosystem) registry.register(CdxgenFsGenerator()) - registry.register(CdxgenImageGenerator()) + + # Priority 25: Syft image generator — preferred for Docker images because + # its SBOMs carry the operating-system component that downstream + # vulnerability scanners (Trivy, Dependency-Track) require (issue #264) + registry.register(SyftImageGenerator()) # Priority 30: Trivy generators - TEMPORARILY DISABLED due to recurring # security vulnerabilities in Trivy. To re-enable, reintroduce the @@ -83,9 +96,12 @@ def create_default_registry() -> GeneratorRegistry: # registry.register(TrivyFsGenerator()) # registry.register(TrivyImageGenerator()) - # Priority 35: Syft generators (version selection, wide ecosystem support) + # Priority 35: Syft lock file generator (version selection, wide ecosystem support) registry.register(SyftFsGenerator()) - registry.register(SyftImageGenerator()) + + # Priority 40: cdxgen image generator — fallback behind Syft; its image + # SBOMs lack the operating-system component (issue #264) + registry.register(CdxgenImageGenerator()) return registry diff --git a/sbomify_action/_generation/generators/__init__.py b/sbomify_action/_generation/generators/__init__.py index c627da29..65cc00c9 100644 --- a/sbomify_action/_generation/generators/__init__.py +++ b/sbomify_action/_generation/generators/__init__.py @@ -4,11 +4,11 @@ - CycloneDXPyGenerator: Native Python CycloneDX generator (priority 10) - CycloneDXCargoGenerator: Native Rust/Cargo CycloneDX generator (priority 10) - CdxgenFsGenerator: cdxgen filesystem scanner (priority 20) -- CdxgenImageGenerator: cdxgen Docker image scanner (priority 20) +- SyftImageGenerator: Syft Docker image scanner (priority 25) - TrivyFsGenerator: Trivy filesystem scanner (priority 30) - TrivyImageGenerator: Trivy Docker image scanner (priority 30) - SyftFsGenerator: Syft filesystem scanner (priority 35) -- SyftImageGenerator: Syft Docker image scanner (priority 35) +- CdxgenImageGenerator: cdxgen Docker image scanner (priority 40) """ from .cdxgen import CdxgenFsGenerator, CdxgenImageGenerator diff --git a/sbomify_action/_generation/generators/cdxgen.py b/sbomify_action/_generation/generators/cdxgen.py index d14978af..c873980e 100644 --- a/sbomify_action/_generation/generators/cdxgen.py +++ b/sbomify_action/_generation/generators/cdxgen.py @@ -1,6 +1,7 @@ """cdxgen generator plugins for filesystem and Docker image scanning. -Priority: 20 (comprehensive multi-ecosystem) +Priority: 20 for lock files (comprehensive multi-ecosystem), +40 for Docker images (fallback behind Syft — see CdxgenImageGenerator). cdxgen is a comprehensive SBOM generator that supports many ecosystems and programming languages with excellent coverage. @@ -228,6 +229,11 @@ class CdxgenImageGenerator: Uses cdxgen to scan Docker images and generate SBOMs. + Fallback behind Syft for Docker images: cdxgen image SBOMs lack the + `operating-system` component, so vulnerability scanners like Trivy + (including Dependency-Track's Trivy analyzer) can't detect OS + packages and scan nothing (see issue #264). + Verified capabilities (cdxgen 12.0.0): - CycloneDX versions: 1.4, 1.5, 1.6, 1.7 (default: 1.6) - SPDX: Not supported @@ -245,8 +251,9 @@ def command(self) -> str: @property def priority(self) -> int: - # Comprehensive multi-ecosystem, higher priority than Trivy/Syft - return 20 + # Fallback behind Syft (25): cdxgen image SBOMs omit the + # operating-system component that Trivy/Dependency-Track need + return 40 @property def supported_formats(self) -> list[FormatVersion]: @@ -306,10 +313,11 @@ def generate(self, input: GenerationInput) -> GenerationResult: try: # run_command raises SBOMGenerationError on failure (uses check=True). - # log_errors=False: cdxgen image scanning is a priority-20 fallback - # ahead of syft; a benign failure here shouldn't spam ERROR when syft - # can still succeed. (Docker-image-not-found is handled separately and - # still logs at WARNING.) + # log_errors=False: cdxgen image scanning is a fallback behind syft; + # a benign failure here shouldn't spam ERROR — if every generator + # fails, the pipeline raises SBOMGenerationError and the run fails. + # (Docker-image-not-found is handled separately and still logs at + # WARNING.) run_command(cmd, "cdxgen", timeout=DEFAULT_TIMEOUT, docker_image=input.docker_image, log_errors=False) # Verify output file was created diff --git a/sbomify_action/_generation/generators/syft.py b/sbomify_action/_generation/generators/syft.py index f8d7a083..dc7825d3 100644 --- a/sbomify_action/_generation/generators/syft.py +++ b/sbomify_action/_generation/generators/syft.py @@ -1,9 +1,13 @@ """Syft generator plugins for filesystem and Docker image scanning. -Priority: 35 (generic multi-ecosystem, lower than Trivy) +Priority: 35 for lock files (generic multi-ecosystem, lower than Trivy), +25 for Docker images (preferred over cdxgen — see SyftImageGenerator). Syft is a comprehensive SBOM generator that supports version selection. -It supports more versions than Trivy but is slightly lower priority. +For lock files it supports more spec versions than Trivy but ranks +slightly lower; for Docker images it ranks higher than both Trivy and +cdxgen because its image SBOMs carry the OS metadata downstream +vulnerability scanners need. Verified capabilities (Syft 1.38.2): - CycloneDX versions: 1.2, 1.3, 1.4, 1.5, 1.6 (default: 1.6) @@ -176,6 +180,13 @@ class SyftImageGenerator: Uses Syft to scan Docker images and generate SBOMs. + Preferred over cdxgen for Docker images: Syft emits the + `operating-system` component and distro PURL qualifiers that + vulnerability scanners (Trivy, Dependency-Track's Trivy analyzer, + Grype) need to detect OS packages. cdxgen image SBOMs lack the OS + component, so Trivy reports "Unsupported os" and scans nothing + (see issue #264). + Verified capabilities (Syft 1.38.2): - CycloneDX versions: 1.2, 1.3, 1.4, 1.5, 1.6 (default: 1.6) - SPDX versions: 2.2, 2.3 (default: 2.3) @@ -192,8 +203,9 @@ def command(self) -> str: @property def priority(self) -> int: - # Generic multi-ecosystem, slightly lower priority than Trivy - return 35 + # Preferred image scanner: unlike cdxgen, Syft's image SBOMs are + # consumable by downstream vulnerability scanners (Trivy/Grype) + return 25 @property def supported_formats(self) -> list[FormatVersion]: diff --git a/tests/test_generation_plugin.py b/tests/test_generation_plugin.py index 0c1387f6..a9796485 100644 --- a/tests/test_generation_plugin.py +++ b/tests/test_generation_plugin.py @@ -235,11 +235,13 @@ def test_get_generators_for_docker_image(self): input = GenerationInput(docker_image="alpine:3.18", output_format="cyclonedx") generators = self.registry.get_generators_for(input) - # cyclonedx-py doesn't support Docker images + # cyclonedx-py doesn't support Docker images. + # Syft is preferred for images: its SBOMs carry the operating-system + # component that Trivy/Dependency-Track need (issue #264). self.assertEqual(len(generators), 3) - self.assertEqual(generators[0].name, "cdxgen-image") # Priority 20 + self.assertEqual(generators[0].name, "syft-image") # Priority 25 self.assertEqual(generators[1].name, "trivy-image") # Priority 30 - self.assertEqual(generators[2].name, "syft-image") # Priority 35 + self.assertEqual(generators[2].name, "cdxgen-image") # Priority 40 @patch("sbomify_action._generation.generators.cyclonedx_py._CYCLONEDX_PY_AVAILABLE", True) @@ -642,7 +644,7 @@ def setUp(self): def test_name_and_priority(self): """Test generator name and priority.""" self.assertEqual(self.generator.name, "cdxgen-image") - self.assertEqual(self.generator.priority, 20) + self.assertEqual(self.generator.priority, 40) def test_supports_docker_images(self): """Test support for Docker images.""" @@ -862,7 +864,7 @@ def setUp(self): def test_name_and_priority(self): """Test generator name and priority.""" self.assertEqual(self.generator.name, "syft-image") - self.assertEqual(self.generator.priority, 35) + self.assertEqual(self.generator.priority, 25) def test_supports_docker_images(self): """Test support for Docker images."""