From 6fabd431b9d4fcb7727ad9a7becd4f2661b87c17 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 25 Aug 2026 11:45:00 +0100 Subject: [PATCH 1/6] Support multiple configurable OSV data sources Signed-off-by: Sahiba Mittal --- ...V202608241029__osv_multi_source_config.sql | 19 +++ .../osv/OsvCompositeVulnDataSource.java | 90 ++++++++++++ .../vulndatasource/osv/OsvVulnDataSource.java | 11 +- .../osv/OsvVulnDataSourceFactory.java | 71 ++++++---- .../vulndatasource/osv/WatermarkManager.java | 3 +- .../vulndatasource/osv/WatermarkStore.java | 10 +- ...osv-vuln-data-source-config-v1.schema.json | 90 +++++++----- .../osv/OsvCompositeVulnDataSourceTest.java | 79 +++++++++++ .../osv/OsvVulnDataSourceFactoryTest.java | 131 +++++++++++------- .../osv/OsvVulnDataSourceTest.java | 13 ++ .../osv/WatermarkManagerTest.java | 14 +- 11 files changed, 405 insertions(+), 126 deletions(-) create mode 100644 migration/src/main/resources/org/dependencytrack/migration/V202608241029__osv_multi_source_config.sql create mode 100644 vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java create mode 100644 vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java diff --git a/migration/src/main/resources/org/dependencytrack/migration/V202608241029__osv_multi_source_config.sql b/migration/src/main/resources/org/dependencytrack/migration/V202608241029__osv_multi_source_config.sql new file mode 100644 index 0000000000..0a1a16726b --- /dev/null +++ b/migration/src/main/resources/org/dependencytrack/migration/V202608241029__osv_multi_source_config.sql @@ -0,0 +1,19 @@ +-- Convert legacy OSV config to multi-source config +-- example: {"enabled": true, ...} -> {"sources": [{"name":"default", "enabled": true, ...}]}. +UPDATE "EXTENSION_RUNTIME_CONFIG" + SET "CONFIG" = jsonb_build_object( + 'sources', + jsonb_build_array( "CONFIG" || jsonb_build_object('name', 'default') ) + ), + "UPDATED_AT" = now() +WHERE "EXTENSION_POINT" = 'vuln-data-source' + AND "EXTENSION" = 'osv' + AND NOT ("CONFIG" ? 'sources'); + +-- Watermarks are namespaced by source name. Move existing watermarks to the default source. +UPDATE "EXTENSION_KV_STORE" + SET "KEY" = 'watermark/default/' || replace("KEY", 'watermark/', '') +WHERE "EXTENSION_POINT" = 'vuln-data-source' + AND "EXTENSION" = 'osv' + AND "KEY" LIKE 'watermark/%' + AND NOT "KEY" LIKE 'watermark/default/%'; \ No newline at end of file diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java new file mode 100644 index 0000000000..8e6ef6c443 --- /dev/null +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java @@ -0,0 +1,90 @@ +/* + * 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.osv; + +import org.cyclonedx.proto.v1_7.Bom; +import org.dependencytrack.vulndatasource.api.VulnDataSource; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.NoSuchElementException; + +import static java.util.Objects.requireNonNull; + +/** + * @since 5.0.0 + */ +final class OsvCompositeVulnDataSource implements VulnDataSource { + + private static final Logger LOGGER = LoggerFactory.getLogger(OsvCompositeVulnDataSource.class); + private final List dataSources; + private @Nullable OsvVulnDataSource currentDataSource; + private int currentDataSourceIndex; + + OsvCompositeVulnDataSource( + final List dataSources) { + this.dataSources = requireNonNull(dataSources, "dataSources must not be null"); + } + + + @Override + public boolean hasNext() { + while (currentDataSourceIndex < dataSources.size()) { + if (dataSources.get(currentDataSourceIndex).hasNext()) { + return true; + } + currentDataSourceIndex ++; + } + return false; + } + + @Override + public Bom next() { + if (currentDataSourceIndex >= dataSources.size()) { + throw new NoSuchElementException(); + } + currentDataSource = dataSources.get(currentDataSourceIndex); + return currentDataSource.next(); + } + + @Override + public void markProcessed(final Bom bom) { + if (currentDataSource == null) { + throw new IllegalStateException("No current data source to mark processed"); + } + currentDataSource.markProcessed(bom); + } + + @Override + public void close() { + for (final var dataSource : dataSources) { + try { + dataSource.close(); + } catch (final Exception e) { + LOGGER.warn("Failed to close data source: {}", dataSource, e); + } + } + } + + List getDataSources() { + return dataSources; + } +} \ No newline at end of file diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSource.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSource.java index b56d713af8..b0865f7a58 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSource.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSource.java @@ -62,6 +62,7 @@ final class OsvVulnDataSource implements VulnDataSource { private static final Logger LOGGER = LoggerFactory.getLogger(OsvVulnDataSource.class); private static final int MAX_INCREMENTAL_ADVISORY_DOWNLOADS = 250; + private final String dataSourceName; private final @Nullable WatermarkManager watermarkManager; private final ObjectMapper objectMapper; private final String dataUrl; @@ -78,12 +79,14 @@ final class OsvVulnDataSource implements VulnDataSource { private final boolean isAliasSyncEnabled; OsvVulnDataSource( + final String dataSourceName, final @Nullable WatermarkManager watermarkManager, final ObjectMapper objectMapper, final String dataUrl, final Collection ecosystems, final HttpClient httpClient, final boolean isAliasSyncEnabled) { + this.dataSourceName = requireNonNull(dataSourceName, "dataSourceName must not be null"); this.watermarkManager = watermarkManager; this.objectMapper = objectMapper; this.dataUrl = dataUrl; @@ -217,8 +220,9 @@ private void logCurrentEcosystemSummary() { } LOGGER.info( - "Finished ecosystem {}: processed {} advisories", + "Finished ecosystem {} of data source {}: processed {} advisories", currentEcosystem, + dataSourceName, currentEcosystemAdvisoriesProcessed); } @@ -227,7 +231,7 @@ private void openNextEcosystem() { currentEcosystemAdvisoriesProcessed = 0; currentAdvisorySource = openAdvisorySource(currentEcosystem); - LOGGER.info("Processing ecosystem {}", currentEcosystem); + LOGGER.info("Processing ecosystem {} of data source {}", currentEcosystem, dataSourceName); } private @Nullable OsvAdvisorySource openAdvisorySource(String ecosystem) { @@ -383,4 +387,7 @@ private Set getModifiedAdvisoryIds(String ecosystem, Instant watermark) return watermarkManager; } + String getDataSourceName() { + return dataSourceName; + } } \ No newline at end of file diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java index 0aece22cb0..316a972f0c 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java @@ -32,6 +32,9 @@ import java.net.URI; import java.net.http.HttpClient; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import static java.util.Objects.requireNonNull; @@ -41,6 +44,7 @@ */ final class OsvVulnDataSourceFactory implements VulnDataSourceFactory, RuntimeConfigurable { + private static final String DEFAULT_SOURCE_NAME = "default"; private @Nullable ConfigRegistry configRegistry; private @Nullable KeyValueStore kvStore; private @Nullable ObjectMapper objectMapper; @@ -72,22 +76,31 @@ public void init(ServiceRegistry serviceRegistry) { @Override public RuntimeConfigSpec runtimeConfigSpec() { - final var defaultConfig = new OsvVulnDataSourceConfigV1() + final var defaultSource = new OsvSourceConfigV1() + .withName(DEFAULT_SOURCE_NAME) .withIncrementalMirroringEnabled(true) .withEnabled(false) .withAliasSyncEnabled(false) .withDataUrl(URI.create("https://storage.googleapis.com/osv-vulnerabilities")) .withEcosystems(Set.of("Go", "Maven", "npm", "NuGet", "PyPI")); - return RuntimeConfigSpec.of(defaultConfig, config -> { - if (!config.isEnabled()) { - return; - } - if (config.getDataUrl() == null) { - throw new InvalidRuntimeConfigException("No data URL provided"); - } - if (config.getEcosystems() == null || config.getEcosystems().isEmpty()) { - throw new InvalidRuntimeConfigException("At least one ecosystem must be specified"); + final var defaultConfig = new OsvVulnDataSourceConfigV1() + .withSources(new LinkedHashSet<>(Set.of(defaultSource))); + + return RuntimeConfigSpec.of(defaultConfig, (OsvVulnDataSourceConfigV1 config) -> { + for (final var source : config.getSources()) { + if (source.getName() == null || source.getName().isBlank()) { + throw new InvalidRuntimeConfigException("No data source name provided"); + } + if (!source.isEnabled()) { + continue; + } + if (source.getDataUrl() == null) { + throw new InvalidRuntimeConfigException("No data URL provided"); + } + if (source.getEcosystems() == null || source.getEcosystems().isEmpty()) { + throw new InvalidRuntimeConfigException("At least one ecosystem must be specified"); + } } }); } @@ -95,7 +108,7 @@ public RuntimeConfigSpec runtimeConfigSpec() { @Override public boolean isDataSourceEnabled() { requireNonNull(configRegistry, "configRegistry must not be null"); - return configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class).isEnabled(); + return !enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)).isEmpty(); } @Override @@ -105,22 +118,32 @@ public VulnDataSource create() { requireNonNull(objectMapper, "objectMapper must not be null"); requireNonNull(httpClient, "httpClient must not be null"); - final var config = configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class); - if (!config.isEnabled()) { + final List sources = enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)); + if (sources.isEmpty()) { throw new IllegalStateException("Vulnerability data source is disabled and cannot be created"); } - final WatermarkManager watermarkManager = config.isIncrementalMirroringEnabled() - ? new WatermarkManager(config.getEcosystems(), kvStore) - : null; - - return new OsvVulnDataSource( - watermarkManager, - objectMapper, - config.getDataUrl().toString(), - config.getEcosystems(), - httpClient, - config.getAliasSyncEnabled()); + final var dataSources = new ArrayList(sources.size()); + for (final OsvSourceConfigV1 source : sources) { + final WatermarkManager watermarkManager = source.isIncrementalMirroringEnabled() + ? new WatermarkManager(source.getName(), source.getEcosystems(), kvStore) + : null; + + dataSources.add(new OsvVulnDataSource( + source.getName(), + watermarkManager, + objectMapper, + source.getDataUrl().toString(), + source.getEcosystems(), + httpClient, + source.getAliasSyncEnabled())); + } + return new OsvCompositeVulnDataSource(dataSources); } + private List enabledSources(final OsvVulnDataSourceConfigV1 config) { + return config.getSources().stream() + .filter(OsvSourceConfigV1::isEnabled) + .toList(); + } } \ No newline at end of file diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java index 27baf514e1..4a134915c7 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java @@ -39,9 +39,10 @@ final class WatermarkManager { private final Map committedRecordByEcosystem; WatermarkManager( + final String sourceName, final Collection ecosystems, final KeyValueStore kvStore) { - final var watermarkStore = new WatermarkStore(kvStore); + final var watermarkStore = new WatermarkStore(sourceName, kvStore); final Map recordByEcosystem = watermarkStore.getForEcosystems(ecosystems); diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java index 29a5df4725..2f7f0744b1 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java @@ -40,16 +40,18 @@ final class WatermarkStore { private static final Logger LOGGER = LoggerFactory.getLogger(WatermarkStore.class); + private final String sourceName; private final KeyValueStore kvStore; - WatermarkStore(final KeyValueStore kvStore) { + WatermarkStore(final String sourceName, final KeyValueStore kvStore) { + this.sourceName = requireNonNull(sourceName, "OSV sourceName must not be null"); this.kvStore = kvStore; } Map getForEcosystems(final Collection ecosystems) { final Map ecosystemByKey = ecosystems.stream() .collect(Collectors.toMap( - WatermarkStore::getKey, + this::getKey, Function.identity())); final Map kvEntryByKey = kvStore.getMany(ecosystemByKey.keySet()); @@ -98,8 +100,8 @@ WatermarkRecord save(final WatermarkRecord watermark) { }; } - private static String getKey(final String ecosystem) { - return "watermark/" + ecosystem; + private String getKey(final String ecosystem) { + return "watermark/" + sourceName + "/" + ecosystem; } } diff --git a/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json b/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json index 72d4cb4689..51dab7943c 100644 --- a/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json +++ b/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json @@ -6,44 +6,64 @@ "org.dependencytrack.plugin.api.config.RuntimeConfig" ], "properties": { - "enabled": { - "type": "boolean", - "title": "Enabled", - "description": "Whether the OSV data source should be enabled.", - "existingJavaType": "boolean" - }, - "aliasSyncEnabled": { - "type": "boolean", - "title": "Alias Synchronization Enabled", - "description": "Whether to include alias information in vulnerability data." - }, - "incrementalMirroringEnabled": { - "type": "boolean", - "title": "Incremental Mirroring Enabled", - "description": "Whether incremental mirroring is enabled. When enabled (default), watermark logic will be used to only mirror new or updated vulnerabilities. When disabled, full mirroring will be performed on each run.", - "default": true, - "existingJavaType": "boolean" - }, - "dataUrl": { - "type": "string", - "title": "Data URL", - "description": "The URL to download OSV data from. \n**Must** be compatible with [OSV's data dumps](https://google.github.io/osv.dev/data/#data-dumps).", - "minLength": 1, - "format": "uri" - }, - "ecosystems": { + "sources": { "type": "array", - "title": "Ecosystems", - "description": "The ecosystems to mirror vulnerability data for. \nA list of available ecosystems can be found in the [OSV documentation](https://ossf.github.io/osv-schema/#defined-ecosystems).", + "title": "Sources", + "description": "The OSV sources to mirror vulnerability data from. \nEach source is configured, enabled and watermarked independently.", "uniqueItems": true, "items": { - "type": "string", - "title": "Ecosystem", - "minLength": 1 + "type": "object", + "title": "Source", + "javaType": "org.dependencytrack.vulndatasource.osv.OsvSourceConfigV1", + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "The name of the OSV data source.", + "minLength": 1 + }, + "enabled": { + "type": "boolean", + "title": "Enabled", + "description": "Whether the OSV data source should be enabled.", + "existingJavaType": "boolean" + }, + "aliasSyncEnabled": { + "type": "boolean", + "title": "Alias Synchronization Enabled", + "description": "Whether to include alias information in vulnerability data." + }, + "incrementalMirroringEnabled": { + "type": "boolean", + "title": "Incremental Mirroring Enabled", + "description": "Whether incremental mirroring is enabled. When enabled (default), watermark logic will be used to only mirror new or updated vulnerabilities. When disabled, full mirroring will be performed on each run.", + "default": true, + "existingJavaType": "boolean" + }, + "dataUrl": { + "type": "string", + "title": "Data URL", + "description": "The URL to download OSV data from. \n**Must** be compatible with [OSV's data dumps](https://google.github.io/osv.dev/data/#data-dumps).", + "minLength": 1, + "format": "uri" + }, + "ecosystems": { + "type": "array", + "title": "Ecosystems", + "description": "The ecosystems to mirror vulnerability data for. \nA list of available ecosystems can be found in the [OSV documentation](https://ossf.github.io/osv-schema/#defined-ecosystems).", + "uniqueItems": true, + "items": { + "type": "string", + "title": "Ecosystem", + "minLength": 1 + } + } + }, + "required": [ + "name", + "enabled" + ] } } - }, - "required": [ - "enabled" - ] + } } \ No newline at end of file diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java new file mode 100644 index 0000000000..7ce5eaca5c --- /dev/null +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java @@ -0,0 +1,79 @@ +/* + * 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.osv; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import org.cyclonedx.proto.v1_7.Bom; +import org.cyclonedx.proto.v1_7.Vulnerability; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@WireMockTest +class OsvCompositeVulnDataSourceTest { + + @Test + void shouldMirrorAllSources() { + final Bom BOV_A = Bom.newBuilder().addVulnerabilities(Vulnerability.newBuilder() + .setId("CVE-A").build()).build(); + + final Bom BOV_B = Bom.newBuilder().addVulnerabilities(Vulnerability.newBuilder() + .setId("CVE-B").build()).build(); + + final var dataSourceA = mock(OsvVulnDataSource.class); + doReturn(true, false).when(dataSourceA).hasNext(); + doReturn(BOV_A).when(dataSourceA).next(); + + final var dataSourceB = mock(OsvVulnDataSource.class); + doReturn(true, false).when(dataSourceB).hasNext(); + doReturn(BOV_B).when(dataSourceB).next(); + + final var bovs = new ArrayList(); + + + try (var dataSource = new OsvCompositeVulnDataSource(List.of(dataSourceA, dataSourceB))) { + while (dataSource.hasNext()) { + final var bov = dataSource.next(); + bovs.add(bov); + dataSource.markProcessed(bov); + } + } + + assertThat(bovs).containsExactly(BOV_A, BOV_B); + verify(dataSourceA).markProcessed(BOV_A); + verify(dataSourceB).markProcessed(BOV_B); + verify(dataSourceA).close(); + verify(dataSourceB).close(); + } + + @Test + void markProcessedShouldThrowWhenNothingToProcess() { + final var compositeDataSource = new OsvCompositeVulnDataSource(List.of(mock(OsvVulnDataSource.class))); + assertThatExceptionOfType(IllegalStateException.class) + .isThrownBy(() -> compositeDataSource.markProcessed(Bom.newBuilder().build())) + .withMessage("No current data source to mark processed"); + } +} \ No newline at end of file diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java index e2d790a496..189e0ab60d 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java @@ -30,7 +30,9 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import java.net.URI; import java.net.http.HttpClient; +import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -56,93 +58,116 @@ void priorityShouldBeZero() { assertThat(factory.priority()).isEqualTo(100); } + @Test + void defaultConfigShouldContainSingleDefaultSource() { + final OsvVulnDataSourceConfigV1 config = defaultConfig(); + assertThat(config.getSources()).satisfiesExactly(source -> { + assertThat(source.getName()).isEqualTo("default"); + assertThat(source.isEnabled()).isFalse(); + assertThat(source.getAliasSyncEnabled()).isFalse(); + assertThat(source.isIncrementalMirroringEnabled()).isTrue(); + assertThat(source.getDataUrl().toString()).isEqualTo("https://storage.googleapis.com/osv-vulnerabilities"); + assertThat(source.getEcosystems()).containsExactlyInAnyOrder("npm", "PyPI", "NuGet", "Maven", "Go"); + }); + } + @ParameterizedTest @ValueSource(booleans = {true, false}) void isDataSourceEnabledShouldReturnTrueWhenEnabledAndFalseOtherwise(final boolean isEnabled) { - final var config = (OsvVulnDataSourceConfigV1) factory.runtimeConfigSpec().defaultConfig(); - config.setEnabled(isEnabled); - - factory.init( - new MutableServiceRegistry() - .register(ConfigRegistry.class, new MockConfigRegistry(factory.runtimeConfigSpec(), config)) - .register(HttpClient.class, HttpClient.newHttpClient()) - .register(KeyValueStore.class, new MockKeyValueStore())); + final OsvVulnDataSourceConfigV1 config = defaultConfig(); + config.getSources().forEach(source -> source.setEnabled(isEnabled)); + initFactory(config); assertThat(factory.isDataSourceEnabled()).isEqualTo(isEnabled); } @Test void createShouldReturnNullWhenDisabled() { - final var config = (OsvVulnDataSourceConfigV1) factory.runtimeConfigSpec().defaultConfig(); - config.setEnabled(false); - - final var configRegistry = new MockConfigRegistry(factory.runtimeConfigSpec(), config); - - factory.init( - new MutableServiceRegistry() - .register(ConfigRegistry.class, configRegistry) - .register(HttpClient.class, HttpClient.newHttpClient()) - .register(KeyValueStore.class, new MockKeyValueStore())); - + final OsvVulnDataSourceConfigV1 config = defaultConfig(); + config.getSources().forEach(source -> source.setEnabled(false)); + initFactory(config); assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(factory::create); } @Test void createShouldReturnDataSource() { - final var config = (OsvVulnDataSourceConfigV1) factory.runtimeConfigSpec().defaultConfig(); - config.setEnabled(true); - - final var configRegistry = new MockConfigRegistry(factory.runtimeConfigSpec(), config); - - factory.init( - new MutableServiceRegistry() - .register(ConfigRegistry.class, configRegistry) - .register(HttpClient.class, HttpClient.newHttpClient()) - .register(KeyValueStore.class, new MockKeyValueStore())); + final OsvVulnDataSourceConfigV1 config = defaultConfig(); + config.getSources().forEach(source -> source.setEnabled(true)); + initFactory(config); + final VulnDataSource dataSource = factory.create(); + assertThat(dataSource).isNotNull(); + assertThat(((OsvCompositeVulnDataSource) dataSource).getDataSources()).hasSize(1); + dataSource.close(); + } + @Test + void createShouldReturnDataSourcePerEnabledSource() { + final OsvVulnDataSourceConfigV1 config = defaultConfig(); + config.getSources().forEach(source -> source.setEnabled(true)); + config.getSources().add(new OsvSourceConfigV1() + .withName("Chainguard") + .withEnabled(true) + .withIncrementalMirroringEnabled(true) + .withAliasSyncEnabled(false) + .withDataUrl(URI.create("https://chainguard.com/osv-vulnerabilities")) + .withEcosystems(Set.of("Maven"))); + config.getSources().add(new OsvSourceConfigV1() + .withName("Red Hat") + .withEnabled(false) + .withIncrementalMirroringEnabled(false) + .withAliasSyncEnabled(false) + .withDataUrl(URI.create("https://redhat.com/osv-vulnerabilities")) + .withEcosystems(Set.of("Go"))); + initFactory(config); final VulnDataSource dataSource = factory.create(); assertThat(dataSource).isNotNull(); + assertThat(((OsvCompositeVulnDataSource) dataSource).getDataSources()).hasSize(2); dataSource.close(); } @Test void createWhenIncrementalMirroringDisabledShouldCreateDataSourceWithNullWatermarkManager() { - final var config = (OsvVulnDataSourceConfigV1) factory.runtimeConfigSpec().defaultConfig(); - config.setEnabled(true); - config.setIncrementalMirroringEnabled(false); - - final var configRegistry = new MockConfigRegistry(factory.runtimeConfigSpec(), config); - - factory.init( - new MutableServiceRegistry() - .register(ConfigRegistry.class, configRegistry) - .register(HttpClient.class, HttpClient.newHttpClient()) - .register(KeyValueStore.class, new MockKeyValueStore())); - + final OsvVulnDataSourceConfigV1 config = defaultConfig(); + config.getSources().forEach(source -> { + source.setEnabled(true); + source.setIncrementalMirroringEnabled(false); + }); + initFactory(config); try (VulnDataSource dataSource = factory.create()) { - assertThat(dataSource).isNotNull(); - assertThat(((OsvVulnDataSource) dataSource).getWatermarkManager()).isNull(); + assertThat(((OsvCompositeVulnDataSource) dataSource).getDataSources()) + .singleElement() + .satisfies(source -> assertThat(source.getWatermarkManager()).isNull()); } } @Test void createWhenIncrementalMirroringEnabledShouldCreateDataSourceWithWatermarkManager() { - final var config = (OsvVulnDataSourceConfigV1) factory.runtimeConfigSpec().defaultConfig(); - config.setEnabled(true); - config.setIncrementalMirroringEnabled(true); + final OsvVulnDataSourceConfigV1 config = defaultConfig(); + config.getSources().forEach(source -> { + source.setEnabled(true); + source.setIncrementalMirroringEnabled(true); + }); + initFactory(config); + try (VulnDataSource dataSource = factory.create()) { + assertThat(((OsvCompositeVulnDataSource) dataSource).getDataSources()) + .singleElement() + .satisfies(source -> assertThat(source.getWatermarkManager()).isNotNull()); + } + } - final var configRegistry = new MockConfigRegistry(factory.runtimeConfigSpec(), config); + private OsvVulnDataSourceConfigV1 defaultConfig() { + return (OsvVulnDataSourceConfigV1) factory.runtimeConfigSpec().defaultConfig(); + } + private void initFactory(final OsvVulnDataSourceConfigV1 config) { + initFactory(new MockConfigRegistry(factory.runtimeConfigSpec(), config)); + } + + private void initFactory(final ConfigRegistry configRegistry) { factory.init( new MutableServiceRegistry() .register(ConfigRegistry.class, configRegistry) .register(HttpClient.class, HttpClient.newHttpClient()) .register(KeyValueStore.class, new MockKeyValueStore())); - - try (VulnDataSource dataSource = factory.create()) { - assertThat(dataSource).isNotNull(); - assertThat(((OsvVulnDataSource) dataSource).getWatermarkManager()).isNotNull(); - } } - } \ No newline at end of file diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceTest.java index 2cd21e86e6..a73b4c546f 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceTest.java @@ -73,6 +73,7 @@ void beforeEach() { objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); vulnDataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, "http://localhost", @@ -183,6 +184,7 @@ void shouldIterateAdvisoriesFromFullArchive(WireMockRuntimeInfo wmRuntimeInfo) t .withHeader("Content-Type", "application/zip"))); OsvVulnDataSource dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -253,6 +255,7 @@ void shouldPercentEncodeSpacesInEcosystemNameForFullArchive(WireMockRuntimeInfo .withBody(zipBytes.toByteArray()))); try (var dataSource = new OsvVulnDataSource( + "default", null, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -295,6 +298,7 @@ void shouldSkipDirectoryAndNonJsonEntriesInFullArchive(WireMockRuntimeInfo wmRun final var ids = new ArrayList(); try (var dataSource = new OsvVulnDataSource( + "default", null, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -333,6 +337,7 @@ void nullWatermarkManagerPerformsFullDownload(WireMockRuntimeInfo wmRuntimeInfo) .withHeader("Content-Type", "application/zip"))); try (var dataSource = new OsvVulnDataSource( + "default", null, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -375,6 +380,7 @@ void watermarkManagerReturnsNullPerformsFullDownload(WireMockRuntimeInfo wmRunti .withHeader("Content-Type", "application/zip"))); try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -416,6 +422,7 @@ void watermarkManagerReturnsInstantPerformsIncrementalDownload(WireMockRuntimeIn .withBody(advisoryJson))); try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -458,6 +465,7 @@ void shouldDownloadIncrementalAdvisoriesLazily(WireMockRuntimeInfo wmRuntimeInfo } try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -512,6 +520,7 @@ void shouldFallBackToFullDownloadWhenIncrementalThresholdExceeded(WireMockRuntim .withBody(zipBytes.toByteArray()))); try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -570,6 +579,7 @@ void shouldOnlyProcessChangedAdvisoriesFromFullArchive(WireMockRuntimeInfo wmRun final var ids = new ArrayList(); try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -633,6 +643,7 @@ void shouldContinueWithRemainingEcosystemsWhenEcosystemHasNoChanges(WireMockRunt final var ids = new ArrayList(); try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -681,6 +692,7 @@ void shouldContinueWithRemainingEcosystemsWhenFirstEcosystemHasNoChanges(WireMoc final var ids = new ArrayList(); try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), @@ -717,6 +729,7 @@ void shouldPercentEncodeSpacesInEcosystemNameForIncrementalAdvisories(WireMockRu } try (var dataSource = new OsvVulnDataSource( + "default", watermarkManagerMock, objectMapper, wmRuntimeInfo.getHttpBaseUrl(), diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java index ee4fc2c07d..0bc5b7bc99 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java @@ -35,11 +35,11 @@ class WatermarkManagerTest { @Test void createShouldInitializeWatermarkWhenAvailable() { kvStore.putMany(Map.ofEntries( - Map.entry("watermark/maven", String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())), - Map.entry("watermark/npm", String.valueOf(Instant.ofEpochSecond(555).toEpochMilli())) + Map.entry("watermark/default/maven", String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())), + Map.entry("watermark/default/npm", String.valueOf(Instant.ofEpochSecond(555).toEpochMilli())) )); - final var watermarkManager = new WatermarkManager(List.of("maven", "npm"), kvStore); + final var watermarkManager = new WatermarkManager("default", List.of("maven", "npm"), kvStore); assertThat(watermarkManager).isNotNull(); assertThat(watermarkManager.getWatermark("maven")).isEqualTo(Instant.ofEpochSecond(666)); assertThat(watermarkManager.getWatermark("npm")).isEqualTo(Instant.ofEpochSecond(555)); @@ -47,14 +47,14 @@ void createShouldInitializeWatermarkWhenAvailable() { @Test void createShouldNotInitializeWatermarkWhenNotAvailable() { - final var watermarkManager = new WatermarkManager(List.of("maven"), kvStore); + final var watermarkManager = new WatermarkManager("default", List.of("maven"), kvStore); assertThat(watermarkManager).isNotNull(); assertThat(watermarkManager.getWatermark("maven")).isNull(); } @Test void shouldAdvanceWatermarkWhenInitialWatermarkIsNull() { - final var watermarkManager = new WatermarkManager(List.of("maven"), kvStore); + final var watermarkManager = new WatermarkManager("default", List.of("maven"), kvStore); watermarkManager.maybeAdvance("maven", Instant.ofEpochSecond(666)); assertThat(watermarkManager.getWatermark("maven")).isNull(); @@ -65,9 +65,9 @@ void shouldAdvanceWatermarkWhenInitialWatermarkIsNull() { @Test void shouldAdvanceWatermarkWhenInitialWatermarkIsEarlier() { - kvStore.put("watermark/maven", String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())); + kvStore.put("watermark/default/maven", String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())); - final var watermarkManager = new WatermarkManager(List.of("maven"), kvStore); + final var watermarkManager = new WatermarkManager("default", List.of("maven"), kvStore); watermarkManager.maybeAdvance("maven", Instant.ofEpochSecond(667)); assertThat(watermarkManager.getWatermark("maven")).isEqualTo(Instant.ofEpochSecond(666)); From 82f709891df03cb47df44e5ae3b1b701dbac7ac5 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 27 Aug 2026 15:51:16 +0100 Subject: [PATCH 2/6] Cleanup formatting Signed-off-by: Sahiba Mittal --- .../osv/OsvCompositeVulnDataSource.java | 8 ++- .../osv/OsvVulnDataSourceFactory.java | 24 +++++---- .../vulndatasource/osv/WatermarkManager.java | 8 +-- .../vulndatasource/osv/WatermarkStore.java | 6 +-- .../osv/OsvCompositeVulnDataSourceTest.java | 16 +++--- .../osv/OsvVulnDataSourceFactoryTest.java | 53 ++++++++++--------- .../osv/WatermarkManagerTest.java | 13 +++-- 7 files changed, 67 insertions(+), 61 deletions(-) diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java index 8e6ef6c443..15a75154bb 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java @@ -39,19 +39,17 @@ final class OsvCompositeVulnDataSource implements VulnDataSource { private @Nullable OsvVulnDataSource currentDataSource; private int currentDataSourceIndex; - OsvCompositeVulnDataSource( - final List dataSources) { + OsvCompositeVulnDataSource(final List dataSources) { this.dataSources = requireNonNull(dataSources, "dataSources must not be null"); } - @Override public boolean hasNext() { while (currentDataSourceIndex < dataSources.size()) { if (dataSources.get(currentDataSourceIndex).hasNext()) { return true; } - currentDataSourceIndex ++; + currentDataSourceIndex++; } return false; } @@ -87,4 +85,4 @@ public void close() { List getDataSources() { return dataSources; } -} \ No newline at end of file +} diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java index 316a972f0c..cad26708e8 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java @@ -55,6 +55,11 @@ public String extensionName() { return "osv"; } + @Override + public String displayName() { + return "OSV"; + } + @Override public Class extensionClass() { return OsvVulnDataSource.class; @@ -70,8 +75,7 @@ public void init(ServiceRegistry serviceRegistry) { this.configRegistry = serviceRegistry.require(ConfigRegistry.class); this.kvStore = serviceRegistry.require(KeyValueStore.class); this.httpClient = serviceRegistry.require(HttpClient.class); - this.objectMapper = new ObjectMapper() - .registerModule(new JavaTimeModule()); + this.objectMapper = new ObjectMapper().registerModule(new JavaTimeModule()); } @Override @@ -84,8 +88,8 @@ public RuntimeConfigSpec runtimeConfigSpec() { .withDataUrl(URI.create("https://storage.googleapis.com/osv-vulnerabilities")) .withEcosystems(Set.of("Go", "Maven", "npm", "NuGet", "PyPI")); - final var defaultConfig = new OsvVulnDataSourceConfigV1() - .withSources(new LinkedHashSet<>(Set.of(defaultSource))); + final var defaultConfig = + new OsvVulnDataSourceConfigV1().withSources(new LinkedHashSet<>(Set.of(defaultSource))); return RuntimeConfigSpec.of(defaultConfig, (OsvVulnDataSourceConfigV1 config) -> { for (final var source : config.getSources()) { @@ -108,7 +112,8 @@ public RuntimeConfigSpec runtimeConfigSpec() { @Override public boolean isDataSourceEnabled() { requireNonNull(configRegistry, "configRegistry must not be null"); - return !enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)).isEmpty(); + return !enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)) + .isEmpty(); } @Override @@ -118,7 +123,8 @@ public VulnDataSource create() { requireNonNull(objectMapper, "objectMapper must not be null"); requireNonNull(httpClient, "httpClient must not be null"); - final List sources = enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)); + final List sources = + enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)); if (sources.isEmpty()) { throw new IllegalStateException("Vulnerability data source is disabled and cannot be created"); } @@ -142,8 +148,6 @@ public VulnDataSource create() { } private List enabledSources(final OsvVulnDataSourceConfigV1 config) { - return config.getSources().stream() - .filter(OsvSourceConfigV1::isEnabled) - .toList(); + return config.getSources().stream().filter(OsvSourceConfigV1::isEnabled).toList(); } -} \ No newline at end of file +} diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java index be1cb390b9..0acd43fd1d 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkManager.java @@ -39,13 +39,9 @@ final class WatermarkManager { private final Map pendingRecordByEcosystem; private final Map committedRecordByEcosystem; - WatermarkManager( - final String sourceName, - final Collection ecosystems, - final KeyValueStore kvStore) { + WatermarkManager(final String sourceName, final Collection ecosystems, final KeyValueStore kvStore) { final var watermarkStore = new WatermarkStore(sourceName, kvStore); - final Map recordByEcosystem = - watermarkStore.getForEcosystems(ecosystems); + final Map recordByEcosystem = watermarkStore.getForEcosystems(ecosystems); this.store = watermarkStore; this.pendingRecordByEcosystem = new HashMap<>(recordByEcosystem); diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java index bc64846f53..b53560418d 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/WatermarkStore.java @@ -49,10 +49,8 @@ final class WatermarkStore { } Map getForEcosystems(final Collection ecosystems) { - final Map ecosystemByKey = ecosystems.stream() - .collect(Collectors.toMap( - this::getKey, - Function.identity())); + final Map ecosystemByKey = + ecosystems.stream().collect(Collectors.toMap(this::getKey, Function.identity())); final Map kvEntryByKey = kvStore.getMany(ecosystemByKey.keySet()); if (kvEntryByKey.isEmpty()) { diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java index 7ce5eaca5c..608f0063ea 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java @@ -37,11 +37,13 @@ class OsvCompositeVulnDataSourceTest { @Test void shouldMirrorAllSources() { - final Bom BOV_A = Bom.newBuilder().addVulnerabilities(Vulnerability.newBuilder() - .setId("CVE-A").build()).build(); + final Bom BOV_A = Bom.newBuilder() + .addVulnerabilities(Vulnerability.newBuilder().setId("CVE-A").build()) + .build(); - final Bom BOV_B = Bom.newBuilder().addVulnerabilities(Vulnerability.newBuilder() - .setId("CVE-B").build()).build(); + final Bom BOV_B = Bom.newBuilder() + .addVulnerabilities(Vulnerability.newBuilder().setId("CVE-B").build()) + .build(); final var dataSourceA = mock(OsvVulnDataSource.class); doReturn(true, false).when(dataSourceA).hasNext(); @@ -53,7 +55,6 @@ void shouldMirrorAllSources() { final var bovs = new ArrayList(); - try (var dataSource = new OsvCompositeVulnDataSource(List.of(dataSourceA, dataSourceB))) { while (dataSource.hasNext()) { final var bov = dataSource.next(); @@ -73,7 +74,8 @@ void shouldMirrorAllSources() { void markProcessedShouldThrowWhenNothingToProcess() { final var compositeDataSource = new OsvCompositeVulnDataSource(List.of(mock(OsvVulnDataSource.class))); assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(() -> compositeDataSource.markProcessed(Bom.newBuilder().build())) + .isThrownBy( + () -> compositeDataSource.markProcessed(Bom.newBuilder().build())) .withMessage("No current data source to mark processed"); } -} \ No newline at end of file +} diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java index 189e0ab60d..44cb46ec1b 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java @@ -37,7 +37,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -class OsvVulnDataSourceFactoryTest extends AbstractExtensionFactoryTest<@NonNull VulnDataSource, @NonNull OsvVulnDataSourceFactory> { +class OsvVulnDataSourceFactoryTest + extends AbstractExtensionFactoryTest<@NonNull VulnDataSource, @NonNull OsvVulnDataSourceFactory> { protected OsvVulnDataSourceFactoryTest() { super(OsvVulnDataSourceFactory.class); @@ -85,8 +86,7 @@ void createShouldReturnNullWhenDisabled() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); config.getSources().forEach(source -> source.setEnabled(false)); initFactory(config); - assertThatExceptionOfType(IllegalStateException.class) - .isThrownBy(factory::create); + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(factory::create); } @Test @@ -104,20 +104,22 @@ void createShouldReturnDataSource() { void createShouldReturnDataSourcePerEnabledSource() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); config.getSources().forEach(source -> source.setEnabled(true)); - config.getSources().add(new OsvSourceConfigV1() - .withName("Chainguard") - .withEnabled(true) - .withIncrementalMirroringEnabled(true) - .withAliasSyncEnabled(false) - .withDataUrl(URI.create("https://chainguard.com/osv-vulnerabilities")) - .withEcosystems(Set.of("Maven"))); - config.getSources().add(new OsvSourceConfigV1() - .withName("Red Hat") - .withEnabled(false) - .withIncrementalMirroringEnabled(false) - .withAliasSyncEnabled(false) - .withDataUrl(URI.create("https://redhat.com/osv-vulnerabilities")) - .withEcosystems(Set.of("Go"))); + config.getSources() + .add(new OsvSourceConfigV1() + .withName("Chainguard") + .withEnabled(true) + .withIncrementalMirroringEnabled(true) + .withAliasSyncEnabled(false) + .withDataUrl(URI.create("https://chainguard.com/osv-vulnerabilities")) + .withEcosystems(Set.of("Maven"))); + config.getSources() + .add(new OsvSourceConfigV1() + .withName("Red Hat") + .withEnabled(false) + .withIncrementalMirroringEnabled(false) + .withAliasSyncEnabled(false) + .withDataUrl(URI.create("https://redhat.com/osv-vulnerabilities")) + .withEcosystems(Set.of("Go"))); initFactory(config); final VulnDataSource dataSource = factory.create(); assertThat(dataSource).isNotNull(); @@ -136,7 +138,8 @@ void createWhenIncrementalMirroringDisabledShouldCreateDataSourceWithNullWaterma try (VulnDataSource dataSource = factory.create()) { assertThat(((OsvCompositeVulnDataSource) dataSource).getDataSources()) .singleElement() - .satisfies(source -> assertThat(source.getWatermarkManager()).isNull()); + .satisfies( + source -> assertThat(source.getWatermarkManager()).isNull()); } } @@ -151,7 +154,8 @@ void createWhenIncrementalMirroringEnabledShouldCreateDataSourceWithWatermarkMan try (VulnDataSource dataSource = factory.create()) { assertThat(((OsvCompositeVulnDataSource) dataSource).getDataSources()) .singleElement() - .satisfies(source -> assertThat(source.getWatermarkManager()).isNotNull()); + .satisfies( + source -> assertThat(source.getWatermarkManager()).isNotNull()); } } @@ -164,10 +168,9 @@ private void initFactory(final OsvVulnDataSourceConfigV1 config) { } private void initFactory(final ConfigRegistry configRegistry) { - factory.init( - new MutableServiceRegistry() - .register(ConfigRegistry.class, configRegistry) - .register(HttpClient.class, HttpClient.newHttpClient()) - .register(KeyValueStore.class, new MockKeyValueStore())); + factory.init(new MutableServiceRegistry() + .register(ConfigRegistry.class, configRegistry) + .register(HttpClient.class, HttpClient.newHttpClient()) + .register(KeyValueStore.class, new MockKeyValueStore())); } -} \ No newline at end of file +} diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java index fcfb5bb994..c0d6c0912a 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/WatermarkManagerTest.java @@ -35,9 +35,12 @@ class WatermarkManagerTest { @Test void createShouldInitializeWatermarkWhenAvailable() { kvStore.putMany(Map.ofEntries( - Map.entry("watermark/default/maven", String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())), - Map.entry("watermark/default/npm", String.valueOf(Instant.ofEpochSecond(555).toEpochMilli())) - )); + Map.entry( + "watermark/default/maven", + String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())), + Map.entry( + "watermark/default/npm", + String.valueOf(Instant.ofEpochSecond(555).toEpochMilli())))); final var watermarkManager = new WatermarkManager("default", List.of("maven", "npm"), kvStore); assertThat(watermarkManager).isNotNull(); @@ -65,7 +68,9 @@ void shouldAdvanceWatermarkWhenInitialWatermarkIsNull() { @Test void shouldAdvanceWatermarkWhenInitialWatermarkIsEarlier() { - kvStore.put("watermark/default/maven", String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())); + kvStore.put( + "watermark/default/maven", + String.valueOf(Instant.ofEpochSecond(666).toEpochMilli())); final var watermarkManager = new WatermarkManager("default", List.of("maven"), kvStore); From 6cd14e52ff0274ce8f3b308f81b4a4f608443066 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 17 Sep 2026 14:09:39 +0100 Subject: [PATCH 3/6] Rename OSV sources to feeds; add MDC logging Signed-off-by: Sahiba Mittal --- .../osv/OsvCompositeVulnDataSource.java | 11 +++-- .../osv/OsvVulnDataSourceFactory.java | 47 ++++++++++--------- ...osv-vuln-data-source-config-v1.schema.json | 14 +++--- .../osv/OsvCompositeVulnDataSourceTest.java | 2 - .../osv/OsvVulnDataSourceFactoryTest.java | 26 +++++----- 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java index 15a75154bb..83f5c67863 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java @@ -23,6 +23,7 @@ import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import java.util.List; import java.util.NoSuchElementException; @@ -60,7 +61,9 @@ public Bom next() { throw new NoSuchElementException(); } currentDataSource = dataSources.get(currentDataSourceIndex); - return currentDataSource.next(); + try (final var _ = MDC.putCloseable("osvSource", currentDataSource.getDataSourceName())) { + return currentDataSource.next(); + } } @Override @@ -68,7 +71,9 @@ public void markProcessed(final Bom bom) { if (currentDataSource == null) { throw new IllegalStateException("No current data source to mark processed"); } - currentDataSource.markProcessed(bom); + try (final var _ = MDC.putCloseable("osvSource", currentDataSource.getDataSourceName())) { + currentDataSource.markProcessed(bom); + } } @Override @@ -77,7 +82,7 @@ public void close() { try { dataSource.close(); } catch (final Exception e) { - LOGGER.warn("Failed to close data source: {}", dataSource, e); + LOGGER.warn("Failed to close data source: {}", dataSource.getDataSourceName(), e); } } } diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java index cad26708e8..8e306601b7 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactory.java @@ -88,21 +88,24 @@ public RuntimeConfigSpec runtimeConfigSpec() { .withDataUrl(URI.create("https://storage.googleapis.com/osv-vulnerabilities")) .withEcosystems(Set.of("Go", "Maven", "npm", "NuGet", "PyPI")); - final var defaultConfig = - new OsvVulnDataSourceConfigV1().withSources(new LinkedHashSet<>(Set.of(defaultSource))); + final var defaultConfig = new OsvVulnDataSourceConfigV1().withFeeds(new LinkedHashSet<>(Set.of(defaultSource))); return RuntimeConfigSpec.of(defaultConfig, (OsvVulnDataSourceConfigV1 config) -> { - for (final var source : config.getSources()) { - if (source.getName() == null || source.getName().isBlank()) { - throw new InvalidRuntimeConfigException("No data source name provided"); + final Set seenNames = new LinkedHashSet<>(); + for (final var feed : config.getFeeds()) { + if (feed.getName() == null || feed.getName().isBlank()) { + throw new InvalidRuntimeConfigException("No data feed name provided"); } - if (!source.isEnabled()) { + if (!seenNames.add(feed.getName())) { + throw new InvalidRuntimeConfigException("Duplicate data feed name provided: " + feed.getName()); + } + if (!feed.isEnabled()) { continue; } - if (source.getDataUrl() == null) { + if (feed.getDataUrl() == null) { throw new InvalidRuntimeConfigException("No data URL provided"); } - if (source.getEcosystems() == null || source.getEcosystems().isEmpty()) { + if (feed.getEcosystems() == null || feed.getEcosystems().isEmpty()) { throw new InvalidRuntimeConfigException("At least one ecosystem must be specified"); } } @@ -112,7 +115,7 @@ public RuntimeConfigSpec runtimeConfigSpec() { @Override public boolean isDataSourceEnabled() { requireNonNull(configRegistry, "configRegistry must not be null"); - return !enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)) + return !enabledFeeds(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)) .isEmpty(); } @@ -123,31 +126,31 @@ public VulnDataSource create() { requireNonNull(objectMapper, "objectMapper must not be null"); requireNonNull(httpClient, "httpClient must not be null"); - final List sources = - enabledSources(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)); - if (sources.isEmpty()) { + final List feeds = + enabledFeeds(configRegistry.getRuntimeConfig(OsvVulnDataSourceConfigV1.class)); + if (feeds.isEmpty()) { throw new IllegalStateException("Vulnerability data source is disabled and cannot be created"); } - final var dataSources = new ArrayList(sources.size()); - for (final OsvSourceConfigV1 source : sources) { - final WatermarkManager watermarkManager = source.isIncrementalMirroringEnabled() - ? new WatermarkManager(source.getName(), source.getEcosystems(), kvStore) + final var dataSources = new ArrayList(feeds.size()); + for (final OsvSourceConfigV1 feed : feeds) { + final WatermarkManager watermarkManager = feed.isIncrementalMirroringEnabled() + ? new WatermarkManager(feed.getName(), feed.getEcosystems(), kvStore) : null; dataSources.add(new OsvVulnDataSource( - source.getName(), + feed.getName(), watermarkManager, objectMapper, - source.getDataUrl().toString(), - source.getEcosystems(), + feed.getDataUrl().toString(), + feed.getEcosystems(), httpClient, - source.getAliasSyncEnabled())); + feed.getAliasSyncEnabled())); } return new OsvCompositeVulnDataSource(dataSources); } - private List enabledSources(final OsvVulnDataSourceConfigV1 config) { - return config.getSources().stream().filter(OsvSourceConfigV1::isEnabled).toList(); + private List enabledFeeds(final OsvVulnDataSourceConfigV1 config) { + return config.getFeeds().stream().filter(OsvSourceConfigV1::isEnabled).toList(); } } diff --git a/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json b/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json index 51dab7943c..ff4b2191c8 100644 --- a/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json +++ b/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json @@ -6,10 +6,10 @@ "org.dependencytrack.plugin.api.config.RuntimeConfig" ], "properties": { - "sources": { + "feeds": { "type": "array", - "title": "Sources", - "description": "The OSV sources to mirror vulnerability data from. \nEach source is configured, enabled and watermarked independently.", + "title": "Feeds", + "description": "The OSV feeds to mirror vulnerability data from. \nEach feed is configured, enabled and watermarked independently.", "uniqueItems": true, "items": { "type": "object", @@ -19,8 +19,10 @@ "name": { "type": "string", "title": "Name", - "description": "The name of the OSV data source.", - "minLength": 1 + "description": "The name of the OSV data source. Used in watermark keys and other identifiers; must be 1–255 characters and only contain letters, digits, dot (.), underscore (_) or hyphen (-).", + "minLength": 1, + "maxLength": 255, + "pattern": "^[A-Za-z0-9._-]+$" }, "enabled": { "type": "boolean", @@ -66,4 +68,4 @@ } } } -} \ No newline at end of file +} diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java index 608f0063ea..6184afb5ff 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java @@ -18,7 +18,6 @@ */ package org.dependencytrack.vulndatasource.osv; -import com.github.tomakehurst.wiremock.junit5.WireMockTest; import org.cyclonedx.proto.v1_7.Bom; import org.cyclonedx.proto.v1_7.Vulnerability; import org.junit.jupiter.api.Test; @@ -32,7 +31,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -@WireMockTest class OsvCompositeVulnDataSourceTest { @Test diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java index 44cb46ec1b..6a0469ed9b 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvVulnDataSourceFactoryTest.java @@ -62,7 +62,7 @@ void priorityShouldBeZero() { @Test void defaultConfigShouldContainSingleDefaultSource() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); - assertThat(config.getSources()).satisfiesExactly(source -> { + assertThat(config.getFeeds()).satisfiesExactly(feed -> { assertThat(source.getName()).isEqualTo("default"); assertThat(source.isEnabled()).isFalse(); assertThat(source.getAliasSyncEnabled()).isFalse(); @@ -76,7 +76,7 @@ void defaultConfigShouldContainSingleDefaultSource() { @ValueSource(booleans = {true, false}) void isDataSourceEnabledShouldReturnTrueWhenEnabledAndFalseOtherwise(final boolean isEnabled) { final OsvVulnDataSourceConfigV1 config = defaultConfig(); - config.getSources().forEach(source -> source.setEnabled(isEnabled)); + config.getFeeds().forEach(feed -> feed.setEnabled(isEnabled)); initFactory(config); assertThat(factory.isDataSourceEnabled()).isEqualTo(isEnabled); } @@ -84,7 +84,7 @@ void isDataSourceEnabledShouldReturnTrueWhenEnabledAndFalseOtherwise(final boole @Test void createShouldReturnNullWhenDisabled() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); - config.getSources().forEach(source -> source.setEnabled(false)); + config.getFeeds().forEach(feed -> feed.setEnabled(false)); initFactory(config); assertThatExceptionOfType(IllegalStateException.class).isThrownBy(factory::create); } @@ -92,7 +92,7 @@ void createShouldReturnNullWhenDisabled() { @Test void createShouldReturnDataSource() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); - config.getSources().forEach(source -> source.setEnabled(true)); + config.getFeeds().forEach(feed -> feed.setEnabled(true)); initFactory(config); final VulnDataSource dataSource = factory.create(); assertThat(dataSource).isNotNull(); @@ -103,8 +103,8 @@ void createShouldReturnDataSource() { @Test void createShouldReturnDataSourcePerEnabledSource() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); - config.getSources().forEach(source -> source.setEnabled(true)); - config.getSources() + config.getFeeds().forEach(feed -> feed.setEnabled(true)); + config.getFeeds() .add(new OsvSourceConfigV1() .withName("Chainguard") .withEnabled(true) @@ -112,7 +112,7 @@ void createShouldReturnDataSourcePerEnabledSource() { .withAliasSyncEnabled(false) .withDataUrl(URI.create("https://chainguard.com/osv-vulnerabilities")) .withEcosystems(Set.of("Maven"))); - config.getSources() + config.getFeeds() .add(new OsvSourceConfigV1() .withName("Red Hat") .withEnabled(false) @@ -130,9 +130,9 @@ void createShouldReturnDataSourcePerEnabledSource() { @Test void createWhenIncrementalMirroringDisabledShouldCreateDataSourceWithNullWatermarkManager() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); - config.getSources().forEach(source -> { - source.setEnabled(true); - source.setIncrementalMirroringEnabled(false); + config.getFeeds().forEach(feed -> { + feed.setEnabled(true); + feed.setIncrementalMirroringEnabled(false); }); initFactory(config); try (VulnDataSource dataSource = factory.create()) { @@ -146,9 +146,9 @@ void createWhenIncrementalMirroringDisabledShouldCreateDataSourceWithNullWaterma @Test void createWhenIncrementalMirroringEnabledShouldCreateDataSourceWithWatermarkManager() { final OsvVulnDataSourceConfigV1 config = defaultConfig(); - config.getSources().forEach(source -> { - source.setEnabled(true); - source.setIncrementalMirroringEnabled(true); + config.getFeeds().forEach(feed -> { + feed.setEnabled(true); + feed.setIncrementalMirroringEnabled(true); }); initFactory(config); try (VulnDataSource dataSource = factory.create()) { From eb80dd5358ca6ff1563e50bb0ac21aa2d353d635 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 17 Sep 2026 16:01:33 +0100 Subject: [PATCH 4/6] Track origin osv data source for Bom Signed-off-by: Sahiba Mittal --- .../osv/OsvCompositeVulnDataSource.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java index 83f5c67863..1d2d6992ca 100644 --- a/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java +++ b/vuln-data-source/osv/src/main/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSource.java @@ -25,7 +25,10 @@ import org.slf4j.LoggerFactory; import org.slf4j.MDC; +import java.util.Collections; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import static java.util.Objects.requireNonNull; @@ -40,6 +43,12 @@ final class OsvCompositeVulnDataSource implements VulnDataSource { private @Nullable OsvVulnDataSource currentDataSource; private int currentDataSourceIndex; + /** + * Tracks the originating data source for a Bom instance so markProcessed can be + * delegated to the producer even if currentDataSource has moved on. + */ + private final Map originMap = Collections.synchronizedMap(new IdentityHashMap<>()); + OsvCompositeVulnDataSource(final List dataSources) { this.dataSources = requireNonNull(dataSources, "dataSources must not be null"); } @@ -62,17 +71,21 @@ public Bom next() { } currentDataSource = dataSources.get(currentDataSourceIndex); try (final var _ = MDC.putCloseable("osvSource", currentDataSource.getDataSourceName())) { - return currentDataSource.next(); + final Bom bom = currentDataSource.next(); + originMap.put(bom, currentDataSource); + return bom; } } @Override public void markProcessed(final Bom bom) { - if (currentDataSource == null) { - throw new IllegalStateException("No current data source to mark processed"); + final var origin = originMap.remove(bom); + final var target = origin != null ? origin : currentDataSource; + if (target == null) { + throw new IllegalStateException("No data source available to mark processed"); } - try (final var _ = MDC.putCloseable("osvSource", currentDataSource.getDataSourceName())) { - currentDataSource.markProcessed(bom); + try (final var _ = MDC.putCloseable("osvSource", target.getDataSourceName())) { + target.markProcessed(bom); } } From ef4861f71dee3c5d1bb872cb2c656c85ed1a6ca4 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 17 Sep 2026 17:10:30 +0100 Subject: [PATCH 5/6] Allow space in osv feed name Signed-off-by: Sahiba Mittal --- .../osv/osv-vuln-data-source-config-v1.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json b/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json index ff4b2191c8..41abeb26a3 100644 --- a/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json +++ b/vuln-data-source/osv/src/main/resources/org/dependencytrack/vulndatasource/osv/osv-vuln-data-source-config-v1.schema.json @@ -19,10 +19,10 @@ "name": { "type": "string", "title": "Name", - "description": "The name of the OSV data source. Used in watermark keys and other identifiers; must be 1–255 characters and only contain letters, digits, dot (.), underscore (_) or hyphen (-).", + "description": "The name of the OSV data feed. Used in watermark keys and other identifiers; must be 1–255 characters and contain only letters, digits, space, dot (.), underscore (_) or hyphen (-).", "minLength": 1, "maxLength": 255, - "pattern": "^[A-Za-z0-9._-]+$" + "pattern": "^[A-Za-z0-9 ._-]+$" }, "enabled": { "type": "boolean", From 501058fd3ddc6f333c7cef072b3c5546dbbd528b Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 18 Sep 2026 09:56:22 +0100 Subject: [PATCH 6/6] Update OsvCompositeVulnDataSourceTest.java Signed-off-by: Sahiba Mittal --- .../vulndatasource/osv/OsvCompositeVulnDataSourceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java index 6184afb5ff..778c73dcea 100644 --- a/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java +++ b/vuln-data-source/osv/src/test/java/org/dependencytrack/vulndatasource/osv/OsvCompositeVulnDataSourceTest.java @@ -74,6 +74,6 @@ void markProcessedShouldThrowWhenNothingToProcess() { assertThatExceptionOfType(IllegalStateException.class) .isThrownBy( () -> compositeDataSource.markProcessed(Bom.newBuilder().build())) - .withMessage("No current data source to mark processed"); + .withMessage("No data source available to mark processed"); } }