A Dependency-Track v5 vulnerability analyzer that adds config-aware Linux
kernel CVE triage. It matches the kernel component in a project's SBOM, resolves
the corresponding KernelScan product, and reports only the CVEs that are
reachable in that kernel's .config — turning DT's config-blind NVD kernel
noise into an actionable list.
Open-source client for the commercial KernelScan service. It runs in-process in your DT apiserver and is read-only outbound: it fetches verdicts from your KernelScan account and never transmits your SBOM, components, or findings anywhere — see SECURITY.md.
- Registers as a first-class analyzer next to Snyk / Trivy / VulnDB (DT's plugin extension point) — no fork, no core changes.
- Plain
CVE-*/NVDidentity: findings merge with DT's own NVD records, no duplicates. - Contained failures: a KernelScan outage or bad key yields "no findings from this analyzer", never a failed project analysis.
DT's stock NVD analyzer matches every CVE for a kernel version by CPE (config-blind). This analyzer, per kernel component:
- reads the kernel version from the component's CPE (or purl),
- resolves the customer's KernelScan product for that version + arch,
- fetches its config-aware VEX, and
- reports the config-reachable CVEs (
exploitable/in_triage) as findings — omitting the config-gated (not_affected) ones.
Example (Debian-based appliance, kernel 5.15.102): NVD alone flags ~5,000 kernel CVEs; this analyzer reports the ~1,000 reachable in the running config.
With the analyzer enabled and the product VEX imported, a project's kernel CVEs are triaged in place — config-reachable ones actionable, config-gated ones auto-marked Not Affected.
Audit view of a Xen Dom0 appliance (kernel 6.1.86): each finding carries an analyzer attribution and a config-aware verdict.
Every verdict travels with KernelScan's config-mapping justification — here the
CVE is Exploitable because CONFIG_PCIEASPM is enabled in the running
.config (confidence 1.0).
Reading the Analyzer column. Findings attributed to
kernelscan(rather than DT'sinternalNVD analyzer) are KernelScan's contribution — including CVEs DT's NVD matching misses. Example: CVE-2024-26655 appears as akernelscanentry on all three appliances above because NVD's version data tracks only the mainline introduction (6.7) and misses the fix's backport to the 5.15 / 6.1 / 6.6 LTS branches — so DT's NVD analyzer never matches those kernels, while KernelScan's per-branch tracking (fixed in 5.15.199 / 6.1.162 / 6.6.122) correctly flags them. Each such finding also links straight tokernelscan.io/cve/<id>for the full CVE analysis.
This plugin is stage 1 of a two-stage integration. Knowing what each stage does (and deliberately does not) avoids surprises.
- Fast matching, including CVEs NVD lacks. The analyzer reports the
config-reachable kernel CVEs — including ones NVD has only just published,
or hasn't scored yet, but KernelScan already tracks. Coverage NVD's CPE
matching misses appears here, attributed to
kernelscan. - No CVSS score for some of them — expected. For a CVE where DT's NVD
mirror has the record but no score (e.g. a brand-new CVE NVD hasn't scored
yet, even though KernelScan has), DT shows the finding with no score. DT
treats its own NVD mirror as authoritative and will not let an analyzer fill
CVSS on a mirror-owned NVD record (
isUpdatableByAnalyzer("NVD") == false). The finding is still there; the score stays blank until NVD — and DT's mirror — catch up. (For a CVE DT's mirror doesn't have at all, the analyzer creates the record with KernelScan's score; only the "NVD stub without a score" case shows blank.) - No verdicts / no triage. DT v5 analyzers produce findings only — analysis
states (
not_affected+ justification) come exclusively from VEX imports or CEL policies, never from an analyzer's VDR. So at stage 1 nothing is suppressed: every matched CVE is an active finding.
So stage 1 gives you presence and coverage (which kernel CVEs are config-reachable, incl. ones NVD is behind on) — not scores or triage.
Upload the same product VEX to DT (PUT /api/v1/vex) from a small in-network
job — analyzers receive only the BOM, not the DT project or credentials, so they
cannot push it themselves. This applies KernelScan's config-aware not_affected
- justification onto the findings from stage 1. KernelScan provides a reference
push_vex.py.
Together: a customer's kernel findings are fully triaged — the config-reachable ones actionable, the config-gated ones auto-marked Not Affected with KernelScan's config-mapping justification.
The apiserver launches with -cp 'dependency-track-apiserver.jar:lib/*', so a
JAR mounted into lib/ is discovered via ServiceLoader at startup.
# docker-compose.yml — apiserver service
volumes:
- ./plugins/kernelscan-dt-plugin.jar:/opt/owasp/dependency-track/lib/kernelscan-dt-plugin.jar:roRestart the apiserver; the analyzer appears at
GET /api/v2/extension-points/vuln-analyzer/extensions and in Administration →
Analyzers.
vuln-analyzer/kernelscan config (admin UI form, rendered from the config schema):
| Field | Default | Notes |
|---|---|---|
enabled |
false |
turn the analyzer on |
apiBaseUrl |
https://kernelscan.io |
KernelScan API base |
apiKey |
— | account API key (ks_live_…) — like a Snyk token |
cacheMinutes |
15 |
in-memory TTL for a resolved product VEX |
The customer's kernels must exist as products in their KernelScan account —
that's where the .config and its verdicts live.
For each kernel component the analyzer resolves the KernelScan product to use:
-
By kernel version (default) — if exactly one product has that kernel version, it's used. No extra setup for the common one-product-per-version case.
-
By explicit binding — if the kernel component carries a
kernelscan:productproperty, the analyzer uses that product (matched by name or id). This is required when two products share a kernel version but have different.config(e.g. a left/right appliance pair): version alone can't tell them apart, so pin each one:// kernel component in the SBOM { "type": "operating-system", "name": "linux", "version": "6.6.69", "cpe": "cpe:2.3:o:linux:linux_kernel:6.6.69:*:*:*:*:*:*:*", "properties": [ { "name": "kernelscan:product", "value": "Router-A [6.6.69]" } ] }
If several products share a version and the component has no binding, the analyzer skips that kernel and logs a warning — it will not guess and risk applying the wrong config's reachability verdicts.
The DT plugin API is not published to Maven Central, so the compile classpath is extracted from the pinned apiserver image. Only Docker is required.
make libs # extract compile classpath from ghcr.io/dependencytrack/apiserver:5.0.2
make build # javac + jar (in eclipse-temurin:25-jdk) -> build/kernelscan-dt-plugin.jar
make test # pure-logic unit tests (no network)
make deploy # scp to the m2 test instance (dev only)VERSION + DT_VERSION are stamped into the JAR manifest. A plugin binary
targets one DT release (X-DependencyTrack-Version); rebuild per DT minor.
- Dependency-Track 5.0.x (plugin API
@since 5.0.0, CycloneDX proto v1_7). - Java 25 (DT's baseline).
src/main/java/io/kernelscan/dtplugin/
KernelScanPlugin.java ServiceLoader entry (registers the analyzer)
analyzer/
KernelScanVulnAnalyzerFactory.java factory + admin-UI config
KernelScanVulnAnalyzer.java product resolution + config-aware findings
KernelScanVulnAnalyzerConfigV1.java typed config
VexModelConverter.java product-VEX JSON -> CycloneDX proto vuln
src/main/resources/
META-INF/services/…Plugin ServiceLoader registration
…/kernel-scan-vuln-analyzer-config-v1.schema.json config form schema
Apache License 2.0 — see LICENSE and NOTICE.
Copyright © 2026 KernelScan UG (haftungsbeschränkt). The KernelScan service
itself is commercial; this plugin is the open-source client.

