From 5110b3a894520883c1881b5da8eee38892aaae41 Mon Sep 17 00:00:00 2001 From: Chad Wilson <29788154+chadlwilson@users.noreply.github.com> Date: Thu, 7 May 2026 23:40:18 +0800 Subject: [PATCH 1/2] feat: allow configuration of connection socket+read timeouts Signed-off-by: Chad Wilson <29788154+chadlwilson@users.noreply.github.com> --- .../extension/DependencyCheckExtension.groovy | 46 +++++++ .../gradle/tasks/ConfiguredTask.groovy | 4 + .../DependencyCheckGradlePluginSpec.groovy | 121 ++++++++++-------- 3 files changed, 117 insertions(+), 54 deletions(-) diff --git a/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy b/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy index 1a186df..16bfa51 100644 --- a/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy +++ b/src/main/groovy/org/owasp/dependencycheck/gradle/extension/DependencyCheckExtension.groovy @@ -32,6 +32,7 @@ import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Optional import javax.inject.Inject +import java.time.Duration import java.util.stream.Collectors import static org.owasp.dependencycheck.reporting.ReportGenerator.Format @@ -77,6 +78,9 @@ class DependencyCheckExtension { private final ConfigurableFileCollection scanSet private boolean scanSetConfigured = false + private final Property connectionTimeout + private final Property readTimeout + /** * The configuration extension for proxy settings. */ @@ -146,6 +150,8 @@ class DependencyCheckExtension { this.analyzedTypes = objects.listProperty(String).convention(['jar', 'aar', 'js', 'war', 'ear', 'zip']) this.skip = objects.property(Boolean).convention(false) this.scanSet = objects.fileCollection() + this.connectionTimeout = objects.property(Duration) + this.readTimeout = objects.property(Duration) cache = objects.newInstance(CacheExtension, objects) slack = objects.newInstance(SlackExtension, objects) @@ -540,6 +546,46 @@ class DependencyCheckExtension { scanSetConfigured } + /** + * The connection timeout used when downloading external data. + */ + @Input + @Optional + Property getConnectionTimeout() { + return connectionTimeout + } + + void setConnectionTimeout(Duration value) { + connectionTimeout.set(value) + } + + /** + * @param value The connection timeout in milliseconds + */ + void setConnectionTimeout(Number value) { + connectionTimeout.set(value != null ? Duration.ofMillis(value.longValue()) : null) + } + + /** + * The connection timeout used when downloading external data. + */ + @Input + @Optional + Property getReadTimeout() { + return readTimeout + } + + void setReadTimeout(Duration value) { + readTimeout.set(value) + } + + /** + * @param value The connection read timeout in milliseconds + */ + void setReadTimeout(Number value) { + readTimeout.set(value != null ? Duration.ofMillis(value.longValue()) : null) + } + /** * Allows programmatic configuration of the proxy extension * @param configClosure the closure to configure the proxy extension diff --git a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/ConfiguredTask.groovy b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/ConfiguredTask.groovy index 347a186..057c97c 100644 --- a/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/ConfiguredTask.groovy +++ b/src/main/groovy/org/owasp/dependencycheck/gradle/tasks/ConfiguredTask.groovy @@ -108,6 +108,10 @@ abstract class ConfiguredTask extends DefaultTask { settings.setFloat(JUNIT_FAIL_ON_CVSS, config.junitFailOnCVSS.get()) settings.setBooleanIfNotNull(FAIL_ON_UNUSED_SUPPRESSION_RULE, config.failBuildOnUnusedSuppressionRule.getOrNull()) + + settings.setIntIfNotNull(CONNECTION_TIMEOUT, config.connectionTimeout.map { it.toMillis().intValue() }.getOrNull()) + settings.setIntIfNotNull(CONNECTION_READ_TIMEOUT, config.readTimeout.map { it.toMillis().intValue() }.getOrNull()) + settings.setBooleanIfNotNull(HOSTED_SUPPRESSIONS_ENABLED, config.hostedSuppressions.enabled.getOrNull()) settings.setBooleanIfNotNull(HOSTED_SUPPRESSIONS_FORCEUPDATE, config.hostedSuppressions.forceupdate.getOrNull()) settings.setStringIfNotNull(HOSTED_SUPPRESSIONS_URL, config.hostedSuppressions.url.getOrNull()) diff --git a/src/test/groovy/org/owasp/dependencycheck/gradle/DependencyCheckGradlePluginSpec.groovy b/src/test/groovy/org/owasp/dependencycheck/gradle/DependencyCheckGradlePluginSpec.groovy index cc0e8cd..29b13be 100644 --- a/src/test/groovy/org/owasp/dependencycheck/gradle/DependencyCheckGradlePluginSpec.groovy +++ b/src/test/groovy/org/owasp/dependencycheck/gradle/DependencyCheckGradlePluginSpec.groovy @@ -27,6 +27,8 @@ import org.owasp.dependencycheck.gradle.extension.OssIndexExtension import org.owasp.dependencycheck.utils.Settings import spock.lang.Specification +import java.time.Duration + import static org.owasp.dependencycheck.utils.Settings.KEYS.* @SuppressWarnings('ConfigurationAvoidance') @@ -80,28 +82,32 @@ class DependencyCheckGradlePluginSpec extends Specification { task.group == 'OWASP dependency-check' task.description == 'Identifies and reports known vulnerabilities (CVEs) in project dependencies.' - project.dependencyCheck.proxy.server.getOrNull() == null - project.dependencyCheck.proxy.port.getOrNull() == null - project.dependencyCheck.proxy.username.getOrNull() == null - project.dependencyCheck.proxy.password.getOrNull() == null - project.dependencyCheck.nvd.apiKey.getOrNull() == null - project.dependencyCheck.nvd.delay.getOrNull() == null - project.dependencyCheck.nvd.maxRetryCount.getOrNull() == null - project.dependencyCheck.outputDirectory.get().asFile == project.layout.buildDirectory.dir('reports').get().asFile - project.dependencyCheck.scanConfigurations.get() == [] - project.dependencyCheck.skipConfigurations.get() == [] - project.dependencyCheck.scanProjects.get() == [] - project.dependencyCheck.skipProjects.get() == [] - project.dependencyCheck.skipGroups.get() == [] - project.dependencyCheck.skipTestGroups.get() == true - project.dependencyCheck.suppressionFile.getOrNull() == null + with(project.dependencyCheck as DependencyCheckExtension) { + proxy.server.getOrNull() == null + proxy.port.getOrNull() == null + proxy.username.getOrNull() == null + proxy.password.getOrNull() == null + nvd.apiKey.getOrNull() == null + nvd.delay.getOrNull() == null + nvd.maxRetryCount.getOrNull() == null + outputDirectory.get().asFile == project.layout.buildDirectory.dir('reports').get().asFile + scanConfigurations.get() == [] + skipConfigurations.get() == [] + scanProjects.get() == [] + skipProjects.get() == [] + skipGroups.get() == [] + skipTestGroups.get() == true + suppressionFile.getOrNull() == null + connectionTimeout.getOrNull() == null + readTimeout.getOrNull() == null + } } def 'tasks use correct values when extension is used'() { given: def slackWebhookUrl = 'https://slack.com/webhook' when: - project.dependencyCheck { + project.getExtensions().findByType(DependencyCheckExtension).with { proxy.server = '127.0.0.1' proxy.port = 3128 proxy.username = 'proxyUsername' @@ -112,6 +118,9 @@ class DependencyCheckGradlePluginSpec extends Specification { nvd.delay = 5000 nvd.maxRetryCount = 20 + connectionTimeout = 3000L + readTimeout = Duration.ofMinutes(2) + hostedSuppressions.url = 'suppressionsurl' hostedSuppressions.validForHours = 5 hostedSuppressions.forceupdate = true @@ -161,44 +170,48 @@ class DependencyCheckGradlePluginSpec extends Specification { } then: - project.dependencyCheck.proxy.server.get() == '127.0.0.1' - project.dependencyCheck.proxy.port.get() == 3128 - project.dependencyCheck.proxy.username.get() == 'proxyUsername' - project.dependencyCheck.proxy.password.get() == 'proxyPassword' - project.dependencyCheck.proxy.nonProxyHosts.get() == ['localhost'] - - project.dependencyCheck.nvd.apiKey.get() == 'apiKey' - project.dependencyCheck.nvd.delay.get() == 5000 - project.dependencyCheck.nvd.maxRetryCount.get() == 20 - project.dependencyCheck.hostedSuppressions.url.get() == 'suppressionsurl' - project.dependencyCheck.hostedSuppressions.validForHours.get() == 5 - project.dependencyCheck.hostedSuppressions.forceupdate.get() == true - project.dependencyCheck.outputDirectory.get().asFile == project.file('outputDirectory') - project.dependencyCheck.scanConfigurations.get() == ['a'] - project.dependencyCheck.skipConfigurations.get() == ['b'] - project.dependencyCheck.scanProjects.get() == ['a'] - project.dependencyCheck.skipProjects.get() == ['b'] - project.dependencyCheck.skipGroups.get() == ['b'] - project.dependencyCheck.skipTestGroups.get() == false - project.dependencyCheck.suppressionFile.get() == './src/config/suppression.xml' - project.dependencyCheck.suppressionFiles.get().getAt(0) == './src/config/suppression1.xml' - project.dependencyCheck.suppressionFiles.get().getAt(1) == './src/config/suppression2.xml' - //project.dependencyCheck.suppressionFiles == ['./src/config/suppression1.xml', './src/config/suppression2.xml'] - project.dependencyCheck.suppressionFileUser.get() == 'suppressionFileUsername' - project.dependencyCheck.suppressionFilePassword.get() == 'suppressionFilePassword' - project.dependencyCheck.analyzers.artifactory.enabled.get() == true - project.dependencyCheck.analyzers.artifactory.url.get() == 'https://example.com/artifacgtory' - project.dependencyCheck.analyzers.artifactory.bearerToken.get() == 'abc123==' - project.dependencyCheck.analyzers.kev.enabled.get() == false - project.dependencyCheck.analyzers.kev.url.get() == "https://example.com" - project.dependencyCheck.analyzers.retirejs.filters.get() == ['filter1', 'filter2'] - project.dependencyCheck.analyzers.retirejs.filterNonVulnerable.get() == true - project.dependencyCheck.slack.enabled.get() == true - project.dependencyCheck.slack.webhookUrl.get() == slackWebhookUrl - project.dependencyCheck.additionalCpes.size() == 3 - project.dependencyCheck.additionalCpes.getByName('additional1').description.get() == 'Additional1' - project.dependencyCheck.additionalCpes.getByName('additional1').cpe.get() == 'cpe:2.3:a:aGroup1:aPackage1:123:*:*:*:*:*:*:*' - + with(project.dependencyCheck as DependencyCheckExtension) { + proxy.server.get() == '127.0.0.1' + proxy.port.get() == 3128 + proxy.username.get() == 'proxyUsername' + proxy.password.get() == 'proxyPassword' + proxy.nonProxyHosts.get() == ['localhost'] + + connectionTimeout.get() == Duration.ofMillis(3000) + readTimeout.get() == Duration.ofMinutes(2) + + nvd.apiKey.get() == 'apiKey' + nvd.delay.get() == 5000 + nvd.maxRetryCount.get() == 20 + hostedSuppressions.url.get() == 'suppressionsurl' + hostedSuppressions.validForHours.get() == 5 + hostedSuppressions.forceupdate.get() == true + outputDirectory.get().asFile == project.file('outputDirectory') + scanConfigurations.get() == ['a'] + skipConfigurations.get() == ['b'] + scanProjects.get() == ['a'] + skipProjects.get() == ['b'] + skipGroups.get() == ['b'] + skipTestGroups.get() == false + suppressionFile.get() == './src/config/suppression.xml' + suppressionFiles.get().getAt(0) == './src/config/suppression1.xml' + suppressionFiles.get().getAt(1) == './src/config/suppression2.xml' + //suppressionFiles == ['./src/config/suppression1.xml', './src/config/suppression2.xml'] + suppressionFileUser.get() == 'suppressionFileUsername' + suppressionFilePassword.get() == 'suppressionFilePassword' + analyzers.artifactory.enabled.get() == true + analyzers.artifactory.url.get() == 'https://example.com/artifacgtory' + analyzers.artifactory.bearerToken.get() == 'abc123==' + analyzers.kev.enabled.get() == false + analyzers.kev.url.get() == "https://example.com" + analyzers.retirejs.filters.get() == ['filter1', 'filter2'] + analyzers.retirejs.filterNonVulnerable.get() == true + slack.enabled.get() == true + slack.webhookUrl.get() == slackWebhookUrl + additionalCpes.size() == 3 + additionalCpes.getByName('additional1').description.get() == 'Additional1' + additionalCpes.getByName('additional1').cpe.get() == 'cpe:2.3:a:aGroup1:aPackage1:123:*:*:*:*:*:*:*' + } } def 'legacy nexus properties mapped to NexusExtension'() { From c26e3075de01e4ceea38a61b24074f407baac1b5 Mon Sep 17 00:00:00 2001 From: Chad Wilson <29788154+chadlwilson@users.noreply.github.com> Date: Fri, 8 May 2026 00:10:32 +0800 Subject: [PATCH 2/2] chore: bump version for dev Signed-off-by: Chad Wilson <29788154+chadlwilson@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ff5f90e..909a751 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -odc = '12.2.2' +odc = '12.2.3-SNAPSHOT' spock = '2.4-groovy-3.0' junit = '5.14.4'