Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions .github/scripts/check-cves-deep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# Deep CVE audit: OWASP dependency-check (NVD/CPE matching) over the full
# reactor including the resolved Tycho target platform. Complements the fast
# per-PR check (check-cves.sh): CPE matching covers Eclipse-native bundles and
# embedded code that have no Maven identity, at the price of a large NVD
# database and fuzzier matching (see dependency-check-suppressions.xml).
#
# Self-test: a 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 it MUST appear in the report's suppressed section - proving the
# whole pipeline (scan, database, matching, suppression parsing) actually ran.
#
# No NVD API key is needed: the database is built from the dependency-check
# project's nightly NVD mirror (see nvdDatafeedUrl in ddk-parent/pom.xml).
# A full build from an empty data directory takes about a minute.
#
# Usage: check-cves-deep.sh [--no-update]
# --no-update skip the database update entirely (fast local iteration
# against an existing data directory)
#
# Environment:
# CVE_SCAN_THREADS Maven -T value (default: 2C)
# ODC_DATA_DIRECTORY dependency-check database directory (default:
# ~/.dependency-check)
#
# Exit codes: 0 = clean, 1 = findings >= CVSS 7 (failBuildOnCVSS), 2 = infra
# or self-test failure.
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
REPORT="${REPO_ROOT}/ddk-parent/target/dependency-check-report.json"
CANARY_DIR="${REPO_ROOT}/ddk-parent/target/cve-canary"
CANARY_JAR="commons-collections-3.2.1.jar"
CANARY_URL="https://repo1.maven.org/maven2/commons-collections/commons-collections/3.2.1/${CANARY_JAR}"
CANARY_SHA1="761ea405b9b37ced573d2df0d1e3a4e0f9edc668"
CANARY_CVE="CVE-2015-6420"
THREADS="${CVE_SCAN_THREADS:-2C}"
DATA_DIR="${ODC_DATA_DIRECTORY:-${HOME}/.dependency-check}"

NO_UPDATE=false
[ "${1:-}" = "--no-update" ] && NO_UPDATE=true

for tool in jq curl mvn; do
if ! command -v "${tool}" > /dev/null; then
echo "ERROR: '${tool}' is required but not on PATH." >&2
exit 2
fi
done

echo "Planting canary jar..."
mkdir -p "${CANARY_DIR}"
if [ ! -f "${CANARY_DIR}/${CANARY_JAR}" ]; then
curl -sSf --retry 3 --max-time 60 -o "${CANARY_DIR}/${CANARY_JAR}" "${CANARY_URL}"
fi
actual_sha1="$(shasum "${CANARY_DIR}/${CANARY_JAR}" | cut -d' ' -f1)"
if [ "${actual_sha1}" != "${CANARY_SHA1}" ]; then
echo "ERROR: canary jar sha1 mismatch (${actual_sha1}) - refusing to scan." >&2
exit 2
fi

extra_flags=()
[ "${NO_UPDATE}" = "true" ] && extra_flags+=("-DautoUpdate=false")

echo "Running dependency-check aggregate (builds/refreshes the NVD database from the nightly mirror, ~1 min from empty)..."
rm -f "${REPORT}" # a stale report must never satisfy the canary gate
start="$(date +%s)"
scan_log="$(mktemp)"
# dependency:resolve is required: aggregate alone runs only on the root module
# and sees none of the Tycho-injected target-platform dependencies.
set +e
mvn -f "${REPO_ROOT}/ddk-parent/pom.xml" -T "${THREADS}" --batch-mode \
validate \
org.apache.maven.plugins:maven-dependency-plugin:3.8.1:resolve \
org.owasp:dependency-check-maven:13.0.0:aggregate \
-DdataDirectory="${DATA_DIR}" \
"${extra_flags[@]+"${extra_flags[@]}"}" > "${scan_log}" 2>&1
mvn_exit=$?
set -e
elapsed="$(( $(date +%s) - start ))"

if [ ! -f "${REPORT}" ]; then
echo "ERROR: no report at ${REPORT}; last 30 lines of the scan log:" >&2
tail -30 "${scan_log}" >&2
rm -f "${scan_log}"
exit 2
fi
rm -f "${scan_log}"

