diff --git a/.github/scripts/check-cves-deep.sh b/.github/scripts/check-cves-deep.sh
new file mode 100644
index 000000000..fbb28e539
--- /dev/null
+++ b/.github/scripts/check-cves-deep.sh
@@ -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
diff --git a/.github/security/dependency-check-suppressions.xml b/.github/security/dependency-check-suppressions.xml
new file mode 100644
index 000000000..870eaefa2
--- /dev/null
+++ b/.github/security/dependency-check-suppressions.xml
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+ .*[/\\]cve-canary[/\\]commons-collections-3\.2\.1\.jar
+ CVE-2015-6420
+
+
+
+
+
+
+ .*org\.eclipse\..*\.jar
+ CVE-2014-125035
+
+
+
+
+ .*org\.eclipse\.equinox\.http\.jetty.*\.jar
+ cpe:/a:jetty:jetty
+
+
+
+ .*org\.eclipse\.equinox\.http\.jetty.*\.jar
+ cpe:/a:eclipse:jetty
+
+
+
+
+ .*mortbay-apache-(el|jsp)-.*\.jar
+ cpe:/a:jetty:jetty
+
+
+
+ .*mortbay-apache-(el|jsp)-.*\.jar
+ cpe:/a:eclipse:jetty
+
+
+
+
+ .*org\.eclipse\.equinox\.p2\..*\.jar
+ CVE-2021-41033
+
+
+
+
+ .*(org\.eclipse\.pde\..*|pdebuild)\.jar
+ CVE-2023-4218
+
+
+
+
+ .*org\.eclipse\.update\.configurator.*\.jar
+ CVE-2020-27225
+
+
+
+
+ .*org\.eclipse\.xtext\.buildship.*\.jar
+ CVE-2019-11770
+
+
+
+
+
+
+ .*assertj-core-.*\.jar
+ CVE-2026-24400
+
+
+
+
+ .*jetty-[a-z0-9]+(-[a-z0-9]+)*-12\..*\.jar
+ CVE-2026-10050
+
+
+
+ .*jetty-[a-z0-9]+(-[a-z0-9]+)*-12\..*\.jar
+ CVE-2026-10051
+
+
+
+
+ .*mortbay-apache-jsp-.*\.jar
+ cpe:/a:apache:tomcat
+
+
+
+
+ .*org\.eclipse\.xtend(\.[a-z.]+)?-2\.2\.0\..*\.jar
+ CVE-2019-10249
+
+
+
diff --git a/.github/workflows/cve-deep-scan.yml b/.github/workflows/cve-deep-scan.yml
new file mode 100644
index 000000000..9737d826c
--- /dev/null
+++ b/.github/workflows/cve-deep-scan.yml
@@ -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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e901c855f..90fbcda62 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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
diff --git a/ddk-parent/pom.xml b/ddk-parent/pom.xml
index 38c921c89..7260667bd 100644
--- a/ddk-parent/pom.xml
+++ b/ddk-parent/pom.xml
@@ -452,6 +452,34 @@
false
+
+ org.owasp
+ dependency-check-maven
+ 13.0.0
+
+
+ https://dependency-check.github.io/DependencyCheck_Builder/nvd_cache/nvdcve-{0}.json.gz
+
+ false
+
+ false
+
+ false
+ 7
+ ${maven.multiModuleProjectDirectory}/.github/security/dependency-check-suppressions.xml
+
+ JSON
+ HTML
+
+
+
+
+ ${maven.multiModuleProjectDirectory}/ddk-parent/target/cve-canary
+
+
+
+