Skip to content

ci: add a weekly NVD-based deep CVE audit - #1492

Draft
joaodinissf wants to merge 2 commits into
feat/cve-fast-scanfrom
feat/cve-deep-scan
Draft

ci: add a weekly NVD-based deep CVE audit#1492
joaodinissf wants to merge 2 commits into
feat/cve-fast-scanfrom
feat/cve-deep-scan

Conversation

@joaodinissf

@joaodinissf joaodinissf commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

What

Adds the second, complementary tier to dependency vulnerability scanning: a non-blocking, scheduled deep audit using OWASP dependency-check (NVD/CPE matching) over the full reactor including the resolved Tycho target platform.

It covers exactly what the per-PR OSV check structurally cannot see:

  • Eclipse-native bundles with no Maven identity (the 314 pkg:p2-only components recorded by the fast lane as its documented gap),
  • code embedded in repackaged jars — e.g. the Tomcat Jasper JSP compiler inside mortbay-apache-jsp, which no purl reveals.

Semantics: weekly cron + manual dispatch; reports uploaded as artifacts; scheduled failures open/update a single [cve-deep-scan] findings issue. It never gates PRs — the two lanes deliberately don't gate on each other's blind spots. It also triggers on pull_request for its own three files, so lane changes get a real run before merge.

No secrets, no caching: the NVD database is built from the dependency-check project's nightly NVD mirror (nvdDatafeedUrl) — the same source the official Dependency-Check_Action uses. Measured 116s end-to-end from an empty data directory including the full 419-dependency scan, vs 10–40 min through the throttled NVD API. Every run starts fresh: no API key to provision, no cache lifecycle, no incremental-update edge cases.

Measured comparison (local, same dependency state)

cve-scan (per-PR) cve-deep-scan (weekly)
Engine OSV batch API, exact purls dependency-check 13.0.0, CPE matching
Dependencies seen 146 Maven purls 419 (target platform + canary)
Time 0.6–39s local, 2m19s CI 116s cold incl. DB build; 39s warm
Raw matches before triage 5 (incl. canary; zero noise) 34 flagged / 66 matches (CPE noise, triaged)
Unsuppressed today 0 (5 accepted in ledger) 1 informational below the CVSS-7 gate (log4j-api CVE-2026-49844, 5.9)

The suppression file ships with a triaged baseline, not an empty file: each rule documents its false-positive mechanics (OSGi bundle versions misread as product versions; long-fixed Eclipse CVEs whose CPE ranges NVD never narrowed) or its accepted-risk rationale mirrored from the fast lane's ledger with the same review dates.

Fail-loud design

  • sha1-verified canary jar planted into the scan set; its CVE is suppressed (canary can't fail the build) but must appear in the report's suppressed section, proving scan + database + matching + suppression parsing all ran; stale reports are deleted so they can never satisfy the gate,
  • dependency:resolve precedes the aggregate goal — without it the aggregate sees only the reactor's own 60 modules instead of 419 dependencies (measured),
  • mirror outage → the update step fails loudly; the weekly cadence tolerates retries.

Notes

  • pom change is pluginManagement-only; default build unaffected.
  • Local usage: bash .github/scripts/check-cves-deep.sh (or --no-update for fast iteration; documented in CONTRIBUTING.md).
  • The one visible finding (log4j-api, CVSS 5.9, fix not yet in any Eclipse Orbit release) is deliberately left visible below the gate: patching CVEs is out of scope for this PR — it conveniently demonstrates the mechanism working.
  • Based on the fast-lane branch; retarget to master after ci: check every pull request for dependencies with known CVEs #1488 merges.

🤖 Generated with Claude Code

joaodinissf and others added 2 commits August 16, 2026 19:45
Pin org.owasp:dependency-check-maven in pluginManagement (no lifecycle
binding; the default build is unaffected) with the settings a usable scan
of this reactor requires:

- the vulnerability database is built from the dependency-check
  project's nightly NVD mirror (nvdDatafeedUrl) instead of the NVD API:
  no API key needed, and a full build from an empty data directory takes
  about a minute instead of 10-40 (the API is heavily throttled),
- the Central analyzer is disabled: search.maven.org rate-limits bulk
  SHA-1 lookups until the analyzer disables itself, wasting ~50 minutes
  per run,
- the OSS Index analyzer is disabled (authentication required since the
  Sonatype Guide migration) and the .NET assembly analyzer is disabled
  (no dotnet runtime on the runners),
- failBuildOnCVSS=7 with JSON+HTML reports,
- a scanSet entry adds the canary jar planted by check-cves-deep.sh.

The suppression file starts from a triaged baseline of the current
target platform rather than an empty file. Recurring false-positive
mechanics documented inline: OSGi bundle versions are not product
versions (equinox.http.jetty 3.9.x is not Jetty 3.9), and long-fixed
Eclipse CVEs re-attach to current builds because NVD never narrows the
CPE range. Accepted risks (assertj XXE, test-only Jetty/Jasper) mirror
the fast-lane ledger with the same review dates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Complement the per-PR OSV check with a scheduled dependency-check lane
covering what purl matching structurally cannot see: Eclipse-native
bundles without Maven identity and code embedded in repackaged jars
(e.g. the Jasper JSP compiler inside mortbay-apache-jsp). Non-blocking:
weekly cron plus manual dispatch, reports uploaded as artifacts, and
scheduled failures open or update a single '[cve-deep-scan] findings'
issue instead of gating PRs.

The lane needs no secrets and no caching: the NVD database is built
from the dependency-check project's nightly mirror in about a minute
(measured 116s end-to-end from an empty data directory, including the
full 419-dependency scan), so every run starts fresh - no API key, no
cache lifecycle, no incremental-update edge cases.

The scan runs dependency:resolve before the aggregate goal - the
aggregate alone executes only on the root module and sees none of the
Tycho-injected target-platform dependencies (measured: 60 vs 419
dependencies).

Self-test: a sha1-verified canary jar with a well-known CVE is planted
into the scan set; its CVE is suppressed (scoped to the canary path) so
it cannot fail the build, but the script requires it to appear in the
report's suppressed section - proving scan, database, matching and
suppression parsing all ran. A stale report cannot satisfy the gate
(deleted before each run).

The workflow also triggers on pull_request for its own files, so
changes to the lane get a real run before merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@joaodinissf

Copy link
Copy Markdown
Collaborator Author

Both lanes measured, same dependency state (deep lane runs keyless — the NVD database builds from the dependency-check project's nightly mirror in ~1 min; no secret configured in this repo):

cve-scan (per-PR, blocking) cve-deep-scan (weekly, non-blocking)
Engine OSV batch API, exact purls dependency-check, NVD/CPE matching
Dependencies seen 146 Maven purls 417 top-level (full target-platform closure; +111 same-family jars merged into them by the Dependency Merging Analyzer)
CI wall-clock 2m14s 2m30s (incl. database build)
Verdict today green — 5 accepted in ledger green — 1 informational below the CVSS-7 gate (log4j-api, 5.9; fix not yet in any Orbit release)

Coverage was cross-checked against the aggregate SBOM: every external artifact in the SBOM appears in the deep-scan report, either as a top-level entry or merged into a same-family sibling — nothing silently dropped. The deep lane's 65 suppressed matches are the triaged CPE false-positive baseline, each rule justified in dependency-check-suppressions.xml.

Posted by Claude (Opus 5) on João's behalf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant