From 62edb3556540d084b6c73ab7a2e4333c9e7aef72 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Mon, 4 May 2026 15:47:30 +0200 Subject: [PATCH 1/4] Add server.request.body.filenames AppSec address for Vert.x 3.4, 4.0 and 5.0 Instruments RoutingContextImpl.fileUploads() in vertx-web-3.4, vertx-web-4.0 and vertx-web-5.0 to fire EVENTS.requestFilesFilenames() with the list of uploaded filenames, enabling WAF-level blocking on malicious file names. --- .../netty41/NettyMultipartHelper.java | 13 +++- .../server/RoutingContextFilenamesAdvice.java | 72 +++++++++++++++++++ .../RoutingContextImplInstrumentation.java | 10 ++- .../server/VertxHttpServerForkedTest.groovy | 5 ++ .../src/test/java/server/VertxTestServer.java | 11 ++- .../server/VertxHttpServerForkedTest.groovy | 5 ++ .../java/server/VertxTestServer.java | 11 ++- .../server/RoutingContextFilenamesAdvice.java | 72 +++++++++++++++++++ .../RoutingContextImplInstrumentation.java | 10 ++- .../server/VertxHttpServerForkedTest.groovy | 5 ++ .../src/test/java/server/VertxTestServer.java | 11 ++- .../server/RoutingContextFilenamesAdvice.java | 71 ++++++++++++++++++ .../RoutingContextImplInstrumentation.java | 40 +++++++++++ .../server/VertxHttpServerForkedTest.groovy | 5 ++ .../src/test/java/server/VertxTestServer.java | 11 ++- 15 files changed, 319 insertions(+), 33 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/RoutingContextFilenamesAdvice.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/RoutingContextFilenamesAdvice.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/RoutingContextFilenamesAdvice.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/RoutingContextImplInstrumentation.java diff --git a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java index b268216f2aa..3e1d7cca046 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java +++ b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java @@ -33,8 +33,15 @@ public static RuntimeException collectBodyData( List filesContent) { RuntimeException exc = null; for (InterfaceHttpData data : parts) { - if (attributes != null - && data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) { + InterfaceHttpData.HttpDataType dataType; + try { + dataType = data.getHttpDataType(); + } catch (UnsupportedOperationException ignored) { + // Some framework-specific implementations (e.g. Vert.x NettyFileUpload) do not support + // getHttpDataType() — skip them; their framework's own instrumentation handles them. + continue; + } + if (attributes != null && dataType == InterfaceHttpData.HttpDataType.Attribute) { String name = data.getName(); List values = attributes.get(name); if (values == null) { @@ -45,7 +52,7 @@ public static RuntimeException collectBodyData( } catch (IOException e) { exc = new UndeclaredThrowableException(e); } - } else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) { + } else if (dataType == InterfaceHttpData.HttpDataType.FileUpload) { FileUpload fileUpload = (FileUpload) data; String filename = fileUpload.getFilename(); if (filenames != null && filename != null && !filename.isEmpty()) { 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 new file mode 100644 index 00000000000..4ab0bbec651 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/datadog/trace/instrumentation/vertx_3_4/server/RoutingContextFilenamesAdvice.java @@ -0,0 +1,72 @@ +package datadog.trace.instrumentation.vertx_3_4.server; + +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.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.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import io.vertx.ext.web.FileUpload; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.BiFunction; +import net.bytebuddy.asm.Advice; + +@RequiresRequestContext(RequestContextSlot.APPSEC) +class RoutingContextFilenamesAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + static int before() { + return CallDepthThreadLocalMap.incrementCallDepth(FileUpload.class); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + static void after( + @Advice.Enter int depth, + @Advice.Return Set uploads, + @ActiveRequestContext RequestContext reqCtx, + @Advice.Thrown(readOnly = false) Throwable throwable) { + CallDepthThreadLocalMap.decrementCallDepth(FileUpload.class); + if (depth != 0 || throwable != null || uploads == null || uploads.isEmpty()) { + return; + } + + List filenames = new ArrayList<>(uploads.size()); + for (FileUpload upload : uploads) { + String name = upload.fileName(); + if (name != null && !name.isEmpty()) { + filenames.add(name); + } + } + if (filenames.isEmpty()) { + return; + } + + CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); + BiFunction, Flow> cb = + cbp.getCallback(EVENTS.requestFilesFilenames()); + if (cb == null) { + return; + } + + Flow flow = cb.apply(reqCtx, filenames); + 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)"); + } + } + } + } +} 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 bc52bbfdbed..e13eef7f18e 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 @@ -15,6 +15,11 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + private static final Reference FILE_UPLOAD_REF = + new Reference.Builder("io.vertx.ext.web.FileUpload") + .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") + .build(); + public RoutingContextImplInstrumentation() { super("vertx", "vertx-3.4"); } @@ -26,7 +31,7 @@ public String instrumentedType() { @Override public Reference[] additionalMuzzleReferences() { - return new Reference[] {PARSABLE_HEADER_VALUE, VIRTUAL_HOST_HANDLER}; + return new Reference[] {PARSABLE_HEADER_VALUE, VIRTUAL_HOST_HANDLER, FILE_UPLOAD_REF}; } @Override @@ -37,5 +42,8 @@ public void methodAdvice(MethodTransformer transformer) { transformer.applyAdvice( named("setSession").and(takesArgument(0, named("io.vertx.ext.web.Session"))), packageName + ".RoutingContextSessionAdvice"); + transformer.applyAdvice( + named("fileUploads").and(takesArguments(0)), + packageName + ".RoutingContextFilenamesAdvice"); } } 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 4b585252db3..c64753affe7 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 @@ -82,6 +82,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilenames() { + true + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/java/server/VertxTestServer.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/java/server/VertxTestServer.java index 2d20d98a0ef..0110d1268da 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/java/server/VertxTestServer.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/test/java/server/VertxTestServer.java @@ -92,6 +92,7 @@ public void start(final Future startFuture) { String res = convertFormAttributes(ctx); ctx.response().setStatusCode(BODY_URLENCODED.getStatus()).end(res); })); + router.route(BODY_MULTIPART.getPath()).handler(BodyHandler.create()); router .route(BODY_MULTIPART.getPath()) .handler( @@ -100,13 +101,9 @@ public void start(final Future startFuture) { ctx, BODY_MULTIPART, () -> { - ctx.request().setExpectMultipart(true); - ctx.request() - .endHandler( - (_void) -> { - String res = convertFormAttributes(ctx); - ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); - }); + ctx.fileUploads(); + String res = convertFormAttributes(ctx); + ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); })); router.route(BODY_JSON.getPath()).handler(BodyHandler.create()); router 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 2c09d35d353..8f276d08f23 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 @@ -77,6 +77,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilenames() { + true + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/java/server/VertxTestServer.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/java/server/VertxTestServer.java index f711678b3bd..8b20617cc7c 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/java/server/VertxTestServer.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/latestDepTest/java/server/VertxTestServer.java @@ -92,6 +92,7 @@ public void start(final Promise startPromise) { String res = convertFormAttributes(ctx); ctx.response().setStatusCode(BODY_URLENCODED.getStatus()).end(res); })); + router.route(BODY_MULTIPART.getPath()).handler(BodyHandler.create()); router .route(BODY_MULTIPART.getPath()) .handler( @@ -100,13 +101,9 @@ public void start(final Promise startPromise) { ctx, BODY_MULTIPART, () -> { - ctx.request().setExpectMultipart(true); - ctx.request() - .endHandler( - (_void) -> { - String res = convertFormAttributes(ctx); - ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); - }); + ctx.fileUploads(); + String res = convertFormAttributes(ctx); + ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); })); router.route(BODY_JSON.getPath()).handler(BodyHandler.create()); router 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 new file mode 100644 index 00000000000..e9ac149e7bb --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/datadog/trace/instrumentation/vertx_4_0/server/RoutingContextFilenamesAdvice.java @@ -0,0 +1,72 @@ +package datadog.trace.instrumentation.vertx_4_0.server; + +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.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.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import io.vertx.ext.web.FileUpload; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.BiFunction; +import net.bytebuddy.asm.Advice; + +@RequiresRequestContext(RequestContextSlot.APPSEC) +class RoutingContextFilenamesAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + static int before() { + return CallDepthThreadLocalMap.incrementCallDepth(FileUpload.class); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + static void after( + @Advice.Enter int depth, + @Advice.Return Collection uploads, + @ActiveRequestContext RequestContext reqCtx, + @Advice.Thrown(readOnly = false) Throwable throwable) { + CallDepthThreadLocalMap.decrementCallDepth(FileUpload.class); + if (depth != 0 || throwable != null || uploads == null || uploads.isEmpty()) { + return; + } + + List filenames = new ArrayList<>(uploads.size()); + for (FileUpload upload : uploads) { + String name = upload.fileName(); + if (name != null && !name.isEmpty()) { + filenames.add(name); + } + } + if (filenames.isEmpty()) { + return; + } + + CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); + BiFunction, Flow> cb = + cbp.getCallback(EVENTS.requestFilesFilenames()); + if (cb == null) { + return; + } + + Flow flow = cb.apply(reqCtx, filenames); + 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)"); + } + } + } + } +} 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 7a1e157e174..ae863b6ec09 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 @@ -18,13 +18,18 @@ public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + private static final Reference FILE_UPLOAD_REF = + new Reference.Builder("io.vertx.ext.web.FileUpload") + .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") + .build(); + public RoutingContextImplInstrumentation() { super("vertx", "vertx-4.0"); } @Override public Reference[] additionalMuzzleReferences() { - return new Reference[] {VertxVersionMatcher.HTTP_1X_SERVER_RESPONSE}; + return new Reference[] {VertxVersionMatcher.HTTP_1X_SERVER_RESPONSE, FILE_UPLOAD_REF}; } @Override @@ -43,5 +48,8 @@ public void methodAdvice(MethodTransformer transformer) { transformer.applyAdvice( named("setSession").and(takesArgument(0, named("io.vertx.ext.web.Session"))), packageName + ".RoutingContextSessionAdvice"); + transformer.applyAdvice( + named("fileUploads").and(takesArguments(0)), + packageName + ".RoutingContextFilenamesAdvice"); } } 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 e18172999ff..1d2f4ec9971 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 @@ -78,6 +78,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilenames() { + true + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java index d4dc482cb2a..5bb66696a9d 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java @@ -101,6 +101,7 @@ public void start(final Promise startPromise) { String res = convertFormAttributes(ctx); ctx.response().setStatusCode(BODY_URLENCODED.getStatus()).end(res); })); + router.route(BODY_MULTIPART.getPath()).handler(BodyHandler.create()); router .route(BODY_MULTIPART.getPath()) .handler( @@ -109,13 +110,9 @@ public void start(final Promise startPromise) { ctx, BODY_MULTIPART, () -> { - ctx.request().setExpectMultipart(true); - ctx.request() - .endHandler( - (_void) -> { - String res = convertFormAttributes(ctx); - ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); - }); + ctx.fileUploads(); + String res = convertFormAttributes(ctx); + ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); })); router.route(BODY_JSON.getPath()).handler(BodyHandler.create()); router 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 new file mode 100644 index 00000000000..cf4aa6482f6 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextFilenamesAdvice.java @@ -0,0 +1,71 @@ +package datadog.trace.instrumentation.vertx_5_0.server; + +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.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.bootstrap.CallDepthThreadLocalMap; +import datadog.trace.bootstrap.instrumentation.api.AgentTracer; +import io.vertx.ext.web.FileUpload; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiFunction; +import net.bytebuddy.asm.Advice; + +@RequiresRequestContext(RequestContextSlot.APPSEC) +class RoutingContextFilenamesAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + static int before() { + return CallDepthThreadLocalMap.incrementCallDepth(FileUpload.class); + } + + @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class) + static void after( + @Advice.Enter int depth, + @Advice.Return List uploads, + @ActiveRequestContext RequestContext reqCtx, + @Advice.Thrown(readOnly = false) Throwable throwable) { + CallDepthThreadLocalMap.decrementCallDepth(FileUpload.class); + if (depth != 0 || throwable != null || uploads == null || uploads.isEmpty()) { + return; + } + + List filenames = new ArrayList<>(uploads.size()); + for (FileUpload upload : uploads) { + String name = upload.fileName(); + if (name != null && !name.isEmpty()) { + filenames.add(name); + } + } + if (filenames.isEmpty()) { + return; + } + + CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC); + BiFunction, Flow> cb = + cbp.getCallback(EVENTS.requestFilesFilenames()); + if (cb == null) { + return; + } + + Flow flow = cb.apply(reqCtx, filenames); + 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)"); + } + } + } + } +} 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 new file mode 100644 index 00000000000..93ff71d4d82 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/datadog/trace/instrumentation/vertx_5_0/server/RoutingContextImplInstrumentation.java @@ -0,0 +1,40 @@ +package datadog.trace.instrumentation.vertx_5_0.server; + +import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.tooling.Instrumenter; +import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.muzzle.Reference; + +@AutoService(InstrumenterModule.class) +public class RoutingContextImplInstrumentation extends InstrumenterModule.AppSec + implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice { + + private static final Reference FILE_UPLOAD_REF = + new Reference.Builder("io.vertx.ext.web.FileUpload") + .withMethod(new String[0], 0, "fileName", "Ljava/lang/String;") + .build(); + + public RoutingContextImplInstrumentation() { + super("vertx", "vertx-5.0"); + } + + @Override + public String instrumentedType() { + return "io.vertx.ext.web.impl.RoutingContextImpl"; + } + + @Override + public Reference[] additionalMuzzleReferences() { + return new Reference[] {VertxVersionMatcher.HTTP_HEADERS_INTERNAL, FILE_UPLOAD_REF}; + } + + @Override + public void methodAdvice(MethodTransformer transformer) { + transformer.applyAdvice( + named("fileUploads").and(takesArguments(0)), + packageName + ".RoutingContextFilenamesAdvice"); + } +} 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 261430c0023..8a9372dd9e7 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 @@ -82,6 +82,11 @@ class VertxHttpServerForkedTest extends HttpServerTest { true } + @Override + boolean testBodyFilenames() { + true + } + @Override boolean testBodyJson() { true diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/java/server/VertxTestServer.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/java/server/VertxTestServer.java index 17feffcc79f..24902867163 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/java/server/VertxTestServer.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/test/java/server/VertxTestServer.java @@ -92,6 +92,7 @@ public void start(final Promise startPromise) { String res = convertFormAttributes(ctx); ctx.response().setStatusCode(BODY_URLENCODED.getStatus()).end(res); })); + router.route(BODY_MULTIPART.getPath()).handler(BodyHandler.create()); router .route(BODY_MULTIPART.getPath()) .handler( @@ -100,13 +101,9 @@ public void start(final Promise startPromise) { ctx, BODY_MULTIPART, () -> { - ctx.request().setExpectMultipart(true); - ctx.request() - .endHandler( - (_void) -> { - String res = convertFormAttributes(ctx); - ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); - }); + ctx.fileUploads(); + String res = convertFormAttributes(ctx); + ctx.response().setStatusCode(BODY_MULTIPART.getStatus()).end(res); })); router.route(BODY_JSON.getPath()).handler(BodyHandler.create()); router From e06a94bc5607dbc75ef465e559a88ec003005c31 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 20 May 2026 10:40:41 +0200 Subject: [PATCH 2/4] Remove stray fibonacci() CPU burn from vertx-web-4.0 test server --- .../src/test/java/server/VertxTestServer.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java index 5bb66696a9d..53c8236ef78 100644 --- a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/test/java/server/VertxTestServer.java @@ -39,13 +39,6 @@ public class VertxTestServer extends AbstractVerticle { public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; public static final String PORT_DATA_ADDRESS = "PORT_DATA"; - int fibonacci(int n) { - if (n <= 1) { - return n; - } - return fibonacci(n - 1) + fibonacci(n - 2); - } - @Override public void start(final Promise startPromise) { final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); @@ -60,10 +53,8 @@ public void start(final Promise startPromise) { controller( ctx, SUCCESS, - () -> { - fibonacci(40); - ctx.response().setStatusCode(SUCCESS.getStatus()).end(SUCCESS.getBody()); - })); + () -> + ctx.response().setStatusCode(SUCCESS.getStatus()).end(SUCCESS.getBody()))); router .route(FORWARDED.getPath()) .handler( From d521276da4048ec28ebc8b779663639bea418b0d Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Fri, 22 May 2026 11:52:14 +0200 Subject: [PATCH 3/4] Fix em dash in NettyMultipartHelper comment and ignore BodyHandler temp dirs - Replace em dash with hyphen in UnsupportedOperationException catch comment - Add .gitignore for file-uploads/ in vertx-web-3.4, 4.0, and 5.0 modules to suppress the temporary upload directories created by BodyHandler.create() during test execution --- .../trace/instrumentation/netty41/NettyMultipartHelper.java | 2 +- .../instrumentation/vertx/vertx-web/vertx-web-3.4/.gitignore | 1 + .../instrumentation/vertx/vertx-web/vertx-web-4.0/.gitignore | 1 + .../instrumentation/vertx/vertx-web/vertx-web-5.0/.gitignore | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/.gitignore create mode 100644 dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/.gitignore create mode 100644 dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/.gitignore diff --git a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java index 3e1d7cca046..95a0c6292e7 100644 --- a/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java +++ b/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/datadog/trace/instrumentation/netty41/NettyMultipartHelper.java @@ -38,7 +38,7 @@ public static RuntimeException collectBodyData( dataType = data.getHttpDataType(); } catch (UnsupportedOperationException ignored) { // Some framework-specific implementations (e.g. Vert.x NettyFileUpload) do not support - // getHttpDataType() — skip them; their framework's own instrumentation handles them. + // getHttpDataType() - skip them; their framework's own instrumentation handles them. continue; } if (attributes != null && dataType == InterfaceHttpData.HttpDataType.Attribute) { diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/.gitignore b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/.gitignore new file mode 100644 index 00000000000..866a6ca4ccc --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/.gitignore @@ -0,0 +1 @@ +/file-uploads/ diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/.gitignore b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/.gitignore new file mode 100644 index 00000000000..866a6ca4ccc --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/.gitignore @@ -0,0 +1 @@ +/file-uploads/ diff --git a/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/.gitignore b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/.gitignore new file mode 100644 index 00000000000..866a6ca4ccc --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/.gitignore @@ -0,0 +1 @@ +/file-uploads/ From dad31815bd13ba737f675109723f42d391d0512b Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Fri, 22 May 2026 15:24:29 +0200 Subject: [PATCH 4/4] Disable testBodyFilenames in VertxRxCircuitBreakerHttpServerForkedTest The circuit breaker test server uses the legacy expectMultipart+endHandler pattern and never calls fileUploads(), so the filenames tag is never emitted. Opt out explicitly to avoid inheriting the true override from VertxHttpServerForkedTest. --- .../server/VertxRxCircuitBreakerHttpServerForkedTest.groovy | 5 +++++ 1 file changed, 5 insertions(+) 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 01479aad752..0c79e4e10f3 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 @@ -39,6 +39,11 @@ class VertxRxCircuitBreakerHttpServerForkedTest extends VertxHttpServerForkedTes false } + @Override + boolean testBodyFilenames() { + false + } + @Override boolean testBodyJson() { false