+
+
+ A WebClient created without a WebClientOptions that explicitly sets an idle
+ timeout may hold open connections indefinitely. This can lead to resource exhaustion, increased latency,
+ and denial-of-service conditions if remote servers stop responding without closing their connections.
+
+
+
+
+
+ Always create a WebClientOptions instance, call setIdleTimeout on it with
+ an appropriate timeout value, and pass the options when calling WebClient.create.
+
+
+
+
+ Instead of creating a WebClient without an idle timeout, such as in the example below:
+
+
+
+
+ create a WebClientOptions with an explicit setIdleTimeout and pass it to
+ WebClient.create, as shown here:
+
+
+
+
+
+
+
+
+ Vert.x Web Client documentation
+
+
+
+
+ Vert.x WebClientOptions API
+
+
+
+
diff --git a/src/main/ql/WebClientMissingIdleTimeout.ql b/src/main/ql/WebClientMissingIdleTimeout.ql
new file mode 100644
index 0000000..e5a846f
--- /dev/null
+++ b/src/main/ql/WebClientMissingIdleTimeout.ql
@@ -0,0 +1,73 @@
+/**
+ * @name WebClient created without idle timeout
+ * @description A WebClient is created without a WebClientOptions that sets an explicit idle timeout.
+ * This can lead to connections hanging indefinitely, potentially exhausting resources.
+ * @kind problem
+ * @problem.severity warning
+ * @precision high
+ * @security-severity 5.0
+ * @id java/vertx/webclient-missing-idle-timeout
+ * @tags security java/vertx reliability
+ */
+
+import java
+
+class WebClient extends RefType {
+ WebClient() {
+ this.getASourceSupertype*().hasQualifiedName("io.vertx.ext.web.client", "WebClient")
+ }
+}
+
+class WebClientOptions extends RefType {
+ WebClientOptions() {
+ this.getASourceSupertype*().hasQualifiedName("io.vertx.ext.web.client", "WebClientOptions")
+ }
+}
+
+class WebClientCreateMethodAccess extends MethodAccess {
+ WebClientCreateMethodAccess() {
+ exists(Method m |
+ this.getMethod() = m and
+ m.getName() = "create" and
+ m.getDeclaringType() instanceof WebClient
+ )
+ }
+}
+
+class SetIdleTimeoutMethodAccess extends MethodAccess {
+ SetIdleTimeoutMethodAccess() {
+ exists(Method m |
+ this.getMethod() = m and
+ m.getName() = "setIdleTimeout" and
+ m.getDeclaringType() instanceof WebClientOptions
+ )
+ }
+}
+
+/**
+ * Holds if the given WebClientOptions variable has setIdleTimeout called on it.
+ */
+predicate optionsHasIdleTimeout(Variable optionsVar) {
+ exists(SetIdleTimeoutMethodAccess setCall |
+ setCall.getQualifier().(VarAccess).getVariable() = optionsVar
+ )
+}
+
+from WebClientCreateMethodAccess call
+where
+ not call.getEnclosingCallable().getDeclaringType() instanceof WebClient and
+ not call.getLocation().getFile().getRelativePath().matches("%/src/test/%") and
+ (
+ // No WebClientOptions argument at all
+ call.getNumArgument() < 2
+ or
+ // A WebClientOptions is passed but setIdleTimeout was never called on it
+ exists(Variable optionsVar |
+ call.getArgument(1).(VarAccess).getVariable() = optionsVar and
+ optionsVar.getType() instanceof WebClientOptions and
+ not optionsHasIdleTimeout(optionsVar)
+ )
+ )
+select
+ call,
+ "WebClient is created without a WebClientOptions that sets an explicit idle timeout."
diff --git a/src/main/ql/java/org/carlspring/security/vertx/webclient/InsecureWebClient.java b/src/main/ql/java/org/carlspring/security/vertx/webclient/InsecureWebClient.java
new file mode 100644
index 0000000..181fbe0
--- /dev/null
+++ b/src/main/ql/java/org/carlspring/security/vertx/webclient/InsecureWebClient.java
@@ -0,0 +1,23 @@
+package org.carlspring.security.vertx.webclient;
+
+import io.vertx.core.AbstractVerticle;
+import io.vertx.core.Vertx;
+import io.vertx.ext.web.client.WebClient;
+
+/**
+ * @author carlspring
+ */
+public class InsecureWebClient
+ extends AbstractVerticle
+{
+
+ @Override
+ public void start()
+ {
+ // Create a WebClient without any WebClientOptions (no idle timeout set)
+ WebClient client = WebClient.create(vertx);
+
+ // Use client...
+ }
+
+}
diff --git a/src/main/ql/java/org/carlspring/security/vertx/webclient/SecureWebClient.java b/src/main/ql/java/org/carlspring/security/vertx/webclient/SecureWebClient.java
new file mode 100644
index 0000000..c17f0f3
--- /dev/null
+++ b/src/main/ql/java/org/carlspring/security/vertx/webclient/SecureWebClient.java
@@ -0,0 +1,31 @@
+package org.carlspring.security.vertx.webclient;
+
+import io.vertx.core.AbstractVerticle;
+import io.vertx.core.Vertx;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.client.WebClientOptions;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author carlspring
+ */
+public class SecureWebClient
+ extends AbstractVerticle
+{
+
+ @Override
+ public void start()
+ {
+ // Create a WebClientOptions with an explicit idle timeout
+ WebClientOptions options = new WebClientOptions()
+ .setIdleTimeout(30)
+ .setIdleTimeoutUnit(TimeUnit.SECONDS);
+
+ // Create a WebClient with the options
+ WebClient client = WebClient.create(vertx, options);
+
+ // Use client...
+ }
+
+}
diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/InsecureWebClient.java b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/InsecureWebClient.java
new file mode 100644
index 0000000..7891860
--- /dev/null
+++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/InsecureWebClient.java
@@ -0,0 +1,22 @@
+package org.carlspring.security.vertx.webclient;
+
+import io.vertx.core.AbstractVerticle;
+import io.vertx.ext.web.client.WebClient;
+
+/**
+ * @author carlspring
+ */
+public class InsecureWebClient
+ extends AbstractVerticle
+{
+
+ @Override
+ public void start()
+ {
+ // Create a WebClient without any WebClientOptions (no idle timeout set)
+ WebClient client = WebClient.create(vertx);
+
+ // Use client...
+ }
+
+}
diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/SecureWebClient.java b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/SecureWebClient.java
new file mode 100644
index 0000000..9396f3d
--- /dev/null
+++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/SecureWebClient.java
@@ -0,0 +1,30 @@
+package org.carlspring.security.vertx.webclient;
+
+import io.vertx.core.AbstractVerticle;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.client.WebClientOptions;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author carlspring
+ */
+public class SecureWebClient
+ extends AbstractVerticle
+{
+
+ @Override
+ public void start()
+ {
+ // Create a WebClientOptions with an explicit idle timeout
+ WebClientOptions options = new WebClientOptions()
+ .setIdleTimeout(30)
+ .setIdleTimeoutUnit(TimeUnit.SECONDS);
+
+ // Create a WebClient with the options
+ WebClient client = WebClient.create(vertx, options);
+
+ // Use client...
+ }
+
+}
diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.expected b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.expected
new file mode 100644
index 0000000..ab00300
--- /dev/null
+++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.expected
@@ -0,0 +1 @@
+| InsecureWebClient.java:17:28:17:50 | create(...) | WebClient is created without a WebClientOptions that sets an explicit idle timeout. |
diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.qlref b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.qlref
new file mode 100644
index 0000000..1d3af31
--- /dev/null
+++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/WebClientMissingIdleTimeout.qlref
@@ -0,0 +1 @@
+WebClientMissingIdleTimeout.ql
diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/options b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/options
new file mode 100644
index 0000000..a16b40d
--- /dev/null
+++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../stubs/ -source 17
diff --git a/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/pom.xml b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/pom.xml
new file mode 100644
index 0000000..fa26a06
--- /dev/null
+++ b/src/test/ql/test/query-tests/WebClientMissingIdleTimeout/pom.xml
@@ -0,0 +1,65 @@
+