From 0df87c1b19f2c5297372408ca20df7a2361cfd84 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 24 Jun 2026 12:12:02 +0200 Subject: [PATCH 1/7] feat(appsec): fire server.request.body.files_content for Vert.x 3/4/5 Extends RoutingContextFilenamesAdvice in vertx-web-3.4, vertx-web-4.0, and vertx-web-5.0 to also collect and fire the requestFilesContent() IG callback alongside the existing requestFilesFilenames() callback. Content is read from disk using FileInputStream and MultipartContentDecoder since Vert.x BodyHandler always persists uploads to disk. The filenames callback fires first; the content callback only fires if filenames did not trigger a block (sequential guard). Muzzle references are updated to include uploadedFileName() and contentType() on FileUpload. Tests enable testBodyFilesContent() for all three Vert.x versions. --- .../server/RoutingContextFilenamesAdvice.java | 75 ++++++++++++++++--- .../RoutingContextImplInstrumentation.java | 2 + .../server/VertxHttpServerForkedTest.groovy | 5 ++ .../server/VertxHttpServerForkedTest.groovy | 5 ++ .../server/RoutingContextFilenamesAdvice.java | 75 ++++++++++++++++--- .../RoutingContextImplInstrumentation.java | 2 + .../server/VertxHttpServerForkedTest.groovy | 5 ++ .../server/RoutingContextFilenamesAdvice.java | 75 ++++++++++++++++--- .../RoutingContextImplInstrumentation.java | 2 + .../server/VertxHttpServerForkedTest.groovy | 5 ++ 10 files changed, 215 insertions(+), 36 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java index 4ab0bbec651..8d13ae6c7bd 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java @@ -5,14 +5,17 @@ import datadog.appsec.api.blocking.BlockingException; import datadog.trace.advice.ActiveRequestContext; import datadog.trace.advice.RequiresRequestContext; +import datadog.trace.api.Config; import datadog.trace.api.gateway.BlockResponseFunction; import datadog.trace.api.gateway.CallbackProvider; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.http.MultipartContentDecoder; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.vertx.ext.web.FileUpload; +import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -38,35 +41,83 @@ static void after( return; } - List filenames = new ArrayList<>(uploads.size()); + CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); + BiFunction, Flow> filenamesCb = + cbp.getCallback(EVENTS.requestFilesFilenames()); + BiFunction, Flow> contentCb = + cbp.getCallback(EVENTS.requestFilesContent()); + if (filenamesCb == null && contentCb == null) { + return; + } + + int maxFiles = Config.get().getAppSecMaxFileContentCount(); + int maxBytes = Config.get().getAppSecMaxFileContentBytes(); + List filenames = null; + List filesContent = null; + for (FileUpload upload : uploads) { String name = upload.fileName(); - if (name != null && !name.isEmpty()) { + if (filenamesCb != null && name != null && !name.isEmpty()) { + if (filenames == null) { + filenames = new ArrayList<>(); + } filenames.add(name); } + if (contentCb != null + && maxFiles > 0 + && (filesContent == null || filesContent.size() < maxFiles)) { + if (filesContent == null) { + filesContent = new ArrayList<>(); + } + filesContent.add(readUploadContent(upload, maxBytes)); + } } - if (filenames.isEmpty()) { - return; + + if (filenamesCb != null && filenames != null) { + throwable = + commitBlockingResponse( + filenamesCb, reqCtx, filenames, "Blocked request (multipart file upload)"); } - CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); - BiFunction, Flow> cb = - cbp.getCallback(EVENTS.requestFilesFilenames()); - if (cb == null) { + if (throwable != null) { return; } - Flow flow = cb.apply(reqCtx, filenames); + if (contentCb != null && filesContent != null) { + throwable = + commitBlockingResponse(contentCb, reqCtx, filesContent, "Blocked request (file content)"); + } + } + + private static BlockingException commitBlockingResponse( + BiFunction, Flow> cb, + RequestContext reqCtx, + List data, + String reason) { + Flow flow = cb.apply(reqCtx, data); Flow.Action action = flow.getAction(); if (action instanceof Flow.Action.RequestBlockingAction) { BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); if (brf != null) { brf.tryCommitBlockingResponse( reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); - if (throwable == null) { - throwable = new BlockingException("Blocked request (multipart file upload)"); - } + return new BlockingException(reason); + } + } + return null; + } + + private static String readUploadContent(FileUpload upload, int maxBytes) { + try { + String path = upload.uploadedFileName(); + if (path == null || path.isEmpty()) { + return ""; + } + try (FileInputStream fis = new FileInputStream(path)) { + return MultipartContentDecoder.readInputStream(fis, maxBytes, upload.contentType()); } + } catch (Exception ignored) { + return ""; } } } diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java index e13eef7f18e..2f9bfe5b6ed 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java @@ -18,6 +18,8 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec private static final Reference FILE_UPLOAD_REF = new Reference.Builder("io.vertx.ext.web.FileUpload") .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") + .withMethod(new String[0], 0, "uploadedFileName", "Ljava/lang/String;") + .withMethod(new String[0], 0, "contentType", "Ljava/lang/String;") .build(); public RoutingContextImplInstrumentation() { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy index c64753affe7..b9491cf1757 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -87,6 +87,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilesContent() { + true + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy index 8f276d08f23..08f5f35b893 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy @@ -82,6 +82,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilesContent() { + true + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java index e9ac149e7bb..94f1fd79012 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java @@ -5,14 +5,17 @@ import datadog.appsec.api.blocking.BlockingException; import datadog.trace.advice.ActiveRequestContext; import datadog.trace.advice.RequiresRequestContext; +import datadog.trace.api.Config; import datadog.trace.api.gateway.BlockResponseFunction; import datadog.trace.api.gateway.CallbackProvider; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.http.MultipartContentDecoder; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.vertx.ext.web.FileUpload; +import java.io.FileInputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -38,35 +41,83 @@ static void after( return; } - List filenames = new ArrayList<>(uploads.size()); + CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); + BiFunction, Flow> filenamesCb = + cbp.getCallback(EVENTS.requestFilesFilenames()); + BiFunction, Flow> contentCb = + cbp.getCallback(EVENTS.requestFilesContent()); + if (filenamesCb == null && contentCb == null) { + return; + } + + int maxFiles = Config.get().getAppSecMaxFileContentCount(); + int maxBytes = Config.get().getAppSecMaxFileContentBytes(); + List filenames = null; + List filesContent = null; + for (FileUpload upload : uploads) { String name = upload.fileName(); - if (name != null && !name.isEmpty()) { + if (filenamesCb != null && name != null && !name.isEmpty()) { + if (filenames == null) { + filenames = new ArrayList<>(); + } filenames.add(name); } + if (contentCb != null + && maxFiles > 0 + && (filesContent == null || filesContent.size() < maxFiles)) { + if (filesContent == null) { + filesContent = new ArrayList<>(); + } + filesContent.add(readUploadContent(upload, maxBytes)); + } } - if (filenames.isEmpty()) { - return; + + if (filenamesCb != null && filenames != null) { + throwable = + commitBlockingResponse( + filenamesCb, reqCtx, filenames, "Blocked request (multipart file upload)"); } - CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); - BiFunction, Flow> cb = - cbp.getCallback(EVENTS.requestFilesFilenames()); - if (cb == null) { + if (throwable != null) { return; } - Flow flow = cb.apply(reqCtx, filenames); + if (contentCb != null && filesContent != null) { + throwable = + commitBlockingResponse(contentCb, reqCtx, filesContent, "Blocked request (file content)"); + } + } + + private static BlockingException commitBlockingResponse( + BiFunction, Flow> cb, + RequestContext reqCtx, + List data, + String reason) { + Flow flow = cb.apply(reqCtx, data); Flow.Action action = flow.getAction(); if (action instanceof Flow.Action.RequestBlockingAction) { BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); if (brf != null) { brf.tryCommitBlockingResponse( reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); - if (throwable == null) { - throwable = new BlockingException("Blocked request (multipart file upload)"); - } + return new BlockingException(reason); + } + } + return null; + } + + private static String readUploadContent(FileUpload upload, int maxBytes) { + try { + String path = upload.uploadedFileName(); + if (path == null || path.isEmpty()) { + return ""; + } + try (FileInputStream fis = new FileInputStream(path)) { + return MultipartContentDecoder.readInputStream(fis, maxBytes, upload.contentType()); } + } catch (Exception ignored) { + return ""; } } } diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java index ae863b6ec09..b6527efb2c8 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java @@ -21,6 +21,8 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec private static final Reference FILE_UPLOAD_REF = new Reference.Builder("io.vertx.ext.web.FileUpload") .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") + .withMethod(new String[0], 0, "uploadedFileName", "Ljava/lang/String;") + .withMethod(new String[0], 0, "contentType", "Ljava/lang/String;") .build(); public RoutingContextImplInstrumentation() { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 1d2f4ec9971..0d8fe3a7500 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -83,6 +83,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilesContent() { + true + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java index cf4aa6482f6..b16625c3645 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java @@ -5,14 +5,17 @@ import datadog.appsec.api.blocking.BlockingException; import datadog.trace.advice.ActiveRequestContext; import datadog.trace.advice.RequiresRequestContext; +import datadog.trace.api.Config; import datadog.trace.api.gateway.BlockResponseFunction; import datadog.trace.api.gateway.CallbackProvider; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; +import datadog.trace.api.http.MultipartContentDecoder; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.vertx.ext.web.FileUpload; +import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; @@ -37,35 +40,83 @@ static void after( return; } - List filenames = new ArrayList<>(uploads.size()); + CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); + BiFunction, Flow> filenamesCb = + cbp.getCallback(EVENTS.requestFilesFilenames()); + BiFunction, Flow> contentCb = + cbp.getCallback(EVENTS.requestFilesContent()); + if (filenamesCb == null && contentCb == null) { + return; + } + + int maxFiles = Config.get().getAppSecMaxFileContentCount(); + int maxBytes = Config.get().getAppSecMaxFileContentBytes(); + List filenames = null; + List filesContent = null; + for (FileUpload upload : uploads) { String name = upload.fileName(); - if (name != null && !name.isEmpty()) { + if (filenamesCb != null && name != null && !name.isEmpty()) { + if (filenames == null) { + filenames = new ArrayList<>(); + } filenames.add(name); } + if (contentCb != null + && maxFiles > 0 + && (filesContent == null || filesContent.size() < maxFiles)) { + if (filesContent == null) { + filesContent = new ArrayList<>(); + } + filesContent.add(readUploadContent(upload, maxBytes)); + } } - if (filenames.isEmpty()) { - return; + + if (filenamesCb != null && filenames != null) { + throwable = + commitBlockingResponse( + filenamesCb, reqCtx, filenames, "Blocked request (multipart file upload)"); } - CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); - BiFunction, Flow> cb = - cbp.getCallback(EVENTS.requestFilesFilenames()); - if (cb == null) { + if (throwable != null) { return; } - Flow flow = cb.apply(reqCtx, filenames); + if (contentCb != null && filesContent != null) { + throwable = + commitBlockingResponse(contentCb, reqCtx, filesContent, "Blocked request (file content)"); + } + } + + private static BlockingException commitBlockingResponse( + BiFunction, Flow> cb, + RequestContext reqCtx, + List data, + String reason) { + Flow flow = cb.apply(reqCtx, data); Flow.Action action = flow.getAction(); if (action instanceof Flow.Action.RequestBlockingAction) { BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); if (brf != null) { brf.tryCommitBlockingResponse( reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); - if (throwable == null) { - throwable = new BlockingException("Blocked request (multipart file upload)"); - } + return new BlockingException(reason); + } + } + return null; + } + + private static String readUploadContent(FileUpload upload, int maxBytes) { + try { + String path = upload.uploadedFileName(); + if (path == null || path.isEmpty()) { + return ""; + } + try (FileInputStream fis = new FileInputStream(path)) { + return MultipartContentDecoder.readInputStream(fis, maxBytes, upload.contentType()); } + } catch (Exception ignored) { + return ""; } } } diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java index 93ff71d4d82..5af76cc67d9 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java @@ -15,6 +15,8 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec private static final Reference FILE_UPLOAD_REF = new Reference.Builder("io.vertx.ext.web.FileUpload") .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") + .withMethod(new String[0], 0, "uploadedFileName", "Ljava/lang/String;") + .withMethod(new String[0], 0, "contentType", "Ljava/lang/String;") .build(); public RoutingContextImplInstrumentation() { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 2be39db6f4a..ff5b2b2f8c5 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -90,6 +90,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilesContent() { + true + } + @Override boolean testBodyJson() { true From 54d524b63e9831ca712bdaf42a78ed9600129622 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 24 Jun 2026 13:00:57 +0200 Subject: [PATCH 2/7] fix(appsec): use Vert.x FileUpload.charSet() when decoding file content FileUpload.contentType() in Vert.x returns only the MIME type without the charset parameter. charSet() exposes it separately. Combine both into a full content-type string before passing to MultipartContentDecoder.readInputStream so extractCharset finds the declared charset instead of falling back to the JVM default. Add charSet() to FILE_UPLOAD_REF muzzle references in all three Vert.x modules. --- .../vertx_3_4/server/RoutingContextFilenamesAdvice.java | 7 ++++++- .../server/RoutingContextImplInstrumentation.java | 1 + .../vertx_4_0/server/RoutingContextFilenamesAdvice.java | 7 ++++++- .../server/RoutingContextImplInstrumentation.java | 1 + .../vertx_5_0/server/RoutingContextFilenamesAdvice.java | 7 ++++++- .../server/RoutingContextImplInstrumentation.java | 1 + 6 files changed, 21 insertions(+), 3 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java index 8d13ae6c7bd..d72ea900370 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java @@ -113,8 +113,13 @@ private static String readUploadContent(FileUpload upload, int maxBytes) { if (path == null || path.isEmpty()) { return ""; } + String charSet = upload.charSet(); + String contentType = + charSet != null && !charSet.isEmpty() + ? upload.contentType() + "; charset=" + charSet + : upload.contentType(); try (FileInputStream fis = new FileInputStream(path)) { - return MultipartContentDecoder.readInputStream(fis, maxBytes, upload.contentType()); + return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); } } catch (Exception ignored) { return ""; diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java index 2f9bfe5b6ed..d0f9f589bb8 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java @@ -20,6 +20,7 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") .withMethod(new String[0], 0, "uploadedFileName", "Ljava/lang/String;") .withMethod(new String[0], 0, "contentType", "Ljava/lang/String;") + .withMethod(new String[0], 0, "charSet", "Ljava/lang/String;") .build(); public RoutingContextImplInstrumentation() { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java index 94f1fd79012..58e053890db 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java @@ -113,8 +113,13 @@ private static String readUploadContent(FileUpload upload, int maxBytes) { if (path == null || path.isEmpty()) { return ""; } + String charSet = upload.charSet(); + String contentType = + charSet != null && !charSet.isEmpty() + ? upload.contentType() + "; charset=" + charSet + : upload.contentType(); try (FileInputStream fis = new FileInputStream(path)) { - return MultipartContentDecoder.readInputStream(fis, maxBytes, upload.contentType()); + return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); } } catch (Exception ignored) { return ""; diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java index b6527efb2c8..3a3511dae63 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java @@ -23,6 +23,7 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") .withMethod(new String[0], 0, "uploadedFileName", "Ljava/lang/String;") .withMethod(new String[0], 0, "contentType", "Ljava/lang/String;") + .withMethod(new String[0], 0, "charSet", "Ljava/lang/String;") .build(); public RoutingContextImplInstrumentation() { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java index b16625c3645..3f179b54aa2 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java @@ -112,8 +112,13 @@ private static String readUploadContent(FileUpload upload, int maxBytes) { if (path == null || path.isEmpty()) { return ""; } + String charSet = upload.charSet(); + String contentType = + charSet != null && !charSet.isEmpty() + ? upload.contentType() + "; charset=" + charSet + : upload.contentType(); try (FileInputStream fis = new FileInputStream(path)) { - return MultipartContentDecoder.readInputStream(fis, maxBytes, upload.contentType()); + return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); } } catch (Exception ignored) { return ""; diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java index 5af76cc67d9..5f4c1eba306 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java @@ -17,6 +17,7 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") .withMethod(new String[0], 0, "uploadedFileName", "Ljava/lang/String;") .withMethod(new String[0], 0, "contentType", "Ljava/lang/String;") + .withMethod(new String[0], 0, "charSet", "Ljava/lang/String;") .build(); public RoutingContextImplInstrumentation() { From def3020b026e990b38c20e7f60a638273048402a Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 24 Jun 2026 13:40:29 +0200 Subject: [PATCH 3/7] fix(appsec): extract FileUploadHelper to fix muzzle validation for Vert.x 3/4/5 Private static helpers in @Advice classes generate INVOKESTATIC references to the advice class itself. Muzzle resolves the advice class by name and validates all its INVOKESTATIC targets against the library classpath, which does not contain our advice classes, causing "Missing class" failures. Extract readUploadContent and commitBlockingResponse into FileUploadHelper per module and declare each in helperClassNames() so ByteBuddy injects them into the target classloader and muzzle validates their references normally. --- .../vertx_3_4/server/FileUploadHelper.java | 51 +++++++++++++++++++ .../server/RoutingContextFilenamesAdvice.java | 48 ++--------------- .../RoutingContextImplInstrumentation.java | 5 ++ .../vertx_4_0/server/FileUploadHelper.java | 51 +++++++++++++++++++ .../server/RoutingContextFilenamesAdvice.java | 48 ++--------------- .../RoutingContextImplInstrumentation.java | 5 ++ .../vertx_5_0/server/FileUploadHelper.java | 51 +++++++++++++++++++ .../server/RoutingContextFilenamesAdvice.java | 48 ++--------------- .../RoutingContextImplInstrumentation.java | 5 ++ 9 files changed, 180 insertions(+), 132 deletions(-) create mode 100644 dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java create mode 100644 dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java create mode 100644 dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java new file mode 100644 index 00000000000..530177ecd92 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java @@ -0,0 +1,51 @@ +package datadog.trace.instrumentation.vertx_3_4.server; + +import datadog.appsec.api.blocking.BlockingException; +import datadog.trace.api.gateway.BlockResponseFunction; +import datadog.trace.api.gateway.Flow; +import datadog.trace.api.gateway.RequestContext; +import datadog.trace.api.http.MultipartContentDecoder; +import io.vertx.ext.web.FileUpload; +import java.io.FileInputStream; +import java.util.List; +import java.util.function.BiFunction; + +class FileUploadHelper { + + static BlockingException commitBlockingResponse( + BiFunction, Flow> cb, + RequestContext reqCtx, + List data, + String reason) { + Flow flow = cb.apply(reqCtx, data); + Flow.Action action = flow.getAction(); + if (action instanceof Flow.Action.RequestBlockingAction) { + BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); + if (brf != null) { + brf.tryCommitBlockingResponse( + reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); + return new BlockingException(reason); + } + } + return null; + } + + static String readUploadContent(FileUpload upload, int maxBytes) { + try { + String path = upload.uploadedFileName(); + if (path == null || path.isEmpty()) { + return ""; + } + String charSet = upload.charSet(); + String contentType = + charSet != null && !charSet.isEmpty() + ? upload.contentType() + "; charset=" + charSet + : upload.contentType(); + try (FileInputStream fis = new FileInputStream(path)) { + return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); + } + } catch (Exception ignored) { + return ""; + } + } +} diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java index d72ea900370..c1abb2e0a97 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java @@ -2,20 +2,16 @@ import static datadog.trace.api.gateway.Events.EVENTS; -import datadog.appsec.api.blocking.BlockingException; import datadog.trace.advice.ActiveRequestContext; import datadog.trace.advice.RequiresRequestContext; import datadog.trace.api.Config; -import datadog.trace.api.gateway.BlockResponseFunction; import datadog.trace.api.gateway.CallbackProvider; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; -import datadog.trace.api.http.MultipartContentDecoder; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.vertx.ext.web.FileUpload; -import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -69,13 +65,13 @@ static void after( if (filesContent == null) { filesContent = new ArrayList<>(); } - filesContent.add(readUploadContent(upload, maxBytes)); + filesContent.add(FileUploadHelper.readUploadContent(upload, maxBytes)); } } if (filenamesCb != null && filenames != null) { throwable = - commitBlockingResponse( + FileUploadHelper.commitBlockingResponse( filenamesCb, reqCtx, filenames, "Blocked request (multipart file upload)"); } @@ -85,44 +81,8 @@ static void after( if (contentCb != null && filesContent != null) { throwable = - commitBlockingResponse(contentCb, reqCtx, filesContent, "Blocked request (file content)"); - } - } - - private static BlockingException commitBlockingResponse( - BiFunction, Flow> cb, - RequestContext reqCtx, - List data, - String reason) { - Flow flow = cb.apply(reqCtx, data); - Flow.Action action = flow.getAction(); - if (action instanceof Flow.Action.RequestBlockingAction) { - BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); - if (brf != null) { - brf.tryCommitBlockingResponse( - reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); - return new BlockingException(reason); - } - } - return null; - } - - private static String readUploadContent(FileUpload upload, int maxBytes) { - try { - String path = upload.uploadedFileName(); - if (path == null || path.isEmpty()) { - return ""; - } - String charSet = upload.charSet(); - String contentType = - charSet != null && !charSet.isEmpty() - ? upload.contentType() + "; charset=" + charSet - : upload.contentType(); - try (FileInputStream fis = new FileInputStream(path)) { - return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); - } - } catch (Exception ignored) { - return ""; + FileUploadHelper.commitBlockingResponse( + contentCb, reqCtx, filesContent, "Blocked request (file content)"); } } } diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java index d0f9f589bb8..af8ee74bf18 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextImplInstrumentation.java @@ -27,6 +27,11 @@ public RoutingContextImplInstrumentation() { super("vertx", "vertx-3.4"); } + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".FileUploadHelper"}; + } + @Override public String instrumentedType() { return "io.vertx.ext.web.impl.RoutingContextImpl"; diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java new file mode 100644 index 00000000000..de9617ae802 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java @@ -0,0 +1,51 @@ +package datadog.trace.instrumentation.vertx_4_0.server; + +import datadog.appsec.api.blocking.BlockingException; +import datadog.trace.api.gateway.BlockResponseFunction; +import datadog.trace.api.gateway.Flow; +import datadog.trace.api.gateway.RequestContext; +import datadog.trace.api.http.MultipartContentDecoder; +import io.vertx.ext.web.FileUpload; +import java.io.FileInputStream; +import java.util.List; +import java.util.function.BiFunction; + +class FileUploadHelper { + + static BlockingException commitBlockingResponse( + BiFunction, Flow> cb, + RequestContext reqCtx, + List data, + String reason) { + Flow flow = cb.apply(reqCtx, data); + Flow.Action action = flow.getAction(); + if (action instanceof Flow.Action.RequestBlockingAction) { + BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); + if (brf != null) { + brf.tryCommitBlockingResponse( + reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); + return new BlockingException(reason); + } + } + return null; + } + + static String readUploadContent(FileUpload upload, int maxBytes) { + try { + String path = upload.uploadedFileName(); + if (path == null || path.isEmpty()) { + return ""; + } + String charSet = upload.charSet(); + String contentType = + charSet != null && !charSet.isEmpty() + ? upload.contentType() + "; charset=" + charSet + : upload.contentType(); + try (FileInputStream fis = new FileInputStream(path)) { + return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); + } + } catch (Exception ignored) { + return ""; + } + } +} diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java index 58e053890db..ce4cd89ed71 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java @@ -2,20 +2,16 @@ import static datadog.trace.api.gateway.Events.EVENTS; -import datadog.appsec.api.blocking.BlockingException; import datadog.trace.advice.ActiveRequestContext; import datadog.trace.advice.RequiresRequestContext; import datadog.trace.api.Config; -import datadog.trace.api.gateway.BlockResponseFunction; import datadog.trace.api.gateway.CallbackProvider; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; -import datadog.trace.api.http.MultipartContentDecoder; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.vertx.ext.web.FileUpload; -import java.io.FileInputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -69,13 +65,13 @@ static void after( if (filesContent == null) { filesContent = new ArrayList<>(); } - filesContent.add(readUploadContent(upload, maxBytes)); + filesContent.add(FileUploadHelper.readUploadContent(upload, maxBytes)); } } if (filenamesCb != null && filenames != null) { throwable = - commitBlockingResponse( + FileUploadHelper.commitBlockingResponse( filenamesCb, reqCtx, filenames, "Blocked request (multipart file upload)"); } @@ -85,44 +81,8 @@ static void after( if (contentCb != null && filesContent != null) { throwable = - commitBlockingResponse(contentCb, reqCtx, filesContent, "Blocked request (file content)"); - } - } - - private static BlockingException commitBlockingResponse( - BiFunction, Flow> cb, - RequestContext reqCtx, - List data, - String reason) { - Flow flow = cb.apply(reqCtx, data); - Flow.Action action = flow.getAction(); - if (action instanceof Flow.Action.RequestBlockingAction) { - BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); - if (brf != null) { - brf.tryCommitBlockingResponse( - reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); - return new BlockingException(reason); - } - } - return null; - } - - private static String readUploadContent(FileUpload upload, int maxBytes) { - try { - String path = upload.uploadedFileName(); - if (path == null || path.isEmpty()) { - return ""; - } - String charSet = upload.charSet(); - String contentType = - charSet != null && !charSet.isEmpty() - ? upload.contentType() + "; charset=" + charSet - : upload.contentType(); - try (FileInputStream fis = new FileInputStream(path)) { - return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); - } - } catch (Exception ignored) { - return ""; + FileUploadHelper.commitBlockingResponse( + contentCb, reqCtx, filesContent, "Blocked request (file content)"); } } } diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java index 3a3511dae63..97623985225 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextImplInstrumentation.java @@ -30,6 +30,11 @@ public RoutingContextImplInstrumentation() { super("vertx", "vertx-4.0"); } + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".FileUploadHelper"}; + } + @Override public Reference[] additionalMuzzleReferences() { return new Reference[] {VertxVersionMatcher.HTTP_1X_SERVER_RESPONSE, FILE_UPLOAD_REF}; diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java new file mode 100644 index 00000000000..545bd69af02 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java @@ -0,0 +1,51 @@ +package datadog.trace.instrumentation.vertx_5_0.server; + +import datadog.appsec.api.blocking.BlockingException; +import datadog.trace.api.gateway.BlockResponseFunction; +import datadog.trace.api.gateway.Flow; +import datadog.trace.api.gateway.RequestContext; +import datadog.trace.api.http.MultipartContentDecoder; +import io.vertx.ext.web.FileUpload; +import java.io.FileInputStream; +import java.util.List; +import java.util.function.BiFunction; + +class FileUploadHelper { + + static BlockingException commitBlockingResponse( + BiFunction, Flow> cb, + RequestContext reqCtx, + List data, + String reason) { + Flow flow = cb.apply(reqCtx, data); + Flow.Action action = flow.getAction(); + if (action instanceof Flow.Action.RequestBlockingAction) { + BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); + if (brf != null) { + brf.tryCommitBlockingResponse( + reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); + return new BlockingException(reason); + } + } + return null; + } + + static String readUploadContent(FileUpload upload, int maxBytes) { + try { + String path = upload.uploadedFileName(); + if (path == null || path.isEmpty()) { + return ""; + } + String charSet = upload.charSet(); + String contentType = + charSet != null && !charSet.isEmpty() + ? upload.contentType() + "; charset=" + charSet + : upload.contentType(); + try (FileInputStream fis = new FileInputStream(path)) { + return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); + } + } catch (Exception ignored) { + return ""; + } + } +} diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java index 3f179b54aa2..2d8a77e342c 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java @@ -2,20 +2,16 @@ import static datadog.trace.api.gateway.Events.EVENTS; -import datadog.appsec.api.blocking.BlockingException; import datadog.trace.advice.ActiveRequestContext; import datadog.trace.advice.RequiresRequestContext; import datadog.trace.api.Config; -import datadog.trace.api.gateway.BlockResponseFunction; import datadog.trace.api.gateway.CallbackProvider; import datadog.trace.api.gateway.Flow; import datadog.trace.api.gateway.RequestContext; import datadog.trace.api.gateway.RequestContextSlot; -import datadog.trace.api.http.MultipartContentDecoder; import datadog.trace.bootstrap.CallDepthThreadLocalMap; import datadog.trace.bootstrap.instrumentation.api.AgentTracer; import io.vertx.ext.web.FileUpload; -import java.io.FileInputStream; import java.util.ArrayList; import java.util.List; import java.util.function.BiFunction; @@ -68,13 +64,13 @@ static void after( if (filesContent == null) { filesContent = new ArrayList<>(); } - filesContent.add(readUploadContent(upload, maxBytes)); + filesContent.add(FileUploadHelper.readUploadContent(upload, maxBytes)); } } if (filenamesCb != null && filenames != null) { throwable = - commitBlockingResponse( + FileUploadHelper.commitBlockingResponse( filenamesCb, reqCtx, filenames, "Blocked request (multipart file upload)"); } @@ -84,44 +80,8 @@ static void after( if (contentCb != null && filesContent != null) { throwable = - commitBlockingResponse(contentCb, reqCtx, filesContent, "Blocked request (file content)"); - } - } - - private static BlockingException commitBlockingResponse( - BiFunction, Flow> cb, - RequestContext reqCtx, - List data, - String reason) { - Flow flow = cb.apply(reqCtx, data); - Flow.Action action = flow.getAction(); - if (action instanceof Flow.Action.RequestBlockingAction) { - BlockResponseFunction brf = reqCtx.getBlockResponseFunction(); - if (brf != null) { - brf.tryCommitBlockingResponse( - reqCtx.getTraceSegment(), (Flow.Action.RequestBlockingAction) action); - return new BlockingException(reason); - } - } - return null; - } - - private static String readUploadContent(FileUpload upload, int maxBytes) { - try { - String path = upload.uploadedFileName(); - if (path == null || path.isEmpty()) { - return ""; - } - String charSet = upload.charSet(); - String contentType = - charSet != null && !charSet.isEmpty() - ? upload.contentType() + "; charset=" + charSet - : upload.contentType(); - try (FileInputStream fis = new FileInputStream(path)) { - return MultipartContentDecoder.readInputStream(fis, maxBytes, contentType); - } - } catch (Exception ignored) { - return ""; + FileUploadHelper.commitBlockingResponse( + contentCb, reqCtx, filesContent, "Blocked request (file content)"); } } } diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java index 5f4c1eba306..627d7798f9e 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java @@ -24,6 +24,11 @@ public RoutingContextImplInstrumentation() { super("vertx", "vertx-5.0"); } + @Override + public String[] helperClassNames() { + return new String[] {packageName + ".FileUploadHelper"}; + } + @Override public String instrumentedType() { return "io.vertx.ext.web.impl.RoutingContextImpl"; From 91bec07a96013eaf659d820f83ec8c4f4c0181bd Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 24 Jun 2026 15:02:30 +0200 Subject: [PATCH 4/7] fix: make FileUploadHelper public and fix max files limit test for Vert.x 3/4/5 Helper classes injected via helperClassNames() are loaded into the app classloader. When package-private, they cause IllegalAccessError from instrumented classes in different packages within the same unnamed module. The max files limit test also needed an override because Vert.x 3.4 returns fileUploads() as a HashSet, whose iteration order depends on identity hash codes. Adding helperClassNames() shifts object allocation counts and therefore hash codes, changing which file is excluded. The override checks the count of inspected files instead of which specific file was excluded. --- .../vertx_3_4/server/FileUploadHelper.java | 6 ++-- .../server/VertxHttpServerForkedTest.groovy | 32 +++++++++++++++++++ .../server/VertxHttpServerForkedTest.groovy | 32 +++++++++++++++++++ .../vertx_4_0/server/FileUploadHelper.java | 6 ++-- .../server/VertxHttpServerForkedTest.groovy | 32 +++++++++++++++++++ .../vertx_5_0/server/FileUploadHelper.java | 6 ++-- .../server/VertxHttpServerForkedTest.groovy | 32 +++++++++++++++++++ 7 files changed, 137 insertions(+), 9 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java index 530177ecd92..913003e8c28 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/FileUploadHelper.java @@ -10,9 +10,9 @@ import java.util.List; import java.util.function.BiFunction; -class FileUploadHelper { +public class FileUploadHelper { - static BlockingException commitBlockingResponse( + public static BlockingException commitBlockingResponse( BiFunction, Flow> cb, RequestContext reqCtx, List data, @@ -30,7 +30,7 @@ static BlockingException commitBlockingResponse( return null; } - static String readUploadContent(FileUpload upload, int maxBytes) { + public static String readUploadContent(FileUpload upload, int maxBytes) { try { String path = upload.uploadedFileName(); if (path == null || path.isEmpty()) { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy index b9491cf1757..fb294be5ef4 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -1,6 +1,8 @@ package server +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static org.junit.jupiter.api.Assumptions.assumeTrue import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.LOGIN import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND @@ -10,6 +12,10 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCES import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.agent.test.base.HttpServer import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.api.Config +import okhttp3.MediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody import datadog.trace.api.DDSpanTypes import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator @@ -92,6 +98,32 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + def 'test instrumentation gateway file upload content max files limit'() { + setup: + assumeTrue(testBodyFilesContent()) + def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() + def bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM) + (1..maxFilesToInspect + 1).each { i -> + bodyBuilder.addFormDataPart("file${i}", "file${i}.bin", + RequestBody.create(MediaType.parse('application/octet-stream'), "content_of_file_${i}")) + } + def httpRequest = request(BODY_MULTIPART, 'POST', bodyBuilder.build()).build() + def response = client.newCall(httpRequest).execute() + + when: + TEST_WRITER.waitForTraces(1) + + then: + TEST_WRITER.get(0).any { span -> + def tag = span.getTag('request.body.files_content') as String + tag != null && tag.count('content_of_file_') == maxFilesToInspect + } + + cleanup: + response.close() + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy index 08f5f35b893..5386f901147 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy @@ -1,6 +1,8 @@ package server +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static org.junit.jupiter.api.Assumptions.assumeTrue import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.LOGIN import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND @@ -10,6 +12,10 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCES import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.agent.test.base.HttpServer import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.api.Config +import okhttp3.MediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody import datadog.trace.api.DDSpanTypes import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator @@ -87,6 +93,32 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + def 'test instrumentation gateway file upload content max files limit'() { + setup: + assumeTrue(testBodyFilesContent()) + def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() + def bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM) + (1..maxFilesToInspect + 1).each { i -> + bodyBuilder.addFormDataPart("file${i}", "file${i}.bin", + RequestBody.create(MediaType.parse('application/octet-stream'), "content_of_file_${i}")) + } + def httpRequest = request(BODY_MULTIPART, 'POST', bodyBuilder.build()).build() + def response = client.newCall(httpRequest).execute() + + when: + TEST_WRITER.waitForTraces(1) + + then: + TEST_WRITER.get(0).any { span -> + def tag = span.getTag('request.body.files_content') as String + tag != null && tag.count('content_of_file_') == maxFilesToInspect + } + + cleanup: + response.close() + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java index de9617ae802..c0eef8cab2e 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/FileUploadHelper.java @@ -10,9 +10,9 @@ import java.util.List; import java.util.function.BiFunction; -class FileUploadHelper { +public class FileUploadHelper { - static BlockingException commitBlockingResponse( + public static BlockingException commitBlockingResponse( BiFunction, Flow> cb, RequestContext reqCtx, List data, @@ -30,7 +30,7 @@ static BlockingException commitBlockingResponse( return null; } - static String readUploadContent(FileUpload upload, int maxBytes) { + public static String readUploadContent(FileUpload upload, int maxBytes) { try { String path = upload.uploadedFileName(); if (path == null || path.isEmpty()) { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 0d8fe3a7500..8c897bc4b40 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -1,6 +1,8 @@ package server +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static org.junit.jupiter.api.Assumptions.assumeTrue import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.LOGIN import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND @@ -10,6 +12,10 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCES import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.agent.test.base.HttpServer import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.api.Config +import okhttp3.MediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody import datadog.trace.api.DDSpanTypes import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator @@ -88,6 +94,32 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + def 'test instrumentation gateway file upload content max files limit'() { + setup: + assumeTrue(testBodyFilesContent()) + def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() + def bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM) + (1..maxFilesToInspect + 1).each { i -> + bodyBuilder.addFormDataPart("file${i}", "file${i}.bin", + RequestBody.create(MediaType.parse('application/octet-stream'), "content_of_file_${i}")) + } + def httpRequest = request(BODY_MULTIPART, 'POST', bodyBuilder.build()).build() + def response = client.newCall(httpRequest).execute() + + when: + TEST_WRITER.waitForTraces(1) + + then: + TEST_WRITER.get(0).any { span -> + def tag = span.getTag('request.body.files_content') as String + tag != null && tag.count('content_of_file_') == maxFilesToInspect + } + + cleanup: + response.close() + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java index 545bd69af02..045d825444a 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/FileUploadHelper.java @@ -10,9 +10,9 @@ import java.util.List; import java.util.function.BiFunction; -class FileUploadHelper { +public class FileUploadHelper { - static BlockingException commitBlockingResponse( + public static BlockingException commitBlockingResponse( BiFunction, Flow> cb, RequestContext reqCtx, List data, @@ -30,7 +30,7 @@ static BlockingException commitBlockingResponse( return null; } - static String readUploadContent(FileUpload upload, int maxBytes) { + public static String readUploadContent(FileUpload upload, int maxBytes) { try { String path = upload.uploadedFileName(); if (path == null || path.isEmpty()) { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index ff5b2b2f8c5..96fcc7aae78 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -1,7 +1,9 @@ package server import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_JSON +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static org.junit.jupiter.api.Assumptions.assumeTrue import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.LOGIN import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.NOT_FOUND @@ -11,6 +13,10 @@ import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCES import datadog.trace.agent.test.asserts.TraceAssert import datadog.trace.agent.test.base.HttpServer import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.api.Config +import okhttp3.MediaType +import okhttp3.MultipartBody +import okhttp3.RequestBody import datadog.trace.api.DDSpanTypes import datadog.trace.bootstrap.instrumentation.api.Tags import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator @@ -95,6 +101,32 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + def 'test instrumentation gateway file upload content max files limit'() { + setup: + assumeTrue(testBodyFilesContent()) + def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() + def bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM) + (1..maxFilesToInspect + 1).each { i -> + bodyBuilder.addFormDataPart("file${i}", "file${i}.bin", + RequestBody.create(MediaType.parse('application/octet-stream'), "content_of_file_${i}")) + } + def httpRequest = request(BODY_MULTIPART, 'POST', bodyBuilder.build()).build() + def response = client.newCall(httpRequest).execute() + + when: + TEST_WRITER.waitForTraces(1) + + then: + TEST_WRITER.get(0).any { span -> + def tag = span.getTag('request.body.files_content') as String + tag != null && tag.count('content_of_file_') == maxFilesToInspect + } + + cleanup: + response.close() + } + @Override boolean testBodyJson() { true From 79b1ef07494f75e6b222671b29cd769216ed6914 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 24 Jun 2026 15:12:31 +0200 Subject: [PATCH 5/7] style: replace em-dashes with colons in test comments --- .../src/test/groovy/server/VertxHttpServerForkedTest.groovy | 2 +- .../groovy/server/VertxHttpServerForkedTest.groovy | 2 +- .../src/test/groovy/server/VertxHttpServerForkedTest.groovy | 2 +- .../src/test/groovy/server/VertxHttpServerForkedTest.groovy | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy index fb294be5ef4..c7c1608b471 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -98,7 +98,7 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last def 'test instrumentation gateway file upload content max files limit'() { setup: assumeTrue(testBodyFilesContent()) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy index 5386f901147..ea99cf7faf3 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy @@ -93,7 +93,7 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last def 'test instrumentation gateway file upload content max files limit'() { setup: assumeTrue(testBodyFilesContent()) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 8c897bc4b40..4aeeb415282 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -94,7 +94,7 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last def 'test instrumentation gateway file upload content max files limit'() { setup: assumeTrue(testBodyFilesContent()) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 96fcc7aae78..473b95f72c3 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -101,7 +101,7 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x — override to verify count instead of which specific file is last + // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last def 'test instrumentation gateway file upload content max files limit'() { setup: assumeTrue(testBodyFilesContent()) From f29a9a9fe5c42fd3ff5490d2f96a1bd436aef93a Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 24 Jun 2026 16:01:58 +0200 Subject: [PATCH 6/7] fix: skip ordering-dependent max files limit test for Vert.x HashSet fileUploads() returns a HashSet in Vert.x so iteration order is JVM- version-dependent. Add testBodyFilesContentOrdering() flag (defaults true) to HttpServerTest to allow skipping the specific-file assertion. Vert.x subclasses return false and add a count-based test instead. --- .../datadog/trace/agent/test/base/HttpServerTest.groovy | 7 ++++++- .../test/groovy/server/VertxHttpServerForkedTest.groovy | 9 +++++++-- .../groovy/server/VertxHttpServerForkedTest.groovy | 9 +++++++-- .../test/groovy/server/VertxHttpServerForkedTest.groovy | 9 +++++++-- .../test/groovy/server/VertxHttpServerForkedTest.groovy | 9 +++++++-- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index 3cf73374fb2..f754a57e797 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -387,6 +387,11 @@ abstract class HttpServerTest extends WithHttpServer { false } + /** Override to false when the multipart implementation uses a set with non-deterministic ordering (e.g. HashSet). */ + boolean testBodyFilesContentOrdering() { + true + } + boolean testBodyJson() { false } @@ -1767,7 +1772,7 @@ abstract class HttpServerTest extends WithHttpServer { def 'test instrumentation gateway file upload content max files limit'() { setup: - assumeTrue(testBodyFilesContent()) + assumeTrue(testBodyFilesContent() && testBodyFilesContentOrdering()) def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() def bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM) (1..maxFilesToInspect + 1).each { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy index c7c1608b471..7fadc69bac5 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -98,8 +98,13 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last - def 'test instrumentation gateway file upload content max files limit'() { + @Override + boolean testBodyFilesContentOrdering() { + false + } + + // fileUploads() returns a HashSet in Vert.x: check count instead of which specific file is excluded + def 'test instrumentation gateway file upload content max files limit count'() { setup: assumeTrue(testBodyFilesContent()) def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy index ea99cf7faf3..f08e9dd7fd2 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/groovy/server/VertxHttpServerForkedTest.groovy @@ -93,8 +93,13 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last - def 'test instrumentation gateway file upload content max files limit'() { + @Override + boolean testBodyFilesContentOrdering() { + false + } + + // fileUploads() returns a HashSet in Vert.x: check count instead of which specific file is excluded + def 'test instrumentation gateway file upload content max files limit count'() { setup: assumeTrue(testBodyFilesContent()) def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 4aeeb415282..62455354611 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -94,8 +94,13 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last - def 'test instrumentation gateway file upload content max files limit'() { + @Override + boolean testBodyFilesContentOrdering() { + false + } + + // fileUploads() returns a HashSet in Vert.x: check count instead of which specific file is excluded + def 'test instrumentation gateway file upload content max files limit count'() { setup: assumeTrue(testBodyFilesContent()) def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 473b95f72c3..132baed6f4b 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -101,8 +101,13 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } - // fileUploads() returns a HashSet in Vert.x: override to verify count instead of which specific file is last - def 'test instrumentation gateway file upload content max files limit'() { + @Override + boolean testBodyFilesContentOrdering() { + false + } + + // fileUploads() returns a HashSet in Vert.x: check count instead of which specific file is excluded + def 'test instrumentation gateway file upload content max files limit count'() { setup: assumeTrue(testBodyFilesContent()) def maxFilesToInspect = Config.get().getAppSecMaxFileContentCount() From 4e4b7d6237927f2af970b7434206820b2a4c42e4 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Thu, 25 Jun 2026 10:35:10 +0200 Subject: [PATCH 7/7] fix: skip files_content test in VertxRxCircuitBreakerHttpServerForkedTest The circuit breaker test server does not configure BodyHandler for the multipart endpoint, so fileUploads() is never populated and the files_content instrumentation does not fire. Consistent with the existing testBodyFilenames() override in the same class. --- .../server/VertxRxCircuitBreakerHttpServerForkedTest.groovy | 5 +++++ .../src/test/groovy/server/VertxHttpServerForkedTest.groovy | 2 -- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx-rx-3.5/src/test/groovy/server/VertxRxCircuitBreakerHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-rx-3.5/src/test/groovy/server/VertxRxCircuitBreakerHttpServerForkedTest.groovy index 0c79e4e10f3..96b8b9b3a54 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-rx-3.5/src/test/groovy/server/VertxRxCircuitBreakerHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-rx-3.5/src/test/groovy/server/VertxRxCircuitBreakerHttpServerForkedTest.groovy @@ -44,6 +44,11 @@ class VertxRxCircuitBreakerHttpServerForkedTest extends VertxHttpServerForkedTes false } + @Override + boolean testBodyFilesContent() { + false + } + @Override boolean testBodyJson() { false diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy index 132baed6f4b..cdd4bca1c19 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/groovy/server/VertxHttpServerForkedTest.groovy @@ -23,8 +23,6 @@ import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator import datadog.trace.instrumentation.vertx_4_0.server.VertxDecorator import io.vertx.core.AbstractVerticle import io.vertx.core.Vertx -import okhttp3.MediaType -import okhttp3.RequestBody class VertxHttpServerForkedTest extends HttpServerTest { @Override