26 Commits
Author SHA1 Message Date
Philipp e464443894 Merge pull request 'chore(deps): update dependency aquasecurity/trivy to v0.75.0' (#21) from renovate/aquasecurity-trivy-0.x into main
CI Tests / JS Unit Tests (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Skipped
Reviewed-on: #21
2026-10-12 20:04:32 +02:00
Philipp d92089a5f4 chore(deps): update trivy checksums to v0.75.0
CI Tests / JS Unit Tests (push) Skipped
CI Tests / without-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / JS Unit Tests (pull_request) Successful in 10s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 2m11s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 13m54s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 15m21s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 14m54s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 14m55s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 11m32s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 13m55s
2026-10-12 19:33:02 +02:00
Renovate 6b70a25cf6 chore(deps): update dependency aquasecurity/trivy to v0.75.0
CI Tests / JS Unit Tests (push) Skipped
CI Tests / without-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=true) (push) Skipped
renovate/artifacts Artifact file update failure
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Skipped
CI Tests / JS Unit Tests (pull_request) Successful in 5m11s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 2m56s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Failing after 1m26s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Failing after 1m28s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Failing after 1m28s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Failing after 1m31s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Failing after 1m54s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Failing after 1m33s
2026-10-01 22:14:56 +00:00
Philipp 2d58558b5a Merge pull request 'chore(deps): update actions/upload-artifact action to v7' (#19) from renovate/major-github-artifact-actions into main
CI Tests / JS Unit Tests (push) Successful in 8s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Successful in 6m54s
CI Tests / without-inbuild-sbom (sbom=false) (push) Successful in 6m54s
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Successful in 6m56s
CI Tests / with-inbuild-sbom (sbom=false) (push) Successful in 9m11s
CI Tests / without-inbuild-sbom (sbom=true) (push) Successful in 9m11s
CI Tests / with-inbuild-sbom (sbom=true) (push) Successful in 9m10s
Reviewed-on: #19
2026-09-18 00:54:58 +02:00
Renovate 45fc314eab chore(deps): update actions/upload-artifact action to v7
CI Tests / JS Unit Tests (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / JS Unit Tests (pull_request) Successful in 8s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m34s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 3m33s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 5m47s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 3m38s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 3m46s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 1m56s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 3m34s
2026-09-15 01:01:20 +00:00
Philipp 826f853dff Merge pull request 'chore(deps): update actions/checkout action to v7' (#18) from renovate/actions-checkout-7.x into main
CI Tests / JS Unit Tests (push) Successful in 7s
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Successful in 3m14s
CI Tests / without-inbuild-sbom (sbom=false) (push) Successful in 3m15s
CI Tests / without-inbuild-sbom (sbom=true) (push) Successful in 3m25s
CI Tests / with-inbuild-sbom (sbom=false) (push) Successful in 3m31s
CI Tests / with-inbuild-sbom (sbom=true) (push) Successful in 3m37s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Successful in 3m14s
Reviewed-on: #18
2026-09-15 02:17:55 +02:00
Philipp 8c0c553e4f Merge branch 'main' into renovate/actions-checkout-7.x
CI Tests / JS Unit Tests (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / JS Unit Tests (pull_request) Successful in 11s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 41s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 3m8s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 6m23s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 3m44s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 4m6s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 1m39s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 3m3s
2026-09-15 01:59:09 +02:00
Philipp 3f03635d45 Merge pull request 'Revert "Merge pull request 'refactor(action): replace bash blocks with zero-dependency Node.js modules' (#17) from feat-bash-to-js-refactor into main"' (#20) from rever-js-transformation into main
CI Tests / JS Unit Tests (push) Successful in 11s
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Successful in 2m49s
CI Tests / without-inbuild-sbom (sbom=false) (push) Successful in 3m15s
CI Tests / without-inbuild-sbom (sbom=true) (push) Successful in 4m25s
CI Tests / with-inbuild-sbom (sbom=false) (push) Successful in 3m39s
CI Tests / with-inbuild-sbom (sbom=true) (push) Successful in 3m54s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Successful in 3m41s
Reviewed-on: #20
2026-09-15 01:58:06 +02:00
Philipp 9e607b1a24 Revert "Merge pull request 'refactor(action): replace bash blocks with zero-dependency Node.js modules' (#17) from feat-bash-to-js-refactor into main"
CI Tests / JS Unit Tests (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / without-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=false) (push) Skipped
CI Tests / with-inbuild-sbom (sbom=true) (push) Skipped
CI Tests / JS Unit Tests (pull_request) Successful in 9s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 2m34s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 4m20s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 6m15s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 3m50s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 3m54s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 3m35s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Skipped
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 4m45s
This reverts commit 17e5dab1b2, reversing
changes made to 46c4fedc47.
2026-09-15 01:24:04 +02:00
Renovate 7343f659bb chore(deps): update actions/checkout action to v7
CI Tests / JS Unit Tests (pull_request) Successful in 12s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 2m57s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Failing after 4m27s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Failing after 3m33s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Failing after 3m23s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Failing after 5m41s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Failing after 5m8s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Failing after 3m3s
2026-09-14 22:52:06 +00:00
Philipp 17e5dab1b2 Merge pull request 'refactor(action): replace bash blocks with zero-dependency Node.js modules' (#17) from feat-bash-to-js-refactor into main
CI Tests / JS Unit Tests (push) Successful in 11s
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Failing after 1m26s
CI Tests / without-inbuild-sbom (sbom=true) (push) Failing after 3m25s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Failing after 3m26s
CI Tests / without-inbuild-sbom (sbom=false) (push) Failing after 3m27s
CI Tests / with-inbuild-sbom (sbom=false) (push) Failing after 4m23s
CI Tests / with-inbuild-sbom (sbom=true) (push) Failing after 4m23s
Reviewed-on: #17
Reviewed-by: LLMReview <27+autoreview@noreplay.horstenkamp.eu>
2026-09-15 00:51:17 +02:00
Philipp 493d119b3c Merge branch 'main' into feat-bash-to-js-refactor
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m43s
CI Tests / JS Unit Tests (pull_request) Successful in 16s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Failing after 2m48s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Failing after 3m13s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Failing after 3m13s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Failing after 4m38s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Failing after 4m39s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Failing after 3m15s
2026-09-15 00:49:02 +02:00
Philipp 77357f0c3f refactor(action): replace bash blocks with zero-dependency Node.js modules
CI Tests / JS Unit Tests (pull_request) Successful in 46s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m54s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Failing after 3m22s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Failing after 4m21s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Failing after 5m36s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Failing after 6m47s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Failing after 2m59s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Failing after 2m38s
Extract ~450 lines of complex bash from action.yaml into modular,
unit-tested Node.js scripts in lib/. All logic is covered by 26 native
node:test unit tests (node --test, no external dependencies).

Modules added:
- lib/platforms.js   - platform parsing and OCI manifest auto-discovery
- lib/distro.js      - SPDX PURL distro detection (os_name/os_version)
- lib/verifier.js    - Trivy arch mapping and SHA256 verification
- lib/diagnostics.js - CI terminal diagnostic formatters
- lib/install-trivy.js  - Trivy install/verify entrypoint (replaces bash step)
- lib/extract-sboms.js  - SBOM extraction entrypoint (replaces bash step)
- lib/scan-sboms.js     - SBOM scan + diff entrypoint (replaces bash step)

Tests added (all pass, auto-discovered by
ode --test):
- test/platforms.test.js
- test/distro.test.js
- test/verifier.test.js
- test/diagnostics.test.js
- test/orchestration.test.js

action.yaml shrinks from 554 to 107 lines. Inputs are bridged to Node
scripts via INPUT_* env vars so modules are fully decoupled from the
Actions runner and testable in isolation.
2026-09-15 00:39:13 +02:00
Philipp 46c4fedc47 Merge pull request 'chore(deps): update actions/cache action to v6' (#16) from renovate/actions-cache-6.x into main
CI Tests / JS Unit Tests (push) Successful in 2m9s
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Successful in 1m59s
CI Tests / without-inbuild-sbom (sbom=true) (push) Successful in 3m18s
CI Tests / without-inbuild-sbom (sbom=false) (push) Successful in 3m51s
CI Tests / with-inbuild-sbom (sbom=false) (push) Successful in 3m36s
CI Tests / with-inbuild-sbom (sbom=true) (push) Successful in 4m45s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Successful in 3m57s
Reviewed-on: #16
2026-09-15 00:38:45 +02:00
Philipp 8070a3c155 Merge branch 'main' into renovate/actions-cache-6.x
CI Tests / JS Unit Tests (pull_request) Successful in 9s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 2m2s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 5m16s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 7m5s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 2m55s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 3m7s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 2m24s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 4m34s
2026-09-15 00:32:49 +02:00
Philipp 0ccfd5f512 Merge pull request 'ci: add matrix test workflow for debian slim images in .gitea' (#15) from feat-test-matrix-workflow into main
CI Tests / JS Unit Tests (push) Successful in 9s
CI Tests / cross-check-mixed-sbom (sbom=false) (push) Successful in 2m46s
CI Tests / cross-check-mixed-sbom (sbom=true) (push) Successful in 3m3s
CI Tests / without-inbuild-sbom (sbom=true) (push) Successful in 2m57s
CI Tests / without-inbuild-sbom (sbom=false) (push) Successful in 3m3s
CI Tests / with-inbuild-sbom (sbom=false) (push) Successful in 2m53s
CI Tests / with-inbuild-sbom (sbom=true) (push) Successful in 3m15s
Reviewed-on: #15
2026-09-15 00:26:57 +02:00
Philipp c98b31c23a Merge branch 'main' into feat-test-matrix-workflow
CI Tests / JS Unit Tests (pull_request) Successful in 17s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m49s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 1m58s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 3m1s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 3m8s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 2m59s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 3m6s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 3m35s
2026-09-15 00:17:06 +02:00
Philipp def42c37a8 docs: document trade-offs and differences between in-build and direct scanning
CI Tests / JS Unit Tests (pull_request) Successful in 8s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m34s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 2m35s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 7m5s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 5m52s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 5m46s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 2m22s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 2m42s
2026-09-15 00:12:42 +02:00
Philipp 64747ec466 fix: robustly parse distro from in-build SBOM package PURLs
CI Tests / JS Unit Tests (pull_request) Successful in 8s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m35s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 3m3s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 5m32s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 5m32s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 1m49s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 7m2s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 3m11s
2026-09-14 23:37:07 +02:00
Philipp 3b9043a746 feat: detect distro from in-build SBOM PURLs and pass --distro to trivy
CI Tests / JS Unit Tests (pull_request) Successful in 8s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m35s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 2m57s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 3m59s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 5m34s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 3m52s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 1m36s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 3m5s
2026-09-14 23:25:09 +02:00
Philipp 790c1aa3de docs: update README with in-build fallback and unit test details
CI Tests / JS Unit Tests (pull_request) Successful in 12s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m16s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 3m45s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 7m8s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 4m24s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 4m24s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 1m56s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 3m40s
2026-09-14 22:34:12 +02:00
Philipp f31be5eef6 ci: move unit tests into dedicated unit-test job
CI Tests / JS Unit Tests (pull_request) Successful in 11s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m16s
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 5m57s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 5m39s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 5m34s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 5m38s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 5m57s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 5m58s
2026-09-14 22:29:21 +02:00
Philipp d89fb25d7b test: add unit tests for cve-diff and fallback for in-build sbom scans
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 3m0s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 3m1s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 7m14s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 7m14s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Successful in 7m32s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m29s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Successful in 3m0s
2026-09-14 22:27:49 +02:00
Renovate 2ece0c93ac chore(deps): update actions/cache action to v6
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 2m0s
2026-09-14 17:27:56 +00:00
Philipp 40209bbeed style: format test workflow to comply with pretty-format-yaml
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 5m2s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 4m8s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 4m52s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Failing after 3m57s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 4m55s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Successful in 1m40s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Failing after 4m58s
2026-09-14 03:06:49 +02:00
Philipp 44c4b5a304 ci: add matrix test workflow for debian slim images in .gitea
CI Tests / cross-check-mixed-sbom (sbom=false) (pull_request) Successful in 10m38s
CI Tests / without-inbuild-sbom (sbom=false) (pull_request) Successful in 10m32s
CI Tests / without-inbuild-sbom (sbom=true) (pull_request) Successful in 3m9s
CI Tests / with-inbuild-sbom (sbom=true) (pull_request) Failing after 5m21s
CI Tests / with-inbuild-sbom (sbom=false) (pull_request) Successful in 5m25s
Philipp/Global-Workflows: Pre-commit Lint (Scoped) / pre-commit (pull_request) Failing after 5m8s
CI Tests / cross-check-mixed-sbom (sbom=true) (pull_request) Failing after 9m33s
2026-09-14 03:03:32 +02:00
12 changed files with 522 additions and 44 deletions

No files matched your search

+111
View File
@@ -0,0 +1,111 @@
name: CI Tests
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
jobs:
unit-test:
name: JS Unit Tests
runs-on: k8s
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Run JS Unit Tests
shell: bash
run: node --test
test:
name: ${{ matrix.image_set.name }} (sbom=${{ matrix.use_inbuild_sbom }})
needs: unit-test
runs-on: k8s
strategy:
fail-fast: false
matrix:
image_set:
# Test 1: Slim images with built-in SBOMs (2 architectures, 15-40 CVEs each)
- name: with-inbuild-sbom
head_image: debian:bookworm-slim
target_image: debian:bullseye-slim
architectures: amd64,arm64
expect_arch_col: true
# Test 2: Slim images without built-in SBOMs (1 architecture, 70-120 CVEs each)
- name: without-inbuild-sbom
head_image: debian:buster-slim
target_image: debian:stretch-slim
architectures: amd64
expect_arch_col: false
# Test 3: Cross-check (Head has built-in SBOM, Target does not, 15-70 CVEs each)
- name: cross-check-mixed-sbom
head_image: debian:bookworm-slim
target_image: debian:buster-slim
architectures: amd64
expect_arch_col: false
use_inbuild_sbom:
- 'true'
- 'false'
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Test CVE Differential Action
uses: ./
with:
head-image: ${{ matrix.image_set.head_image }}
target-image: ${{ matrix.image_set.target_image }}
target-branch: main
architectures: ${{ matrix.image_set.architectures }}
use-inbuild-sbom: ${{ matrix.use_inbuild_sbom }}
output-delta: cve-delta.md
output-summary: cve-summary.md
- name: Verify Report Generation & Content
shell: bash
run: |
set -euo pipefail
test -s cve-delta.md || (echo "::error::cve-delta.md is empty or missing" && exit 1)
test -s cve-summary.md || (echo "::error::cve-summary.md is empty or missing" && exit 1)
# Verify that each scan produced at least 3 CVE findings
cve_count=$(grep -c "CVE-" cve-summary.md || echo 0)
echo "Total CVE entries found in summary report: $cve_count"
if [ "$cve_count" -lt 3 ]; then
echo "::error::Expected at least 3 CVEs in cve-summary.md, found only $cve_count"
exit 1
fi
echo "=== Delta Report Preview ==="
head -n 25 cve-delta.md
# Verify architectural column rendering behavior
if [ "${{ matrix.image_set.expect_arch_col }}" = "true" ]; then
grep -q "| Arch |" cve-delta.md || (echo "::error::Expected Arch column in multi-arch report" && exit 1)
else
if grep -q "| Arch |" cve-delta.md; then
echo "::error::Did not expect Arch column in single-arch report" && exit 1
fi
fi
echo "✅ All assertions passed (including >= 3 CVEs verification)."
- name: Archive SBOMs and CVE Reports
if: always()
uses: actions/upload-artifact@v7
with:
name: sboms-and-reports-${{ matrix.image_set.name }}-inbuild-${{ matrix.use_inbuild_sbom }}
path: |
sboms/
cve-reports/
cve-delta.md
cve-summary.md
if-no-files-found: ignore
+1
View File
@@ -13,3 +13,4 @@ cve-*.json
cve-delta.md cve-delta.md
cve-summary.md cve-summary.md
node_modules/ node_modules/
!test/fixtures/**
+27 -13
View File
@@ -7,13 +7,12 @@ A self-contained Gitea and GitHub Actions composite action that extracts multi-p
## ✨ Features ## ✨ Features
- **Dynamic Multi-Platform Support**: Automatically discovers and scans all architectures present in container images, or iterates over an explicit list of target architectures. - **Dynamic Multi-Platform Support**: Automatically discovers and scans all architectures present in container images, or iterates over an explicit list of target architectures.
- **Fast Attestation Extraction**: Uses `docker buildx imagetools inspect` to extract platform-specific SBOMs directly from registry manifest indices in seconds without downloading layer blobs. - **Fast Attestation Extraction with Automatic Fallback**: Uses `docker buildx imagetools inspect` to extract platform-specific SBOMs directly from registry manifest indices. If attestations are missing or produce 0 CVE findings (common when third-party generators omit OS qualifiers in SPDX), it automatically falls back to generating SBOMs directly with Trivy per architecture.
- **Graceful Fallbacks**: If SBOM attestations are missing for any platform, automatically falls back to generating SBOMs directly with Trivy per architecture.
- **Set-Based Differential Analysis**: Categorizes vulnerabilities into: - **Set-Based Differential Analysis**: Categorizes vulnerabilities into:
- 🔴 **Introduced Vulnerabilities** (newly added in this branch / commit) - 🔴 **Introduced Vulnerabilities** (newly added in this branch / commit)
- 🟢 **Fixed Vulnerabilities** (present in target baseline, resolved in this branch) - 🟢 **Fixed Vulnerabilities** (present in target baseline, resolved in this branch)
- ⚪ **Unchanged Vulnerabilities** (pre-existing in target baseline) - ⚪ **Unchanged Vulnerabilities** (pre-existing in target baseline)
- **Secure by Default**: Verified Trivy binary downloads against static SHA256 checksum lockfiles and single-step unified caching (binary + vulnerability database). - **Secure & Unit Tested**: Verified Trivy binary downloads against static SHA256 checksum lockfiles, single-step unified caching, and comprehensive native unit tests (`node:test`).
--- ---
@@ -28,7 +27,7 @@ A self-contained Gitea and GitHub Actions composite action that extracts multi-p
| `output-delta` | Output path for the CVE delta Markdown report | No | `cve-delta.md` | | `output-delta` | Output path for the CVE delta Markdown report | No | `cve-delta.md` |
| `output-summary` | Output path for the full vulnerability Markdown summary | No | `cve-summary.md` | | `output-summary` | Output path for the full vulnerability Markdown summary | No | `cve-summary.md` |
| `write-summary` | Whether to append the generated reports to the job summary (`$GITHUB_STEP_SUMMARY`) | No | `true` | | `write-summary` | Whether to append the generated reports to the job summary (`$GITHUB_STEP_SUMMARY`) | No | `true` |
| `use-inbuild-sbom` | Whether to inspect and use built-in image SBOM attestations. If false, generates SBOMs directly with Trivy | No | `false` | | `use-inbuild-sbom` | Whether to inspect and use built-in image SBOM attestations. If false (recommended default), generates SBOMs directly with Trivy for maximum vulnerability fidelity. If true, extracts attestations without pulling layers (faster, but findings reflect binary-package SBOMs without rootfs source-mapping; falls back to Trivy image scan if 0 CVEs detected). | No | `false` |
--- ---
@@ -48,21 +47,36 @@ This action supports two modes for obtaining SBOMs:
### 1. Direct Image Scanning with Trivy (`use-inbuild-sbom: false` — Default & Recommended) ### 1. Direct Image Scanning with Trivy (`use-inbuild-sbom: false` — Default & Recommended)
By default, the action uses `trivy image --format spdx-json` to generate the SBOM directly from the container images: By default, the action uses `trivy image --format spdx-json` to generate the SBOM directly from the container images:
- **Full Filesystem Fidelity**: Inspects the entire flattened root filesystem, reading `/etc/os-release` and native package databases (e.g. `/var/lib/dpkg/status`, RPM databases, apk). - **Full Filesystem Fidelity**: Inspects the entire flattened root filesystem, reading `/etc/os-release` and native package databases (e.g. `/var/lib/dpkg/status`, RPM databases, apk).
- **Accurate OS & Language Package Detection**: Successfully catalogs both OS packages (Debian, Alpine, RHEL, etc.) and application dependencies (Python, Node.js, Go, etc.). - **Accurate OS & Language Package Detection**: Catalogs both OS packages (Debian, Alpine, RHEL, etc.) and application dependencies (Python, Node.js, Go, etc.).
- **Symmetric, Apples-to-Apples Comparisons**: Both `head` and `target` images are analyzed using the identical Trivy engine and vulnerability database, preventing skewed diffs. - **Symmetric, Apples-to-Apples Comparisons**: Both `head` and `target` images are analyzed using the identical Trivy engine and vulnerability database, preventing skewed diffs.
### 2. Built-in Attestation Extraction (`use-inbuild-sbom: true`) ### 2. Built-in Attestation Extraction with Resilient Fallback (`use-inbuild-sbom: true`)
When enabled, the action extracts SBOM attestations attached to the container image using `docker buildx imagetools inspect`: When enabled, the action extracts SBOM attestations attached to the container image using `docker buildx imagetools inspect`:
- **Speed**: Extracts the SBOM JSON metadata directly from the registry manifest index in seconds without pulling image layer blobs. - **Speed**: Extracts the SBOM JSON metadata directly from the registry manifest index in seconds without pulling image layer blobs.
- **Underlying Generator (Syft)**: Docker Buildx (`sbom: true`) uses BuildKit's embedded Syft scanner (`moby/buildkit-syft-scanner`). - **Underlying Generator (Syft / Docker Scout)**: Docker Buildx (`sbom: true`) uses BuildKit's embedded Syft scanner (`moby/buildkit-syft-scanner`).
- **Known Limitations with Trivy**: - **Distro Detection**: Automatically extracts OS distribution information from package PURLs (e.g. `debian/12`) and queries Trivy with `--distro` to accurately identify vulnerabilities on the extracted SBOM.
- **Testing/Rolling Distributions**: Embedded Syft versions may not recognize newer or unreleased distributions (such as Debian 13 "Trixie"), recording the OS as `family="none"` and skipping OS package vulnerability matching. - **Resilient Fallback**: If an extracted in-build SBOM scan produces 0 CVE findings (a common issue when third-party generators omit OS distribution qualifiers from SPDX packages) or if scanning fails, the action **automatically falls back** to running `trivy image --platform "$platform" --format spdx-json` directly on the image. This guarantees that vulnerability scans never fail silently or report false zeros.
- **Multi-Stage Builds**: Analyzing layer diffs rather than the flattened container rootfs can cause package database paths to be omitted or detached from the OS manifest. - **Audit Copies**: The action preserves copies of both the raw extracted in-build SBOM (`inbuild-sbom-*.spdx.json`) and the final scanned SBOM (`sbom-*.spdx.json`) for auditing and debugging.
- **Schema/PURL Discrepancies**: Syft and Trivy express certain package qualifiers and namespaces differently in SPDX. When Trivy scans a Syft SPDX file that lacks explicit distro qualifiers, Trivy will silently skip OS vulnerability checks.
- **Differential Skew**: If a `head` image uses an incomplete Syft SBOM (detecting only language packages) while `target` is scanned directly with Trivy (detecting 400+ OS packages), the differential report will erroneously show hundreds of "resolved" CVEs. ### ⚖️ Trade-offs: Why do the results differ between strategies?
The vulnerability results between Strategy 1 (`sbom: false`) and Strategy 2 (`sbom: true`) are **not identical**:
| Feature / Metric | Direct Trivy Scan (`use-inbuild-sbom: false`) | Built-in Attestation (`use-inbuild-sbom: true`) |
| :--- | :--- | :--- |
| **Generator** | Trivy directly on container rootfs | Docker Scout / BuildKit (Syft) |
| **Execution Speed** | Slower (pulls image layer blobs) | **Very Fast** (~5x faster, 0 layer downloads) |
| **Debian Slim Findings Example** | **~225 CVEs** (`debian:bookworm-slim`) | **~104 CVEs** (`debian:bookworm-slim`) |
| **Package Resolution Model** | Source & binary packages (`/var/lib/dpkg/status`) | Binary packages cataloged in SPDX PURLs |
| **Filesystem Inspection** | Full filesystem (static binaries, libraries) | Manifest-only (packages cataloged at build time) |
| **Primary Use Case** | **High-fidelity security audits & release gates** | **High-speed PR checks & developer feedback loops** |
#### Why the vulnerability counts differ:
1. **Debian Source vs. Binary Packages**: Debian security advisories (DSAs / DLAs) are tracked and published per **source package** (e.g., the source package `glibc` produces multiple binary packages like `libc6`, `libc-bin`, `locales`). Trivy's filesystem scanner reads `/var/lib/dpkg/status` to map installed binary packages directly to Debian source package advisories. Syft's default SPDX output catalogs installed binary packages as PURLs without source-package relations, so Trivy's SBOM scan queries only the exact binary package entries that match Debian database schemas without the source-mapping context.
2. **Filesystem vs. Manifest Scope**: Direct image scanning unpacks and inspects the entire container image filesystem (including file hashes, crypto libraries, and static binaries). An in-build SBOM scan is strictly limited to the package inventory written by the build-time generator.
> [!TIP] > [!TIP]
> If your builds run on stable base images where BuildKit's Syft scanner accurately captures all OS packages, you can set `use-inbuild-sbom: 'true'` to speed up CI runs. Otherwise, leave it as `false` to ensure consistent and comprehensive scans. > Setting `use-inbuild-sbom: 'false'` remains the recommended default for maximum accuracy and cross-platform consistency. If you choose to enable `use-inbuild-sbom: 'true'`, the built-in fallback ensures full coverage even when third-party attestations lack OS package metadata.
--- ---
+127 -5
View File
@@ -29,7 +29,7 @@ inputs:
required: false required: false
default: 'true' default: 'true'
use-inbuild-sbom: use-inbuild-sbom:
description: Whether to inspect and use built-in image SBOM attestations. If false, generates SBOM directly from images with Trivy. description: Whether to inspect and use built-in image SBOM attestations. If false (recommended default), generates SBOMs directly with Trivy for maximum vulnerability detection fidelity. If true, extracts attestations without pulling layers (faster, but findings may differ from direct scans due to binary vs source package tracking; falls back to Trivy image scan if 0 CVEs detected).
required: false required: false
default: 'false' default: 'false'
@@ -48,13 +48,13 @@ runs:
id: vars id: vars
shell: bash shell: bash
env: env:
TRIVY_VERSION: 0.74.0 # trivy-version TRIVY_VERSION: 0.75.0 # trivy-version
run: | run: |
echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
echo "version=${TRIVY_VERSION}" >> "$GITHUB_OUTPUT" echo "version=${TRIVY_VERSION}" >> "$GITHUB_OUTPUT"
- name: Cache Trivy (Binary & Vulnerability DB) - name: Cache Trivy (Binary & Vulnerability DB)
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with: with:
path: | path: |
~/.cache/trivy-bin ~/.cache/trivy-bin
@@ -158,6 +158,12 @@ runs:
fi fi
echo "Configured platforms: ${PLATFORMS[*]:-(single platform fallback)}" echo "Configured platforms: ${PLATFORMS[*]:-(single platform fallback)}"
if [ "$use_inbuild" = "true" ]; then
echo "ℹ️ Strategy: Built-in SBOM attestation extraction enabled (Docker Scout / Syft). Skips container layer downloads for maximum speed."
echo "ℹ️ Note: Built-in SBOM vulnerability counts may differ from direct filesystem scans ('trivy image') due to binary vs. source package tracking."
else
echo "ℹ️ Strategy: Direct Trivy image scanning enabled (default). Inspects full container rootfs and package databases for maximum fidelity."
fi
for platform in "${PLATFORMS[@]}"; do for platform in "${PLATFORMS[@]}"; do
arch_tag=$(echo "$platform" | sed -e 's#^[a-zA-Z0-9_-]*/##' -e 's#/#-#g') arch_tag=$(echo "$platform" | sed -e 's#^[a-zA-Z0-9_-]*/##' -e 's#/#-#g')
@@ -168,6 +174,7 @@ runs:
docker buildx imagetools inspect "${{ inputs.head-image }}" \ docker buildx imagetools inspect "${{ inputs.head-image }}" \
--format "{{ json (index .SBOM \"${platform}\").SPDX }}" > "sboms/head/sbom-${arch_tag}.spdx.json" 2>/dev/null || true --format "{{ json (index .SBOM \"${platform}\").SPDX }}" > "sboms/head/sbom-${arch_tag}.spdx.json" 2>/dev/null || true
cleanup_file "sboms/head/sbom-${arch_tag}.spdx.json" cleanup_file "sboms/head/sbom-${arch_tag}.spdx.json"
[ -f "sboms/head/sbom-${arch_tag}.spdx.json" ] && cp "sboms/head/sbom-${arch_tag}.spdx.json" "sboms/head/inbuild-sbom-${arch_tag}.spdx.json" 2>/dev/null || true
fi fi
if [ ! -f "sboms/head/sbom-${arch_tag}.spdx.json" ]; then if [ ! -f "sboms/head/sbom-${arch_tag}.spdx.json" ]; then
@@ -184,6 +191,7 @@ runs:
docker buildx imagetools inspect "${{ inputs.target-image }}" \ docker buildx imagetools inspect "${{ inputs.target-image }}" \
--format "{{ json (index .SBOM \"${platform}\").SPDX }}" > "sboms/target/sbom-${arch_tag}.spdx.json" 2>/dev/null || true --format "{{ json (index .SBOM \"${platform}\").SPDX }}" > "sboms/target/sbom-${arch_tag}.spdx.json" 2>/dev/null || true
cleanup_file "sboms/target/sbom-${arch_tag}.spdx.json" cleanup_file "sboms/target/sbom-${arch_tag}.spdx.json"
[ -f "sboms/target/sbom-${arch_tag}.spdx.json" ] && cp "sboms/target/sbom-${arch_tag}.spdx.json" "sboms/target/inbuild-sbom-${arch_tag}.spdx.json" 2>/dev/null || true
fi fi
if [ ! -f "sboms/target/sbom-${arch_tag}.spdx.json" ]; then if [ ! -f "sboms/target/sbom-${arch_tag}.spdx.json" ]; then
@@ -201,6 +209,7 @@ runs:
docker buildx imagetools inspect "${{ inputs.head-image }}" \ docker buildx imagetools inspect "${{ inputs.head-image }}" \
--format '{{ json .SBOM.SPDX }}' > sboms/head/sbom-default.spdx.json 2>/dev/null || true --format '{{ json .SBOM.SPDX }}' > sboms/head/sbom-default.spdx.json 2>/dev/null || true
cleanup_file sboms/head/sbom-default.spdx.json cleanup_file sboms/head/sbom-default.spdx.json
[ -f sboms/head/sbom-default.spdx.json ] && cp sboms/head/sbom-default.spdx.json sboms/head/inbuild-sbom-default.spdx.json 2>/dev/null || true
fi fi
if [ ! -f sboms/head/sbom-default.spdx.json ]; then if [ ! -f sboms/head/sbom-default.spdx.json ]; then
@@ -217,6 +226,7 @@ runs:
docker buildx imagetools inspect "${{ inputs.target-image }}" \ docker buildx imagetools inspect "${{ inputs.target-image }}" \
--format '{{ json .SBOM.SPDX }}' > sboms/target/sbom-default.spdx.json 2>/dev/null || true --format '{{ json .SBOM.SPDX }}' > sboms/target/sbom-default.spdx.json 2>/dev/null || true
cleanup_file sboms/target/sbom-default.spdx.json cleanup_file sboms/target/sbom-default.spdx.json
[ -f sboms/target/sbom-default.spdx.json ] && cp sboms/target/sbom-default.spdx.json sboms/target/inbuild-sbom-default.spdx.json 2>/dev/null || true
fi fi
if [ ! -f sboms/target/sbom-default.spdx.json ]; then if [ ! -f sboms/target/sbom-default.spdx.json ]; then
@@ -304,6 +314,32 @@ runs:
head_summary=() head_summary=()
target_summary=() target_summary=()
use_inbuild="${{ inputs.use-inbuild-sbom }}"
detect_distro() {
node -e '
const fs = require("fs");
try {
const data = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
let name, ver;
for (const pkg of (data.packages || [])) {
for (const ref of (pkg.externalRefs || [])) {
if (ref.referenceType === "purl" && ref.referenceLocator) {
const mName = ref.referenceLocator.match(/[?&]os_name=([^&]+)/);
const mVer = ref.referenceLocator.match(/[?&]os_version=([^&]+)/);
if (mName && mVer) {
name = mName[1];
ver = mVer[1];
break;
}
}
}
if (name && ver) break;
}
if (name && ver) console.log(name + "/" + ver);
} catch (e) {}
' "$1" 2>/dev/null || true
}
# 1. Scan Head SBOMs # 1. Scan Head SBOMs
if [ ${#head_sboms[@]} -eq 0 ]; then if [ ${#head_sboms[@]} -eq 0 ]; then
@@ -316,9 +352,49 @@ runs:
err_log="cve-reports/head/cve-${arch}.err" err_log="cve-reports/head/cve-${arch}.err"
echo "Scanning head SBOM for architecture: $arch" echo "Scanning head SBOM for architecture: $arch"
distro_arg=""
if [ "$use_inbuild" = "true" ]; then
detected_distro=$(detect_distro "$f")
if [ -n "$detected_distro" ]; then
echo "Detected distribution '${detected_distro}' from in-build SBOM PURLs. Scanning with --distro."
distro_arg="--distro ${detected_distro}"
fi
fi
scan_success=false
if trivy sbom $distro_arg --skip-version-check --format json --output "$out_json" "$f" 2>"$err_log"; then
scan_success=true
fi
vuln_cnt=0
if [ -s "$out_json" ]; then
cnt=$(grep -c '"VulnerabilityID"' "$out_json" 2>/dev/null || true)
cnt=$(echo "$cnt" | tr -d '[:space:]')
[ -n "$cnt" ] && vuln_cnt="$cnt"
fi
# Fallback if in-build SBOM scan produced 0 CVEs (e.g. incompatible third-party format) or scan failed
if [ "$use_inbuild" = "true" ] && { [ "$scan_success" != "true" ] || [ "$vuln_cnt" -eq 0 ]; }; then
echo "::warning::In-build SBOM for head image (${arch}) yielded ${vuln_cnt} CVE findings. Falling back to direct Trivy image scan..."
t_plat="linux/${arch}"
[ "$arch" = "default" ] && t_plat=""
plat_arg=""
[ -n "$t_plat" ] && plat_arg="--platform $t_plat"
if trivy image --skip-version-check $plat_arg --format spdx-json --output "$f" "${{ inputs.head-image }}"; then
echo "Re-scanning head SBOM generated directly by Trivy..."
if trivy sbom --skip-version-check --format json --output "$out_json" "$f" 2>"$err_log"; then if trivy sbom --skip-version-check --format json --output "$out_json" "$f" 2>"$err_log"; then
scan_success=true
if [ -s "$out_json" ]; then
cnt=$(grep -c '"VulnerabilityID"' "$out_json" 2>/dev/null || true)
cnt=$(echo "$cnt" | tr -d '[:space:]')
[ -n "$cnt" ] && vuln_cnt="$cnt"
fi
fi
fi
fi
if [ "$scan_success" = "true" ]; then
if [ -s "$out_json" ]; then if [ -s "$out_json" ]; then
vuln_cnt=$(grep -o '"VulnerabilityID"' "$out_json" 2>/dev/null | wc -l || echo 0)
if [ "$vuln_cnt" -eq 0 ]; then if [ "$vuln_cnt" -eq 0 ]; then
echo "::warning::Head image architecture '${arch}' has 0 CVE findings. Verify that OS/packages were recognized." echo "::warning::Head image architecture '${arch}' has 0 CVE findings. Verify that OS/packages were recognized."
head_summary+=(" - ${arch}: ⚠️ WARNING (0 vulnerability findings detected)") head_summary+=(" - ${arch}: ⚠️ WARNING (0 vulnerability findings detected)")
@@ -350,9 +426,49 @@ runs:
err_log="cve-reports/target/cve-${arch}.err" err_log="cve-reports/target/cve-${arch}.err"
echo "Scanning target SBOM for architecture: $arch" echo "Scanning target SBOM for architecture: $arch"
distro_arg=""
if [ "$use_inbuild" = "true" ]; then
detected_distro=$(detect_distro "$f")
if [ -n "$detected_distro" ]; then
echo "Detected distribution '${detected_distro}' from in-build SBOM PURLs. Scanning with --distro."
distro_arg="--distro ${detected_distro}"
fi
fi
scan_success=false
if trivy sbom $distro_arg --skip-version-check --format json --output "$out_json" "$f" 2>"$err_log"; then
scan_success=true
fi
vuln_cnt=0
if [ -s "$out_json" ]; then
cnt=$(grep -c '"VulnerabilityID"' "$out_json" 2>/dev/null || true)
cnt=$(echo "$cnt" | tr -d '[:space:]')
[ -n "$cnt" ] && vuln_cnt="$cnt"
fi
# Fallback if in-build SBOM scan produced 0 CVEs (e.g. incompatible third-party format) or scan failed
if [ "$use_inbuild" = "true" ] && { [ "$scan_success" != "true" ] || [ "$vuln_cnt" -eq 0 ]; }; then
echo "::warning::In-build SBOM for target image (${arch}) yielded ${vuln_cnt} CVE findings. Falling back to direct Trivy image scan..."
t_plat="linux/${arch}"
[ "$arch" = "default" ] && t_plat=""
plat_arg=""
[ -n "$t_plat" ] && plat_arg="--platform $t_plat"
if trivy image --skip-version-check $plat_arg --format spdx-json --output "$f" "${{ inputs.target-image }}"; then
echo "Re-scanning target SBOM generated directly by Trivy..."
if trivy sbom --skip-version-check --format json --output "$out_json" "$f" 2>"$err_log"; then if trivy sbom --skip-version-check --format json --output "$out_json" "$f" 2>"$err_log"; then
scan_success=true
if [ -s "$out_json" ]; then
cnt=$(grep -c '"VulnerabilityID"' "$out_json" 2>/dev/null || true)
cnt=$(echo "$cnt" | tr -d '[:space:]')
[ -n "$cnt" ] && vuln_cnt="$cnt"
fi
fi
fi
fi
if [ "$scan_success" = "true" ]; then
if [ -s "$out_json" ]; then if [ -s "$out_json" ]; then
vuln_cnt=$(grep -o '"VulnerabilityID"' "$out_json" 2>/dev/null | wc -l || echo 0)
if [ "$vuln_cnt" -eq 0 ]; then if [ "$vuln_cnt" -eq 0 ]; then
echo "::warning::Target baseline image architecture '${arch}' has 0 CVE findings. Verify that OS/packages were recognized." echo "::warning::Target baseline image architecture '${arch}' has 0 CVE findings. Verify that OS/packages were recognized."
target_summary+=(" - ${arch}: ⚠️ WARNING (0 vulnerability findings detected)") target_summary+=(" - ${arch}: ⚠️ WARNING (0 vulnerability findings detected)")
@@ -378,6 +494,12 @@ runs:
echo "================================================================================" echo "================================================================================"
echo " CVE VULNERABILITY SCAN DIAGNOSTIC OVERVIEW " echo " CVE VULNERABILITY SCAN DIAGNOSTIC OVERVIEW "
echo "================================================================================" echo "================================================================================"
if [ "$use_inbuild" = "true" ]; then
echo "Scan Strategy: Built-in OCI Attestation (Syft/Docker Scout) with distro detection"
else
echo "Scan Strategy: Direct Trivy Image Scan (Full rootfs & source package mapping)"
fi
echo "--------------------------------------------------------------------------------"
echo "HEAD Image: ${{ inputs.head-image }}" echo "HEAD Image: ${{ inputs.head-image }}"
for line in "${head_summary[@]}"; do for line in "${head_summary[@]}"; do
echo "$line" echo "$line"
+22 -22
View File
@@ -1,22 +1,22 @@
b8e8834a90b0f28c172498a96d1b91876eb2f09867f02aa314df9a862aa86227 trivy_0.74.0_FreeBSD-64bit.tar.gz 89c88a41cc259cb93951a4fc923fbeee145302c369cc098b2af56298569cfa06 trivy_0.75.0_FreeBSD-64bit.tar.gz
cea47f6624cab9123a6bebc2f6586f3f82b8cd3165fc0ecd21677ba8a4ca0f5c trivy_0.74.0_Linux-32bit.deb 21e1b4cfadfd4c975ee95c77b03c3ef3ffa8de732cbaf45b729e3bd5a59a4c67 trivy_0.75.0_Linux-32bit.deb
bec3a7aed02d60b6bb2996bb63897f604464c2db0b2b29dfc3cc7ce9959c8aaf trivy_0.74.0_Linux-32bit.rpm b617436b0b55c04f58f1f654ba80e98f1063d21ceaff2cc192f830bacb1d0aba trivy_0.75.0_Linux-32bit.rpm
048af0c9597a8e275f60bb9e0543748dbc76e22bdd52456b84ea0fa0cd2a078a trivy_0.74.0_Linux-32bit.tar.gz 5dca0e5b0579a1c5fff1bf7dc541185020a3df892fe7c3345a1bf22988cc0e9c trivy_0.75.0_Linux-32bit.tar.gz
cf1e32ec8d4d8823e023096a28cadb14f5b5123ce03f201fb633c5b76aa712dd trivy_0.74.0_Linux-64bit.deb b6d7b944b6cbf4e7de5b4bc6c3635900c50fed53b64b77b99a5fbc3136cd1d5d trivy_0.75.0_Linux-64bit.deb
2331b82ec0ce5529a09fad8ccbfe03987ec9277ff4fbd6e96c6cb0aff8103477 trivy_0.74.0_Linux-64bit.rpm fba2b73579645839892dfd7b94c5551983f0f094ff88efc94a69383de00a1f26 trivy_0.75.0_Linux-64bit.rpm
2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a trivy_0.74.0_Linux-64bit.tar.gz c6e65abddb348e25f10549df887045629cf28cc72453cd1c63acb717316b3f3f trivy_0.75.0_Linux-64bit.tar.gz
5665eaa443bfb117c5c4c373254f0ba9e2888985e6233588ce98bb0960f558c3 trivy_0.74.0_Linux-ARM.deb 6d35e18b2f09c3f389c5cf94f0f259c5320debf1ce16c37f010d9a8ca1bec88f trivy_0.75.0_Linux-ARM.deb
bdd73ea298ec538ffae3351614a3adefc8c48599fce7943e6376fcfa040c0e85 trivy_0.74.0_Linux-ARM.rpm a39a931e5b88050892e9811f843e0c05e29c73c03bac4afffc7508a45b215dff trivy_0.75.0_Linux-ARM.rpm
99adbe319fa685cb4bff04dd0077e31cb94828338cab5db7f78fe6dcecca8ec1 trivy_0.74.0_Linux-ARM.tar.gz 8cbe2286600ba56267fab0f10cf5696c252993b7b7935c5cef518abab3c84af4 trivy_0.75.0_Linux-ARM.tar.gz
14fae2604eab589fd544224ef56b8b8a44266872c43b259bd36cfde81428acc8 trivy_0.74.0_Linux-ARM64.deb 23018f1f7136b8391bb300e2579bcf4759e3c64012856f51c0fb6fffe1ee3841 trivy_0.75.0_Linux-ARM64.deb
bb753f2dacd22417ac7709fdd910a0b017f4bb43330eac8b51c2a90403ae2fd3 trivy_0.74.0_Linux-ARM64.rpm a28a78b82c06049d3b1d8c4b17ddb4586357ccf8e4b0aa1b30d0867beb3b11a0 trivy_0.75.0_Linux-ARM64.rpm
b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5 trivy_0.74.0_Linux-ARM64.tar.gz a1ee9f6ffb7d112b64ff726a2a0717c21175c1114361391f4a132956751a13b3 trivy_0.75.0_Linux-ARM64.tar.gz
16a1e36b6d405951b36f1e4b31bb7655f8551f1581934fcb2e6e5bdf8c46f6c9 trivy_0.74.0_Linux-PPC64LE.deb eed0d48c91904036a1c039c3c8c1c2b13eee8983b101c371dbe49c9ae24d8e01 trivy_0.75.0_Linux-PPC64LE.deb
cc7183af95dbab1ae1400fca17b35ca8f9591676a1042144994698cad9cac5a6 trivy_0.74.0_Linux-PPC64LE.rpm cfefab190d7d972735ed372ec32e5adda6af30ff24d7e0669bfd0eac9a15a1b5 trivy_0.75.0_Linux-PPC64LE.rpm
c5aabfe8789d0d43f578ef2d01174884c8ff0f2106c627a220a43f7bf9054959 trivy_0.74.0_Linux-PPC64LE.tar.gz dbd7403d4191b14efd5ee4a808c93bc97b56fa9f62dd98a691a61bf6e556642e trivy_0.75.0_Linux-PPC64LE.tar.gz
f8fe56cee9a502e82f05e45465943e3bc6e75de05530ffc7bddf93e65b0de5bb trivy_0.74.0_Linux-s390x.deb f440ffa772be9d517f5e03b93bfe99035bc28dd760d841877c5994259481c94a trivy_0.75.0_Linux-s390x.deb
4fb2d5bd1ab5e61917e73cd2936daeae1c5ab9af024321a222437d824bac5579 trivy_0.74.0_Linux-s390x.rpm c1269d4af420a69e81cb0b7cf234728db5bfe6ecc350c78f5f3bbfd0be1c228c trivy_0.75.0_Linux-s390x.rpm
516cfe38eaede79c178088265657aad12be6c155271ac68ff728a141961ef197 trivy_0.74.0_Linux-s390x.tar.gz 6acd3a3af4688567f5d3cdf3dd45e871ae060a88c07cea01401ae1487c4f19a3 trivy_0.75.0_Linux-s390x.tar.gz
472816f6888dda689d075c30254d4210b4d1035acf365aa72332f584c2f60485 trivy_0.74.0_macOS-64bit.tar.gz 291edaa9778acbe4693d067b5ad60ee11570e5ac68296e85595417528ca641e4 trivy_0.75.0_macOS-64bit.tar.gz
1caada5e0e2091909357c7525d3aa76f4b660b13821bc143b190c7483e31cc11 trivy_0.74.0_macOS-ARM64.tar.gz 4a77108cccf8e55c8d6823e1e759939a622277e66cd0daa3c1fc621ed69e4568 trivy_0.75.0_macOS-ARM64.tar.gz
94c40e0696e4b907a74b7b2e1438d5d72ebaca83115817407f568a002d520842 trivy_0.74.0_windows-64bit.zip 4e43bd71a30f51aee39525f60f2b47043af77eb8df8fe082aae4372b69c6660f trivy_0.75.0_windows-64bit.zip
+5 -4
View File
@@ -84,14 +84,15 @@ function loadReportsForArg(arg) {
process.exit(1); process.exit(1);
} }
if (!data || typeof data !== 'object' || !Array.isArray(data.Results)) { if (!data || typeof data !== 'object') {
console.error(`::error::Malformed Trivy report structure (missing Results array) in: ${filePath}`); console.error(`::error::Malformed Trivy report structure in: ${filePath}`);
process.exit(1); process.exit(1);
} }
const results = Array.isArray(data.Results) ? data.Results : [];
loadedFiles++; loadedFiles++;
for (const result of data.Results) { for (const result of results) {
const target = result.Target || result.Type || 'App'; const target = result.Target || result.Type || 'App';
const vulns = Array.isArray(result.Vulnerabilities) ? result.Vulnerabilities : []; const vulns = Array.isArray(result.Vulnerabilities) ? result.Vulnerabilities : [];
for (const v of vulns) { for (const v of vulns) {
@@ -135,7 +136,7 @@ function formatVulnRow(v, allScannedArchs = new Set(), showArch = true) {
const installed = v.InstalledVersion || '-'; const installed = v.InstalledVersion || '-';
const fixed = v.FixedVersion || '-'; const fixed = v.FixedVersion || '-';
let title = (v.Title || v.Description || '').replace(/\n/g, ' ').trim(); let title = (v.Title || v.Description || '').replace(/\n/g, ' ').replace(/\|/g, '\\|').trim();
if (title.length > 80) { if (title.length > 80) {
title = title.slice(0, 77) + '...'; title = title.slice(0, 77) + '...';
} }
+111
View File
@@ -0,0 +1,111 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const {
loadReportsForArg,
formatVulnRow,
renderMarkdownDiff,
renderFullSummary,
} = require('../cve-diff.js');
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
test('loadReportsForArg loads valid reports and extracts multi-arch metadata', () => {
const targetDir = path.join(FIXTURES_DIR, 'target');
const cves = loadReportsForArg(targetDir);
assert.equal(cves.size, 2);
assert.ok(cves.has('CVE-2023-0001:::glibc'));
assert.ok(cves.has('CVE-2023-0002:::openssl'));
const glibc = cves.get('CVE-2023-0001:::glibc');
assert.equal(glibc.Severity, 'CRITICAL');
assert.deepEqual(Array.from(glibc._archs).sort(), ['amd64', 'arm64']);
assert.deepEqual(Array.from(cves.scannedArchs).sort(), ['amd64', 'arm64']);
const openssl = cves.get('CVE-2023-0002:::openssl');
assert.equal(openssl.Severity, 'HIGH');
assert.deepEqual(Array.from(openssl._archs), ['amd64']);
});
test('loadReportsForArg handles reports with missing or null Results array without crashing', () => {
const emptyDir = path.join(FIXTURES_DIR, 'empty');
const cves = loadReportsForArg(emptyDir);
assert.equal(cves.size, 0);
});
test('renderMarkdownDiff produces correct introduced, resolved, and unchanged sets', () => {
const targetDir = path.join(FIXTURES_DIR, 'target');
const headDir = path.join(FIXTURES_DIR, 'head');
const baseCves = loadReportsForArg(targetDir);
const branchCves = loadReportsForArg(headDir);
const diff = renderMarkdownDiff(baseCves, branchCves, 'main');
// Introduced: CVE-2023-0003 (curl)
assert.ok(diff.includes('New Vulnerabilities Introduced (1)'));
assert.ok(diff.includes('CVE-2023-0003'));
assert.ok(diff.includes('`curl`'));
// Resolved: CVE-2023-0001 (glibc)
assert.ok(diff.includes('Resolved / Removed Vulnerabilities (1)'));
assert.ok(diff.includes('CVE-2023-0001'));
assert.ok(diff.includes('`glibc`'));
// Unchanged count
assert.ok(diff.includes('| ⚪ **Unchanged Vulnerabilities** | 1 |'));
// Multi-arch is present in fixtures (amd64, arm64), so Arch column should be shown
assert.ok(diff.includes('| Arch |'));
});
test('renderMarkdownDiff hides Arch column when single architecture is scanned', () => {
const targetSingle = path.join(FIXTURES_DIR, 'target', 'cve-amd64.json');
const headSingle = path.join(FIXTURES_DIR, 'head', 'cve-amd64.json');
const baseCves = loadReportsForArg(targetSingle);
const branchCves = loadReportsForArg(headSingle);
const diff = renderMarkdownDiff(baseCves, branchCves, 'main');
assert.ok(!diff.includes('| Arch |'));
assert.ok(diff.includes('New Vulnerabilities Introduced (1)'));
assert.ok(diff.includes('Resolved / Removed Vulnerabilities (1)'));
});
test('renderFullSummary outputs summary table and lists all vulnerabilities', () => {
const headDir = path.join(FIXTURES_DIR, 'head');
const headCves = loadReportsForArg(headDir);
const summary = renderFullSummary(headCves);
assert.ok(summary.includes('### 🛡️ Security Scan Report'));
assert.ok(summary.includes('| Total Vulnerabilities | **2** |'));
assert.ok(summary.includes('CVE-2023-0002'));
assert.ok(summary.includes('CVE-2023-0003'));
});
test('formatVulnRow properly formats badge, architecture tag, and escape pipes', () => {
const vuln = {
Severity: 'CRITICAL',
VulnerabilityID: 'CVE-2023-0001',
PrimaryURL: 'https://avd.aquasec.com/nvd/cve-2023-0001',
PkgName: 'glibc',
InstalledVersion: '2.31',
FixedVersion: '2.32',
Title: 'A vulnerability with | pipe symbol',
_archs: new Set(['amd64']),
};
const allArchs = new Set(['amd64', 'arm64']);
const rowWithArch = formatVulnRow(vuln, allArchs, true);
assert.ok(rowWithArch.includes('🔴 CRITICAL'));
assert.ok(rowWithArch.includes('`amd64`'));
assert.ok(rowWithArch.includes('\\|')); // Pipe escaped
const rowWithoutArch = formatVulnRow(vuln, allArchs, false);
assert.ok(!rowWithoutArch.includes('`amd64`'));
});
+8
View File
@@ -0,0 +1,8 @@
{
"SchemaVersion": 2,
"ArtifactName": "debian:bullseye-slim",
"ArtifactType": "spdx",
"Metadata": {
"OS": null
}
}
+30
View File
@@ -0,0 +1,30 @@
{
"SchemaVersion": 2,
"ArtifactName": "debian:bookworm-slim",
"ArtifactType": "container_image",
"Results": [
{
"Target": "debian:bookworm-slim (debian 12)",
"Class": "os-pkgs",
"Type": "debian",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2023-0002",
"PkgName": "openssl",
"InstalledVersion": "1.1.1n-0+deb11u4",
"FixedVersion": "1.1.1n-0+deb11u5",
"Severity": "HIGH",
"Title": "Timing attack in OpenSSL"
},
{
"VulnerabilityID": "CVE-2023-0003",
"PkgName": "curl",
"InstalledVersion": "7.88.1-10",
"FixedVersion": "7.88.1-10+deb12u1",
"Severity": "MEDIUM",
"Title": "Buffer overflow in curl"
}
]
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"SchemaVersion": 2,
"ArtifactName": "debian:bookworm-slim",
"ArtifactType": "container_image",
"Results": [
{
"Target": "debian:bookworm-slim (debian 12)",
"Class": "os-pkgs",
"Type": "debian",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2023-0003",
"PkgName": "curl",
"InstalledVersion": "7.88.1-10",
"FixedVersion": "7.88.1-10+deb12u1",
"Severity": "MEDIUM",
"Title": "Buffer overflow in curl"
}
]
}
]
}
+36
View File
@@ -0,0 +1,36 @@
{
"SchemaVersion": 2,
"ArtifactName": "debian:bullseye-slim",
"ArtifactType": "container_image",
"Metadata": {
"OS": {
"Family": "debian",
"Name": "11"
}
},
"Results": [
{
"Target": "debian:bullseye-slim (debian 11)",
"Class": "os-pkgs",
"Type": "debian",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2023-0001",
"PkgName": "glibc",
"InstalledVersion": "2.31-13+deb11u5",
"FixedVersion": "2.31-13+deb11u6",
"Severity": "CRITICAL",
"Title": "Memory corruption in glibc"
},
{
"VulnerabilityID": "CVE-2023-0002",
"PkgName": "openssl",
"InstalledVersion": "1.1.1n-0+deb11u4",
"FixedVersion": "1.1.1n-0+deb11u5",
"Severity": "HIGH",
"Title": "Timing attack in OpenSSL"
}
]
}
]
}
+22
View File
@@ -0,0 +1,22 @@
{
"SchemaVersion": 2,
"ArtifactName": "debian:bullseye-slim",
"ArtifactType": "container_image",
"Results": [
{
"Target": "debian:bullseye-slim (debian 11)",
"Class": "os-pkgs",
"Type": "debian",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2023-0001",
"PkgName": "glibc",
"InstalledVersion": "2.31-13+deb11u5",
"FixedVersion": "2.31-13+deb11u6",
"Severity": "CRITICAL",
"Title": "Memory corruption in glibc"
}
]
}
]
}