# Canary self-test: the canary CVE is suppressed by design (so it cannot fail
# the build) but it must be present in the suppressed section of the report.
canary_hits="$(jq --arg cve "${CANARY_CVE}" \
'[.dependencies[] | select(.fileName | test("commons-collections-3\\.2\\.1")) |
(.suppressedVulnerabilities // [])[] | select(.name == $cve)] | length' "${REPORT}")"
if [ "${canary_hits}" -eq 0 ]; then
echo "SCAN SELF-TEST FAILED: canary ${CANARY_CVE} not detected on ${CANARY_JAR}." >&2
echo "The scan pipeline is broken - do not trust this report." >&2
exit 2
fi
echo "Canary self-test passed (${CANARY_CVE} detected and suppressed as designed)."

scanned="$(jq '.dependencies | length' "${REPORT}")"
flagged="$(jq '[.dependencies[] | select(.vulnerabilities != null)] | length' "${REPORT}")"
suppressed="$(jq '[.dependencies[] | (.suppressedVulnerabilities // [])[]] | length' "${REPORT}")"
echo ""
echo "Deep scan: ${scanned} dependencies scanned in ${elapsed}s; ${flagged} with unsuppressed findings, ${suppressed} suppressed matches (see suppressions file)."

if [ "${flagged}" -gt 0 ]; then
echo ""
echo "Unsuppressed findings (build fails at CVSS >= 7):"
jq -r '.dependencies[] | select(.vulnerabilities != null) |
" \(.fileName)\n \([.vulnerabilities[] | "\(.name) (CVSS \(.cvssv3.baseScore // .cvssv2.score // "?"))"] | join(", "))"' "${REPORT}"
fi

if [ "${mvn_exit}" -ne 0 ]; then
echo ""
echo "FAILED: findings at or above the CVSS 7 gate. Fix the dependency (usually"
echo "via ddk-target/ddk.target) or add a justified suppression to"
echo ".github/security/dependency-check-suppressions.xml."
exit 1
fi
echo "OK: no findings at or above the CVSS 7 gate."
exit 0
167 changes: 167 additions & 0 deletions .github/security/dependency-check-suppressions.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Suppression rules for the cve-deep-scan lane (OWASP dependency-check).

Policy: every rule needs <notes> explaining why the match is a false positive
or an accepted risk, and should be as narrow as its class allows. Grown from
triage of real runs; do not pre-emptively suppress. The fast-lane ledger
(.github/security/cve-ignores.json) covers OSV advisory ids separately;
accepted risks are mirrored in both with the same review dates.

Recurring false-positive mechanics in this reactor:
- OSGi bundle versions are not product versions (org.eclipse.equinox.http.jetty
3.9.700 is not Jetty 3.9), so CPE version-range checks misfire,
- Eclipse ships fixes without NVD narrowing the CPE range, so long-fixed
platform CVEs re-attach to current builds.
-->
<suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">

<!-- ================= scan self-test ================= -->

<suppress>
<notes><![CDATA[
Canary jar planted by check-cves-deep.sh. The scan must detect
CVE-2015-6420 on it (the script verifies this via the report's suppressed
section), but the canary must not fail the build. Scoped to the canary
directory only.
]]></notes>
<filePath regex="true">.*[/\\]cve-canary[/\\]commons-collections-3\.2\.1\.jar</filePath>
<cve>CVE-2015-6420</cve>
</suppress>

<!-- ================= false positives ================= -->

<suppress>
<notes><![CDATA[
CVE-2014-125035 is a 2014 advisory against an unrelated product that
attaches to current org.eclipse.* bundles on low-confidence evidence
without any CPE (verified: flagged a 2026 build of org.eclipse.core.jobs).
]]></notes>
<filePath regex="true">.*org\.eclipse\..*\.jar</filePath>
<cve>CVE-2014-125035</cve>
</suppress>

<suppress>
<notes><![CDATA[
org.eclipse.equinox.http.jetty's bundle version (3.9.x) is matched as
Jetty 3.9, attaching 2009-2017 Jetty CVEs. The actual Jetty in the target
platform is 10.x/12.x and is assessed separately.
]]></notes>
<filePath regex="true">.*org\.eclipse\.equinox\.http\.jetty.*\.jar</filePath>
<cpe>cpe:/a:jetty:jetty</cpe>
</suppress>
<suppress>
<notes><![CDATA[ Same bundle, same mechanics, alternate vendor spelling. ]]></notes>
<filePath regex="true">.*org\.eclipse\.equinox\.http\.jetty.*\.jar</filePath>
<cpe>cpe:/a:eclipse:jetty</cpe>
</suppress>

<suppress>
<notes><![CDATA[
mortbay-apache-el/-jsp are Jetty's repackaged Tomcat EL/JSP artifacts;
their 9.0.x (Tomcat) version is matched as Jetty 9.0, attaching old Jetty
CVEs (verified: CVE-2017-765x era).
]]></notes>
<filePath regex="true">.*mortbay-apache-(el|jsp)-.*\.jar</filePath>
<cpe>cpe:/a:jetty:jetty</cpe>
</suppress>
<suppress>
<notes><![CDATA[ Same artifacts, alternate vendor spelling. ]]></notes>
<filePath regex="true">.*mortbay-apache-(el|jsp)-.*\.jar</filePath>
<cpe>cpe:/a:eclipse:jetty</cpe>
</suppress>

<suppress>
<notes><![CDATA[
CVE-2021-41033 (Equinox p2, fixed in 2021 / Eclipse 4.21) re-attaches to
current p2 bundles because their per-bundle versions (1.x/2.x) defeat the
platform-version range. Target platform is Eclipse 2026-06.
]]></notes>
<filePath regex="true">.*org\.eclipse\.equinox\.p2\..*\.jar</filePath>
<cve>CVE-2021-41033</cve>
</suppress>

<suppress>
<notes><![CDATA[
CVE-2023-4218 (PDE .api_description XXE, fixed in 2023) re-attaches to
current PDE bundles for the same bundle-version reason. Target platform
is Eclipse 2026-06; PDE is build/IDE tooling, not shipped.
]]></notes>
<filePath regex="true">.*(org\.eclipse\.pde\..*|pdebuild)\.jar</filePath>
<cve>CVE-2023-4218</cve>
</suppress>

<suppress>
<notes><![CDATA[
CVE-2020-27225 (Equinox help/update authentication, fixed in Eclipse 4.18)
against a 2026 build of org.eclipse.update.configurator.
]]></notes>
<filePath regex="true">.*org\.eclipse\.update\.configurator.*\.jar</filePath>
<cve>CVE-2020-27225</cve>
</suppress>

<suppress>
<notes><![CDATA[
CVE-2019-11770 concerns Eclipse Buildship (the Gradle integration);
org.eclipse.xtext.buildship is a different product that collides on the
product name.
]]></notes>
<filePath regex="true">.*org\.eclipse\.xtext\.buildship.*\.jar</filePath>
<cve>CVE-2019-11770</cve>
</suppress>

<!-- ================= accepted risks (mirrored in cve-ignores.json) ================= -->

<suppress>
<notes><![CDATA[
CVE-2026-24400 / GHSA-rqfh-9r24-8c9r: assertj XXE in isXmlEqualTo, local
vector. assertj-core IS shipped in ddk-repository (via check.ui /
checkcfg.ui); accepted until the target platform picks up a fixed assertj.
Mirrors cve-ignores.json. Review by 2026-11-01.
]]></notes>
<filePath regex="true">.*assertj-core-.*\.jar</filePath>
<cve>CVE-2026-24400</cve>
</suppress>

<suppress>
<notes><![CDATA[
CVE-2026-10050 / CVE-2026-10051 (Jetty 12.1.x): Jetty is pulled only by
com.avaloq.tools.ddk.xtext.test (SWTBot/Equinox test infrastructure) and
is not part of the shipped update site. Mirrors the GHSA entries in
cve-ignores.json. Review by 2027-02-01.
]]></notes>
<filePath regex="true">.*jetty-[a-z0-9]+(-[a-z0-9]+)*-12\..*\.jar</filePath>
<cve>CVE-2026-10050</cve>
</suppress>
<suppress>
<notes><![CDATA[ Same scope and rationale as CVE-2026-10050 above. ]]></notes>
<filePath regex="true">.*jetty-[a-z0-9]+(-[a-z0-9]+)*-12\..*\.jar</filePath>
<cve>CVE-2026-10051</cve>
</suppress>

<suppress>
<notes><![CDATA[
mortbay-apache-jsp embeds the Tomcat 9.0.x Jasper JSP compiler, so the
apache:tomcat CPE match is legitimate rather than a name collision; most
Tomcat CVEs target the server container rather than the embedded Jasper
subset, and the artifact is pulled only by the SWTBot/Equinox test
infrastructure - it is not part of the shipped update site. Accepted for
test scope; review by 2027-02-01 or when SWTBot updates its Jetty stack.
]]></notes>
<filePath regex="true">.*mortbay-apache-jsp-.*\.jar</filePath>
<cpe>cpe:/a:apache:tomcat</cpe>
</suppress>

<suppress>
<notes><![CDATA[
CVE-2019-10249 (Xtext/Xtend < 2.17: HTTP repository in mwe2 tooling)
against the org.eclipse.xtend* 2.2.0.v201605260315 bundles - the legacy
Xpand-era generator toolchain, used at build time only and frozen
upstream (Xpand is archived; no fixed version exists). Not shipped.
Review when the Xpand dependency is dropped from ddk.target.
]]></notes>
<filePath regex="true">.*org\.eclipse\.xtend(\.[a-z.]+)?-2\.2\.0\..*\.jar</filePath>
<cve>CVE-2019-10249</cve>
</suppress>

</suppressions>
64 changes: 64 additions & 0 deletions .github/workflows/cve-deep-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: cve-deep-scan
on:
schedule:
- cron: '17 4 * * 1' # Mondays 04:17 UTC
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/cve-deep-scan.yml'
- '.github/scripts/check-cves-deep.sh'
- '.github/security/dependency-check-suppressions.xml'
jobs:
cve-deep-scan:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
issues: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: 'temurin'
java-version: '21'
- name: Set up Workspace Environment Variable
run: echo "WORKSPACE=${{ github.workspace }}" >> $GITHUB_ENV
- name: Restore Maven dependency cache
# Restore-only: PR scopes cannot share caches with each other, so per-PR
# saves are dead weight that evicts the useful master-scoped caches
# (10 GB repo budget). The producer is snapshot.yml on master pushes
# (Linux-maven-publish-*). Path and key must mirror snapshot.yml exactly:
# the literal path spec is hashed into the cache *version*, so any
# variation (~/.m2 vs /home/runner/.m2) makes its caches unmatchable.
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-publish-${{ hashFiles('**/pom.xml', '**/*.target') }}
restore-keys: ${{ runner.os }}-maven-publish-
- name: Deep CVE audit (dependency-check)
# No secrets: the NVD database is built from the dependency-check
# project's nightly mirror in about a minute (see nvdDatafeedUrl in
# ddk-parent/pom.xml), so there is nothing worth caching either.
run: bash .github/scripts/check-cves-deep.sh
- name: Archive dependency-check reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: cve-deep-scan-reports
path: |
ddk-parent/target/dependency-check-report.json
ddk-parent/target/dependency-check-report.html
retention-days: 30
- name: Open or update findings issue
if: failure() && github.event_name == 'schedule'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
title='[cve-deep-scan] findings'
body="The scheduled deep CVE audit failed on $(date -u +%F). See the run for reports: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
existing="$(gh issue list --state open --search "in:title ${title}" --json number --jq '.[0].number // empty')"
if [ -n "${existing}" ]; then
gh issue comment "${existing}" --body "${body}"
else
gh issue create --title "${title}" --body "${body}"
fi
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ When the check fails:

A failure reading `SCAN SELF-TEST FAILED` means the scan itself is broken or the OSV API is unreachable — the check fails loudly rather than passing silently. Re-run it; do not bypass it.

A second, non-blocking audit (the `cve-deep-scan` workflow) runs weekly with OWASP dependency-check, covering Eclipse-native bundles that have no Maven identity. Its reports land as workflow artifacts, failures open a `[cve-deep-scan] findings` issue, and its suppression rules live in `.github/security/dependency-check-suppressions.xml` (same policy: every rule needs a justification). No API key is needed: the vulnerability database is built from the dependency-check project's nightly NVD mirror in about a minute. Run it locally with `bash .github/scripts/check-cves-deep.sh` (or `--no-update` for fast iteration against an existing database).

## Guidelines for Pull Requests

* Provide a good pull request description
Expand Down
28 changes: 28 additions & 0 deletions ddk-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,34 @@
<skipNotDeployed>false</skipNotDeployed>
</configuration>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>13.0.0</version>
<configuration>
<!-- build the database from the dependency-check project's nightly NVD mirror instead of
the NVD API: no API key needed, and a full build takes ~1 minute instead of 10-40 -->
<nvdDatafeedUrl>https://dependency-check.github.io/DependencyCheck_Builder/nvd_cache/nvdcve-{0}.json.gz</nvdDatafeedUrl>
<!-- search.maven.org rate-limits bulk lookups into self-disabling after ~50 wasted minutes -->
<centralAnalyzerEnabled>false</centralAnalyzerEnabled>
<!-- OSS Index requires authentication since its Sonatype Guide migration -->
<ossIndexAnalyzerEnabled>false</ossIndexAnalyzerEnabled>
<!-- no dotnet runtime on the runners; some Eclipse bundles embed .dll resources -->
<assemblyAnalyzerEnabled>false</assemblyAnalyzerEnabled>
<failBuildOnCVSS>7</failBuildOnCVSS>
<suppressionFile>${maven.multiModuleProjectDirectory}/.github/security/dependency-check-suppressions.xml</suppressionFile>
<formats>
<format>JSON</format>
<format>HTML</format>
</formats>
<!-- extra scan path for the canary jar placed by check-cves-deep.sh -->
<scanSet>
<fileSet>
<directory>${maven.multiModuleProjectDirectory}/ddk-parent/target/cve-canary</directory>
</fileSet>
</scanSet>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Expand Down