Skip to content
Open
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
5 changes: 5 additions & 0 deletions apiserver/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@
<artifactId>vuln-data-source-github</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.dependencytrack</groupId>
<artifactId>vuln-data-source-jvn</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.dependencytrack</groupId>
<artifactId>vuln-data-source-nvd</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ public enum Source {
INTERNAL, // Internally-managed (and manually entered) vulnerability
OSV, // Google OSV Advisories
SNYK, // Snyk Purl Vulnerability
JVN, // Japan Vulnerability Notes (JVN iPedia)
UNKNOWN; // Unknown vulnerability sources

public static boolean isKnownSource(String source) {
Expand Down
120 changes: 120 additions & 0 deletions docs/adr/031-jvn-vulnerability-data-source.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
| Status | Date | Author(s) |
|:---------|:-----------|:---------------------------------------------------------------|
| Proposed | 2026-07-08 | [@mitsukado-shinagawa](https://github.com/mitsukado-shinagawa) |

## Context

Dependency-Track mirrors vulnerability intelligence from the NVD, GitHub Advisories, and OSV
(see [ADR-010](./010-vulnerability-datasource-extension-point.md)). None of these cover the
[Japan Vulnerability Notes (JVN) / JVN iPedia](https://jvndb.jvn.jp/) database operated by
JPCERT/CC and IPA.

Organizations operating in Japan run software — both domestic-vendor products (e.g. Hitachi
Cosminexus) and internationally-distributed products (e.g. Cisco) — for which JVN publishes
vulnerability information, sometimes ahead of, or in addition to, the NVD. Users have asked
whether JVN data can be mirrored into Dependency-Track the same way the NVD is, so that
CPE-based matching (`InternalVulnAnalyzer`) can flag affected components.

Before committing to an implementation, a PoC measured the machine-readability of the JVN data
over the **50 most recently published JVNDB entries** (2026-07-07):

| Metric | Result | Interpretation |
|:------------------------------------|:-------------|:--------------------------------------------------|
| Entries carrying a CPE | 100% (50/50) | Matching substrate is essentially always present |
| CPE encoding | 100% CPE 2.2 | Must be converted to CPE 2.3 for storage |
| CPE + resolvable version | 94% (47/50) | Upper bound on precise (range) CPE matching |
| Entries carrying a CVE ID | 96% (48/50) | Heavy overlap with the NVD |
| JVN-only (no CVE ID) | 4% (2/50) | The genuinely additive coverage |

Three findings dominate the design:

1. **Versions are not in the CPE.** Every sampled CPE was product-level
(`cpe:/a:suse:rancher_fleet`), with the affected version carried in a *separate*,
free-text Japanese field (`VersionNumber`, e.g. `"0.15.0 以上 0.15.2 未満"` or `"25.3.3.1"`).
Storing the CPE alone yields all-versions (product-level) matches — false positives.
Precise matching requires **parsing the Japanese version expressions**
(`以上`→`>=`, `以下`→`<=`, `未満`→`<`, `より前`→`<`, exact → `=`) into `vers` ranges,
which `BovModelConverter` already turns into `versionStart/End{Including,Excluding}`.

2. **96% of entries duplicate the NVD.** For CVE-carrying entries the NVD is the authoritative
source with better-structured version ranges. Per
[`VulnerabilityUpdatePolicy`](../../apiserver/src/main/java/org/dependencytrack/vulnanalysis/VulnerabilityUpdatePolicy.java),
a non-authoritative data source cannot overwrite a vulnerability owned by an enabled
authoritative source, so a JVN source cannot "enrich" an NVD-owned CVE through the standard
mirror path. The unique, durable value of JVN is the **~4% JVN-only advisories**
(domestic-vendor products, `JVN#`/`JVNVU` without a CVE) plus Japanese-language descriptions
and publication timeliness — **not** blanket coverage of already-CVE'd products.

3. **The MyJVN query API cannot backfill history.** MyJVN's `getVulnOverviewList` caps at the
~747 most-recently-published advisories regardless of the requested date range (measured
2026-07-08: `datePublicStart=2026-06-01`→731, `=2020-01-01`→747, `=2010-01-01`→747), and
bounded historical windows return `totalRes=0`. JVN however publishes **yearly detail feeds**
(`.../ja/feed/detail/jvndb_detail_YYYY.rdf`, full history 1988–present) alongside a
`checksum.txt` manifest with a `sha256` per feed file. The feeds use the same VULDEF schema
as the API's detail responses, and are the acquisition channel used by
[vulsio/go-cve-dictionary](https://github.com/vulsio/go-cve-dictionary) (Vuls).

## Decision

Add a `jvn` module under `vuln-data-source/`, implementing the `VulnDataSource` extension point
of [ADR-010](./010-vulnerability-datasource-extension-point.md), disabled by default. It
exchanges data as CycloneDX BOVs, reusing the existing mirror pipeline
(`MirrorVulnDataSourceWorkflow` → `BovModelConverter` → `VulnerableSoftware`), so no changes to
ingestion, storage, or matching are required.

Scope and behavior:

* **Fetch.** Download the yearly detail feeds `jvndb_detail_YYYY.rdf` for
`startYear..currentYear`, skipping years whose `sha256` in `checksum.txt` is unchanged since
the last successful run (feed-digest checkpoint in the plugin KV store, mirroring the NVD
data source's checkpointing). A failed year is not checkpointed and is retried on the next run.
* **CPE normalization.** Convert CPE 2.2 URIs to CPE 2.3 formatted strings before emitting them
on BOV `affects` components.
* **Version parsing.** Translate the `VersionNumber` free-text field into `vers` ranges. When no
version expression can be parsed for a product, fall back to an all-versions (wildcard) range —
the product *is* declared affected by JVN, and this matches how product-level CPEs from the NVD
behave — rather than silently dropping the product.
* **Source identity.** Emit **every** advisory under a new `JVN` source with its
`JVNDB-YYYY-NNNNNN` identifier — no CVE→NVD routing and no NVD duplicate check. JVN is stored
as-is, like the NVD source; JVN and NVD records for the same CVE coexist.

*Considered alternative:* emit CVE-carrying advisories under the CVE / NVD identity and reserve
the `JVN` source for CVE-less entries, avoiding duplicate records. Rejected because the NVD
remains authoritative for those CVEs anyway (`VulnerabilityUpdatePolicy` prevents JVN from
updating them once NVD publishes), so the JVN detail — Japanese descriptions, JVN's own version
ranges, JVN references — would be unreachable; and because "the JVN record you see is exactly
what JVN published" is easier to reason about operationally. The cost is accepted, documented
duplication (see Consequences).
* **Configuration.** `enabled` (default `false`), `feedBaseUrl`
(default `https://jvndb.jvn.jp/ja/feed`), `startYear` (default `1998`, the beginning of
JVN iPedia data).
* **`Vulnerability.Source` registration.** A data source that emits its own source identity must
be registered in the `org.dependencytrack.model.Vulnerability.Source` enum: the mirror derives
the source name from the data source name (`VulnDataSourceMirrorService` upper-cases it, `jvn`
→ `JVN`) and validates it via `Source.ofName(...)` in `MirrorVulnDataSourceActivity`; an
unregistered name fails the whole mirror run. `BovModelConverter` likewise resolves the BOV's
source via `Source.ofName`. Adding `JVN` to the enum is therefore part of this change.

## Consequences

* Users can detect JVN advisories (notably for domestic-vendor products and JVN-only entries)
via the same CPE matching they already use, by registering the affected product as a component
with its CPE.
* No new persistence, ingestion, or matching code — apart from the one-line `Source` enum entry,
the change is isolated to one new module.
* **Full-history storage and intentional duplication.** Mirroring all years stores tens of
thousands of `JVN`-sourced vulnerabilities, and CVE-carrying advisories produce a `JVN` record
alongside the NVD's `CVE-*` record — a component may be flagged by both. This duplication is
an accepted trade-off for complete, self-contained JVN coverage. The first full backfill
downloads all yearly feeds; subsequent runs only re-fetch years whose `checksum.txt` `sha256`
changed.
* A **Japanese version-expression parser** becomes a maintained component; its coverage caps
matching precision, and un-parseable expressions degrade to all-versions matches. This parser
is the primary implementation and maintenance risk.
* CPE string alignment between JVN and the NVD/user-registered components is assumed; entries
with non-concrete CPEs (e.g. `cpe:/a:misc:multiple_vendors`) will not match and are dropped.
* For internationally-covered products (e.g. Cisco), this adds little over the existing NVD
source; the value is realized on domestic-vendor and JVN-only entries.
* Prior art exists for the *architecture* (fetch JVN feeds → local DB → CPE match) in
[vulsio/go-cve-dictionary](https://github.com/vulsio/go-cve-dictionary), but not within
Dependency-Track; this would be the first such integration.
123 changes: 123 additions & 0 deletions vuln-data-source/jvn/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Dependency-Track.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
~ SPDX-License-Identifier: Apache-2.0
~ Copyright (c) OWASP Foundation. All Rights Reserved.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.dependencytrack</groupId>
<artifactId>vuln-data-source-parent</artifactId>
<version>5.1.0-SNAPSHOT</version>
</parent>

<artifactId>vuln-data-source-jvn</artifactId>
<packaging>jar</packaging>

<name>Vulnerability Data Source :: JVN</name>

<properties>
<project.parentBaseDir>${project.basedir}/../..</project.parentBaseDir>
</properties>

<dependencies>
<dependency>
<groupId>org.dependencytrack</groupId>
<artifactId>vuln-data-source-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.dependencytrack</groupId>
<artifactId>plugin-testing</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>us.springett</groupId>
<artifactId>cpe-parser</artifactId>
</dependency>

<!-- Required at compile time by the jsonschema2pojo-generated config class. -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>

<!-- Parses the JVN feed checksum.txt manifest; provided at runtime by Dependency-Track. -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>

<dependency>
<groupId>io.github.nscuro</groupId>
<artifactId>versatile-core</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock-standalone</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.jsonschema2pojo</groupId>
<artifactId>jsonschema2pojo-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<sourceDirectory>${basedir}/src/main/resources/org/dependencytrack/vulndatasource/jvn</sourceDirectory>
<targetPackage>org.dependencytrack.vulndatasource.jvn</targetPackage>
<generateBuilders>true</generateBuilders>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* This file is part of Dependency-Track.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) OWASP Foundation. All Rights Reserved.
*/
package org.dependencytrack.vulndatasource.jvn;

import org.jspecify.annotations.Nullable;

import java.time.Instant;
import java.util.List;

/**
* A parsed JVN advisory, as returned by the MyJVN {@code getVulnDetailInfo} API.
*
* @since 5.1.0
*/
record JvnAdvisory(
String jvnDbId,
@Nullable String title,
@Nullable String overview,
@Nullable String detail,
@Nullable String recommendation,
List<String> cveIds,
List<Integer> cweIds,
List<Cvss> cvssList,
List<AffectedProduct> affected,
List<String> referenceUrls,
@Nullable Instant datePublic,
@Nullable Instant dateLastUpdated) {

/** An affected product: a single (product-level) CPE plus its Japanese version expressions. */
record AffectedProduct(
@Nullable String vendor,
@Nullable String productName,
String cpe22,
List<String> versionTexts) {
}

/** A CVSS rating attached to the advisory. */
record Cvss(
@Nullable String version,
@Nullable String severity,
@Nullable Double baseScore,
@Nullable String vector) {
}
}
Loading