diff --git a/pom.xml b/pom.xml
index e9230a37d6..adeff877d6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,8 @@
11.1.2.jre11-preview
8.0.29
42.4.0
+
+ 6.0.0
application
false
@@ -269,6 +271,12 @@
commons-compress
1.21
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ ${lib.jackson-databind.version}
+
junit
@@ -330,6 +338,31 @@
+
+ org.openapitools
+ openapi-generator-maven-plugin
+ ${plugin.openapi-generator.version}
+
+
+
+ generate
+
+
+ ${project.basedir}/src/main/resources/osv_service_v1.swagger.json
+ java
+ false
+ false
+
+ src/gen/java/main
+ apache-httpclient
+ com.google.osv.api
+ com.google.osv.model
+ jackson
+
+
+
+
+
maven-antrun-plugin
3.1.0
diff --git a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java
index e41c2c493a..219904cb74 100644
--- a/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java
+++ b/src/main/java/org/dependencytrack/event/EventSubsystemInitializer.java
@@ -43,6 +43,7 @@
import org.dependencytrack.tasks.repositories.RepositoryMetaAnalyzerTask;
import org.dependencytrack.tasks.scanners.InternalAnalysisTask;
import org.dependencytrack.tasks.scanners.OssIndexAnalysisTask;
+import org.dependencytrack.tasks.scanners.OsvAnalysisTask;
import org.dependencytrack.tasks.scanners.VulnDbAnalysisTask;
import javax.servlet.ServletContextEvent;
@@ -80,6 +81,7 @@ public void contextInitialized(final ServletContextEvent event) {
EVENT_SERVICE.subscribe(InternalAnalysisEvent.class, InternalAnalysisTask.class);
EVENT_SERVICE.subscribe(OssIndexAnalysisEvent.class, OssIndexAnalysisTask.class);
EVENT_SERVICE.subscribe(GitHubAdvisoryMirrorEvent.class, GitHubAdvisoryMirrorTask.class);
+ EVENT_SERVICE.subscribe(OsvAnalysisEvent.class, OsvAnalysisTask.class);
EVENT_SERVICE.subscribe(VulnDbSyncEvent.class, VulnDbSyncTask.class);
EVENT_SERVICE.subscribe(VulnDbAnalysisEvent.class, VulnDbAnalysisTask.class);
EVENT_SERVICE.subscribe(VulnerabilityAnalysisEvent.class, VulnerabilityAnalysisTask.class);
@@ -114,6 +116,7 @@ public void contextDestroyed(final ServletContextEvent event) {
EVENT_SERVICE.unsubscribe(InternalAnalysisTask.class);
EVENT_SERVICE.unsubscribe(OssIndexAnalysisTask.class);
EVENT_SERVICE.unsubscribe(GitHubAdvisoryMirrorTask.class);
+ EVENT_SERVICE.unsubscribe(OsvAnalysisTask.class);
EVENT_SERVICE.unsubscribe(VulnDbSyncTask.class);
EVENT_SERVICE.unsubscribe(VulnDbAnalysisTask.class);
EVENT_SERVICE.unsubscribe(VulnerabilityAnalysisTask.class);
diff --git a/src/main/java/org/dependencytrack/event/OsvAnalysisEvent.java b/src/main/java/org/dependencytrack/event/OsvAnalysisEvent.java
new file mode 100644
index 0000000000..9acb299d36
--- /dev/null
+++ b/src/main/java/org/dependencytrack/event/OsvAnalysisEvent.java
@@ -0,0 +1,43 @@
+/*
+ * 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) Steve Springett. All Rights Reserved.
+ */
+package org.dependencytrack.event;
+
+import org.dependencytrack.model.Component;
+
+import java.util.List;
+
+/**
+ * Defines an event used to start an analysis via Sonatype OSS Index REST API.
+ *
+ * @author Steve Springett
+ * @since 3.2.0
+ */
+public class OsvAnalysisEvent extends VulnerabilityAnalysisEvent {
+
+ public OsvAnalysisEvent() { }
+
+ public OsvAnalysisEvent(final Component component) {
+ super(component);
+ }
+
+ public OsvAnalysisEvent(final List components) {
+ super(components);
+ }
+
+}
diff --git a/src/main/java/org/dependencytrack/model/Vulnerability.java b/src/main/java/org/dependencytrack/model/Vulnerability.java
index 486332a9c0..f17c40d080 100644
--- a/src/main/java/org/dependencytrack/model/Vulnerability.java
+++ b/src/main/java/org/dependencytrack/model/Vulnerability.java
@@ -95,7 +95,8 @@ public enum Source {
VULNDB, // VulnDB from Risk Based Security
OSSINDEX, // Sonatype OSS Index
RETIREJS, // Retire.js
- INTERNAL // Internally-managed (and manually entered) vulnerability
+ INTERNAL, // Internally-managed (and manually entered) vulnerability
+ OSV
}
@PrimaryKey
diff --git a/src/main/java/org/dependencytrack/tasks/VulnerabilityAnalysisTask.java b/src/main/java/org/dependencytrack/tasks/VulnerabilityAnalysisTask.java
index 4d1fb20d66..d260b95fff 100644
--- a/src/main/java/org/dependencytrack/tasks/VulnerabilityAnalysisTask.java
+++ b/src/main/java/org/dependencytrack/tasks/VulnerabilityAnalysisTask.java
@@ -25,6 +25,7 @@
import org.dependencytrack.event.InternalAnalysisEvent;
import org.dependencytrack.event.MetricsUpdateEvent;
import org.dependencytrack.event.OssIndexAnalysisEvent;
+import org.dependencytrack.event.OsvAnalysisEvent;
import org.dependencytrack.event.PortfolioVulnerabilityAnalysisEvent;
import org.dependencytrack.event.VulnDbAnalysisEvent;
import org.dependencytrack.event.VulnerabilityAnalysisEvent;
@@ -35,6 +36,7 @@
import org.dependencytrack.tasks.scanners.CacheableScanTask;
import org.dependencytrack.tasks.scanners.InternalAnalysisTask;
import org.dependencytrack.tasks.scanners.OssIndexAnalysisTask;
+import org.dependencytrack.tasks.scanners.OsvAnalysisTask;
import org.dependencytrack.tasks.scanners.ScanTask;
import org.dependencytrack.tasks.scanners.VulnDbAnalysisTask;
@@ -49,6 +51,7 @@ public class VulnerabilityAnalysisTask implements Subscriber {
private final List internalCandidates = new ArrayList<>();
private final List ossIndexCandidates = new ArrayList<>();
+ private final List osvCandidates = new ArrayList<>();
private final List vulnDbCandidates = new ArrayList<>();
/**
@@ -103,10 +106,12 @@ private void analyzeComponents(final QueryManager qm, final List comp
*/
final InternalAnalysisTask internalAnalysisTask = new InternalAnalysisTask();
final OssIndexAnalysisTask ossIndexAnalysisTask = new OssIndexAnalysisTask();
+ final var osvAnalysisTask = new OsvAnalysisTask();
final VulnDbAnalysisTask vulnDbAnalysisTask = new VulnDbAnalysisTask();
for (final Component component : components) {
inspectComponentReadiness(component, internalAnalysisTask, internalCandidates);
inspectComponentReadiness(component, ossIndexAnalysisTask, ossIndexCandidates);
+ inspectComponentReadiness(component, osvAnalysisTask, osvCandidates);
inspectComponentReadiness(component, vulnDbAnalysisTask, vulnDbCandidates);
}
@@ -117,6 +122,7 @@ private void analyzeComponents(final QueryManager qm, final List comp
// from interrupting the successful execution of all analyzers.
performAnalysis(internalAnalysisTask, new InternalAnalysisEvent(internalCandidates));
performAnalysis(ossIndexAnalysisTask, new OssIndexAnalysisEvent(ossIndexCandidates));
+ performAnalysis(osvAnalysisTask, new OsvAnalysisEvent(osvCandidates));
performAnalysis(vulnDbAnalysisTask, new VulnDbAnalysisEvent(vulnDbCandidates));
}
diff --git a/src/main/java/org/dependencytrack/tasks/scanners/AnalyzerIdentity.java b/src/main/java/org/dependencytrack/tasks/scanners/AnalyzerIdentity.java
index c90ad58843..7c23a26a9e 100644
--- a/src/main/java/org/dependencytrack/tasks/scanners/AnalyzerIdentity.java
+++ b/src/main/java/org/dependencytrack/tasks/scanners/AnalyzerIdentity.java
@@ -28,5 +28,6 @@ public enum AnalyzerIdentity {
OSSINDEX_ANALYZER,
NPM_AUDIT_ANALYZER,
VULNDB_ANALYZER,
+ OSV_ANALYZER,
NONE
}
diff --git a/src/main/java/org/dependencytrack/tasks/scanners/OssIndexAnalysisTask.java b/src/main/java/org/dependencytrack/tasks/scanners/OssIndexAnalysisTask.java
index 61ffa909e9..e074393db6 100644
--- a/src/main/java/org/dependencytrack/tasks/scanners/OssIndexAnalysisTask.java
+++ b/src/main/java/org/dependencytrack/tasks/scanners/OssIndexAnalysisTask.java
@@ -80,7 +80,7 @@ public AnalyzerIdentity getAnalyzerIdentity() {
*/
public void inform(final Event e) {
if (e instanceof OssIndexAnalysisEvent) {
- if (!super.isEnabled(ConfigPropertyConstants.SCANNER_OSSINDEX_ENABLED)) {
+ if (super.isEnabled(ConfigPropertyConstants.SCANNER_OSSINDEX_ENABLED)) {
return;
}
try (QueryManager qm = new QueryManager()) {
diff --git a/src/main/java/org/dependencytrack/tasks/scanners/OsvAnalysisTask.java b/src/main/java/org/dependencytrack/tasks/scanners/OsvAnalysisTask.java
new file mode 100644
index 0000000000..b39868cc22
--- /dev/null
+++ b/src/main/java/org/dependencytrack/tasks/scanners/OsvAnalysisTask.java
@@ -0,0 +1,195 @@
+package org.dependencytrack.tasks.scanners;
+
+import alpine.common.logging.Logger;
+import alpine.event.framework.Event;
+import alpine.event.framework.Subscriber;
+import com.google.osv.ApiClient;
+import com.google.osv.ApiException;
+import com.google.osv.api.ApiApi;
+import com.google.osv.model.OsvVulnerability;
+import com.google.osv.model.V1BatchQuery;
+import com.google.osv.model.V1BatchVulnerabilityList;
+import com.google.osv.model.V1Query;
+import com.google.osv.model.V1QueryPackage;
+import com.google.osv.model.V1VulnerabilityList;
+import org.apache.commons.lang3.StringUtils;
+import org.dependencytrack.common.HttpClientPool;
+import org.dependencytrack.event.OsvAnalysisEvent;
+import org.dependencytrack.model.Component;
+import org.dependencytrack.model.Vulnerability;
+import org.dependencytrack.persistence.QueryManager;
+import org.dependencytrack.util.NotificationUtil;
+
+import java.util.List;
+import java.util.Optional;
+
+public class OsvAnalysisTask extends BaseComponentAnalyzerTask implements Subscriber {
+
+ private static final Logger LOGGER = Logger.getLogger(OsvAnalysisTask.class);
+
+ @Override
+ public void inform(final Event e) {
+ if (!(e instanceof OsvAnalysisEvent)) {
+ return;
+ }
+
+ final var analysisEvent = (OsvAnalysisEvent) e;
+ if (!analysisEvent.getComponents().isEmpty()) {
+ analyze(analysisEvent.getComponents());
+ }
+ }
+
+ @Override
+ public AnalyzerIdentity getAnalyzerIdentity() {
+ return AnalyzerIdentity.OSV_ANALYZER;
+ }
+
+ @Override
+ public void analyze(final List components) {
+ final var osvApiClient = new ApiClient(HttpClientPool.getClient());
+ final var osvApi = new ApiApi(osvApiClient);
+
+ // TODO: Limit batch size to <= 1000
+ final var batchQuery = new V1BatchQuery();
+ for (final Component component : components) {
+ batchQuery.addQueriesItem(new V1Query()
+ ._package(new V1QueryPackage().purl(component.getPurl().toString())));
+ }
+
+ final List vulnerabilityLists;
+ try {
+ final V1BatchVulnerabilityList queryResult = osvApi.oSVQueryAffectedBatch(batchQuery);
+
+ vulnerabilityLists = queryResult.getResults();
+ if (vulnerabilityLists == null || vulnerabilityLists.isEmpty()) {
+ LOGGER.warn("OSV returned no results");
+ return;
+ }
+ } catch (ApiException ex) {
+ LOGGER.error("Performing batch query failed", ex);
+ return;
+ }
+
+ // OSV will return a list of vulnerabilities for each query in the batch we submitted.
+ // If no vulnerabilities are found, the respective list will be empty.
+ // Correlation between input queries / components and results is based on their order.
+ if (vulnerabilityLists.size() != components.size()) {
+ LOGGER.warn("OSV returned " + vulnerabilityLists.size() + " results, but we expected " + components.size());
+ return;
+ }
+
+ for (int i = 0; i < components.size(); i++) {
+ final V1VulnerabilityList vulnerabilityList = vulnerabilityLists.get(i);
+ if (vulnerabilityList.getVulns() == null || vulnerabilityList.getVulns().isEmpty()) {
+ LOGGER.info("No vulnerabilities found for component " + components.get(i).getPurl());
+ continue;
+ }
+
+ try (final var qm = new QueryManager()) {
+ final var component = qm.getObjectById(Component.class, components.get(i).getId());
+
+ for (OsvVulnerability osvVuln : vulnerabilityList.getVulns()) {
+ // Do we know this vulnerability already?
+ Vulnerability vuln = resolveVulnerability(qm, osvVuln.getId());
+
+ if (vuln == null) {
+ try {
+ // Vulnerabilities in batch query responses only contain their respective ID.
+ // If we need more fields, we have to explicitly request them.
+ // TODO: Cache these responses?
+ osvVuln = osvApi.oSVGetVulnById(osvVuln.getId());
+ } catch (ApiException e) {
+ LOGGER.error("Requesting details for vulnerability " + osvVuln.getId() + " failed", e);
+ continue;
+ }
+
+ // Primary ID of the vulnerability is unknown, but maybe we know one of its aliases?
+ if (osvVuln.getAliases() != null && !osvVuln.getAliases().isEmpty()) {
+ for (final String alias : osvVuln.getAliases()) {
+ vuln = resolveVulnerability(qm, alias);
+ if (vuln != null) {
+ break;
+ }
+ }
+ }
+
+ // Vulnerability is not known to us yet, so we have to create it.
+ if (vuln == null) {
+ vuln = new Vulnerability();
+
+ // Similar to how it's done in OssIndexAnalysisTask, we prefer using the ID
+ // of the authoritative vulnerability source. We also prioritize CVE/NVD over GHSA.
+ // Vulnerability data will ultimately be overridden by those sources once
+ // Dependency-Track starts mirroring them.
+ final Optional optCve = resolveCve(osvVuln);
+ final Optional optGhsaId = resolveGhsaId(osvVuln);
+ if (optCve.isPresent()) {
+ vuln.setSource(Vulnerability.Source.NVD);
+ vuln.setVulnId(optCve.get());
+ } else if (optGhsaId.isPresent()) {
+ vuln.setSource(Vulnerability.Source.GITHUB);
+ vuln.setVulnId(optGhsaId.get());
+ } else {
+ vuln.setSource(Vulnerability.Source.OSV);
+ vuln.setVulnId(osvVuln.getId());
+ }
+
+ vuln.setTitle(StringUtils.truncate(osvVuln.getSummary(), 255));
+ vuln.setDescription(osvVuln.getDetails());
+
+ // TODO: Parse more details
+
+ vuln = qm.createVulnerability(vuln, false);
+ }
+ }
+
+ LOGGER.info(osvVuln.getId() + " matched to " + vuln.getVulnId() + " (" + vuln.getSource() + ")");
+ NotificationUtil.analyzeNotificationCriteria(qm, vuln, component);
+ qm.addVulnerability(vuln, component, this.getAnalyzerIdentity(), osvVuln.getId(), "https://osv.dev/vulnerability/" + osvVuln.getId());
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isCapable(final Component component) {
+ return component.getPurl() != null
+ && component.getPurl().getName() != null
+ && component.getPurl().getVersion() != null;
+ }
+
+ private Vulnerability resolveVulnerability(final QueryManager qm, final String vulnerabilityId) {
+ return qm.getVulnerabilityByVulnId(resolveVulnerabilitySource(vulnerabilityId), vulnerabilityId);
+ }
+
+ private Vulnerability.Source resolveVulnerabilitySource(final String vulnerabilityId) {
+ if (StringUtils.startsWith(vulnerabilityId, "CVE-")) {
+ return Vulnerability.Source.NVD;
+ } else if (StringUtils.startsWith(vulnerabilityId, "GHSA-")) {
+ return Vulnerability.Source.GITHUB;
+ }
+
+ return Vulnerability.Source.OSV;
+ }
+
+ private Optional resolveCve(final OsvVulnerability osvVuln) {
+ if (resolveVulnerabilitySource(osvVuln.getId()) == Vulnerability.Source.NVD) {
+ return Optional.ofNullable(osvVuln.getId());
+ }
+
+ return Optional.ofNullable(osvVuln.getAliases()).orElseGet(List::of).stream()
+ .filter(alias -> resolveVulnerabilitySource(alias) == Vulnerability.Source.NVD)
+ .findFirst();
+ }
+
+ private Optional resolveGhsaId(final OsvVulnerability osvVuln) {
+ if (resolveVulnerabilitySource(osvVuln.getId()) == Vulnerability.Source.GITHUB) {
+ return Optional.ofNullable(osvVuln.getId());
+ }
+
+ return Optional.ofNullable(osvVuln.getAliases()).orElseGet(List::of).stream()
+ .filter(alias -> resolveVulnerabilitySource(alias) == Vulnerability.Source.GITHUB)
+ .findFirst();
+ }
+
+}
diff --git a/src/main/resources/osv_service_v1.swagger.json b/src/main/resources/osv_service_v1.swagger.json
new file mode 100644
index 0000000000..0baf29e1bc
--- /dev/null
+++ b/src/main/resources/osv_service_v1.swagger.json
@@ -0,0 +1,514 @@
+{
+ "swagger": "2.0",
+ "info": {
+ "title": "OSV",
+ "version": "1.0",
+ "description": "# OSV API\n---\n\n### How does the API work?\n\nThe API currently accepts a git commit hash or a `(package, version number)` and returns the\nlist of vulnerabilities that are present for that version.\n\n# Getting Started\n---\n\n### Using the API\n\nBrowse the rest of the documentation for details on the\nAPI.\n\nFor some quick examples, run:\n\n```\ncurl -X POST -d '{\"commit\": \"6879efc2c1596d11a6a6ad296f80063b558d5e0f\"}' \\\n \"https://api.osv.dev/v1/query\"\n```\n\n```\ncurl -X POST -d \\\n '{\"version\": \"2.4.1\", \"package\": {\"name\": \"jinja2\", \"ecosystem\": \"PyPI\"}}' \\\n \"https://api.osv.dev/v1/query\"\n```\n"
+ },
+ "consumes": [
+ "application/json"
+ ],
+ "produces": [
+ "application/json"
+ ],
+ "paths": {
+ "/v1/query": {
+ "post": {
+ "summary": "Query vulnerabilities for a particular project at a given commit or\nversion.",
+ "operationId": "OSV_QueryAffected",
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "$ref": "#/definitions/v1VulnerabilityList"
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "$ref": "#/definitions/rpcStatus"
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/v1Query"
+ }
+ }
+ ],
+ "tags": [
+ "api"
+ ],
+ "x-code-samples": [
+ {
+ "lang": "Curl example",
+ "source": "curl -X POST -d \\\n '{\"commit\": \"6879efc2c1596d11a6a6ad296f80063b558d5e0f\"}' \\\n \"https://api.osv.dev/v1/query\"\n\ncurl -X POST -d \\\n '{\"package\": {\"name\": \"mruby\"}, \"version\": \"2.1.2rc\"}' \\\n \"https://api.osv.dev/v1/query\""
+ }
+ ]
+ }
+ },
+ "/v1/querybatch": {
+ "post": {
+ "summary": "Query vulnerabilities (batched) for given package versions and commits.\nThis currently allows a maximum of 1000 package versions to be included in a single query.",
+ "operationId": "OSV_QueryAffectedBatch",
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "$ref": "#/definitions/v1BatchVulnerabilityList"
+ }
+ },
+ "default": {
+ "description": "An unexpected error response.",
+ "schema": {
+ "$ref": "#/definitions/rpcStatus"
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/v1BatchQuery"
+ }
+ }
+ ],
+ "tags": [
+ "api"
+ ],
+ "x-code-samples": [
+ {
+ "lang": "Curl example",
+ "source": "cat <."
+ },
+ "purl": {
+ "type": "string",
+ "description": "Optional. The package URL for this package."
+ }
+ },
+ "description": "Package information and version."
+ },
+ "osvRange": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/osvRangeType",
+ "description": "Required. The type of version information."
+ },
+ "repo": {
+ "type": "string",
+ "description": "Required if type is GIT. The publicly accessible URL of the repo that can\nbe directly passed to clone commands."
+ },
+ "events": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/osvEvent"
+ },
+ "description": "Required. Version event information."
+ }
+ },
+ "description": "Affected ranges."
+ },
+ "osvRangeType": {
+ "type": "string",
+ "enum": [
+ "UNSPECIFIED",
+ "GIT",
+ "SEMVER",
+ "ECOSYSTEM"
+ ],
+ "default": "UNSPECIFIED",
+ "description": "Type of the version information."
+ },
+ "osvReference": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/osvReferenceType",
+ "description": "Required. The type of the reference."
+ },
+ "url": {
+ "type": "string",
+ "description": "Required. The URL."
+ }
+ },
+ "description": "Reference URL."
+ },
+ "osvReferenceType": {
+ "type": "string",
+ "enum": [
+ "NONE",
+ "WEB",
+ "ADVISORY",
+ "REPORT",
+ "FIX",
+ "PACKAGE",
+ "ARTICLE"
+ ],
+ "default": "NONE"
+ },
+ "osvSeverity": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "$ref": "#/definitions/osvSeverityType",
+ "description": "The type of this severity entry."
+ },
+ "score": {
+ "type": "string",
+ "description": "The quantitative score."
+ }
+ }
+ },
+ "osvSeverityType": {
+ "type": "string",
+ "enum": [
+ "UNSPECIFIED",
+ "CVSS_V3"
+ ],
+ "default": "UNSPECIFIED",
+ "description": "Type of the severity."
+ },
+ "osvVulnerability": {
+ "type": "object",
+ "properties": {
+ "schemaVersion": {
+ "type": "string",
+ "description": "The OSV schema version."
+ },
+ "id": {
+ "type": "string",
+ "description": "The `id` field is a unique identifier for the vulnerability entry. It is a\nstring of the format `-`, where `DB` names the database and\n`ENTRYID` is in the format used by the database. For example:\n\u201cOSV-2020-111\u201d, \u201cCVE-2021-3114\u201d, or \u201cGHSA-vp9c-fpxx-744v\u201d."
+ },
+ "published": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The RFC3339 timestamp indicating when this entry was published."
+ },
+ "modified": {
+ "type": "string",
+ "format": "date-time",
+ "description": "The RFC3339 timestamp indicating when this entry was last modified."
+ },
+ "withdrawn": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Optional. The RFC3339 timestamp indicating when this entry is considered to\nbe withdrawn."
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Optional. IDs for the same vulnerability in other databases."
+ },
+ "related": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Optional. List of IDs of closely related vulnerabilities, such as the same\nproblem in alternate ecosystems."
+ },
+ "package": {
+ "description": "Required. Package information.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/osvPackage"
+ }
+ ]
+ },
+ "summary": {
+ "type": "string",
+ "description": "Required. One line human readable summary for the vulnerability. It is\nrecommended to keep this under 120 characters."
+ },
+ "details": {
+ "type": "string",
+ "description": "Required. Any additional human readable details for the vulnerability."
+ },
+ "affected": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/osvAffected"
+ },
+ "description": "Required. Affected commit ranges and versions."
+ },
+ "references": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/osvReference"
+ },
+ "description": "Optional. URLs to more information/advisories (including the\nscheme e.g \"https://\")."
+ },
+ "severity": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/osvSeverity"
+ },
+ "description": "Optional. Severity of the vulnerability."
+ },
+ "credits": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/osvCredit"
+ },
+ "description": "Optional. Credits for the vulnerability."
+ },
+ "database_specific": {
+ "type": "object",
+ "description": "Optional. JSON object holding additional information about the\nvulnerability as defined by the database for which the record applies."
+ }
+ },
+ "description": "A vulnerability entry.\nThe protobuf representation is *NOT* stable and only used for implementing\nthe JSON based API."
+ },
+ "protobufAny": {
+ "type": "object",
+ "properties": {
+ "typeUrl": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string",
+ "format": "byte"
+ }
+ }
+ },
+ "protobufNullValue": {
+ "type": "string",
+ "enum": [
+ "NULL_VALUE"
+ ],
+ "default": "NULL_VALUE",
+ "description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\n The JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
+ },
+ "rpcStatus": {
+ "type": "object",
+ "properties": {
+ "code": {
+ "type": "integer",
+ "format": "int32"
+ },
+ "message": {
+ "type": "string"
+ },
+ "details": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/protobufAny"
+ }
+ }
+ }
+ },
+ "v1BatchQuery": {
+ "type": "object",
+ "properties": {
+ "queries": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/v1Query"
+ },
+ "description": "The queries that form this batch query."
+ }
+ },
+ "description": "Batch query format."
+ },
+ "v1BatchVulnerabilityList": {
+ "type": "object",
+ "properties": {
+ "results": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/v1VulnerabilityList"
+ }
+ }
+ },
+ "description": "Batched lists of Vulnerability entries."
+ },
+ "v1Query": {
+ "type": "object",
+ "properties": {
+ "commit": {
+ "type": "string",
+ "description": "The commit hash to query for. If specified, `version` should not be set."
+ },
+ "version": {
+ "type": "string",
+ "description": "The version string to query for. A fuzzy match is done against upstream\nversions. If specified, `commit` should not be set."
+ },
+ "package": {
+ "description": "The package to query against. When a `commit` hash is given, this is\noptional.",
+ "allOf": [
+ {
+ "$ref": "#/definitions/osvPackage"
+ }
+ ]
+ }
+ },
+ "description": "Query format."
+ },
+ "v1VulnerabilityList": {
+ "type": "object",
+ "properties": {
+ "vulns": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/osvVulnerability"
+ }
+ }
+ },
+ "description": "A list of Vulnerability entries."
+ }
+ },
+ "host": "api.osv.dev",
+ "schemes": ["https"],
+ "tags": [
+ {
+ "name": "api",
+ "x-displayName": "API",
+ "description": "The API has 3 methods:"
+ },
+ {
+ "name": "vulnerability_schema",
+ "x-displayName": "Vulnerability schema",
+ "description": "Please see the [OpenSSF Open Source Vulnerability spec](https://ossf.github.io/osv-schema/)."
+ }
+ ],
+ "x-tagGroups": [
+ {
+ "name": "API",
+ "tags": [
+ "api"
+ ]
+ },
+ {
+ "name": "Schema",
+ "tags": [
+ "vulnerability_schema"
+ ]
+ }
+ ]
+}
\ No newline at end of file