From 0275a50e3c8ef79dd6f828a714a191b9a6200afd Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Fri, 4 Jul 2025 14:45:40 +0200 Subject: [PATCH 01/11] WIP --- .../com/datadog/iast/HttpRouteHandler.java | 16 +++ .../com/datadog/iast/IastRequestContext.java | 10 ++ .../java/com/datadog/iast/IastSystem.java | 5 + .../iast/overhead/OverheadController.java | 15 ++- .../datadog/iast/HttpRouteHandlerTest.groovy | 39 ++++++ .../com/datadog/iast/IastSystemTest.groovy | 1 + .../play23/PlayHttpServerDecorator.java | 18 ++- .../app/controllers/IastController.scala | 47 +++++++ dd-smoke-tests/play-2.5/build.gradle | 3 + .../smoketest/IastPlayNettySmokeTest.groovy | 115 ++++++++++++++++++ 10 files changed, 263 insertions(+), 6 deletions(-) create mode 100644 dd-java-agent/agent-iast/src/main/java/com/datadog/iast/HttpRouteHandler.java create mode 100644 dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/HttpRouteHandlerTest.groovy create mode 100644 dd-smoke-tests/play-2.5/app/controllers/IastController.scala create mode 100644 dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/HttpRouteHandler.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/HttpRouteHandler.java new file mode 100644 index 00000000000..2e6017254d8 --- /dev/null +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/HttpRouteHandler.java @@ -0,0 +1,16 @@ +package com.datadog.iast; + +import datadog.trace.api.gateway.RequestContext; +import datadog.trace.api.gateway.RequestContextSlot; +import java.util.function.BiConsumer; + +public class HttpRouteHandler implements BiConsumer { + + @Override + public void accept(final RequestContext ctx, final String route) { + final IastRequestContext iastCtx = ctx.getData(RequestContextSlot.IAST); + if (iastCtx != null) { + iastCtx.setRoute(route); + } + } +} diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java index c2960eb82a7..ebfd78c7d3e 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastRequestContext.java @@ -36,6 +36,7 @@ public class IastRequestContext implements IastContext, HasMetricCollector { @Nullable private volatile String xForwardedProto; @Nullable private volatile String contentType; @Nullable private volatile String authorization; + @Nullable private volatile String route; /** * Use {@link IastRequestContext#IastRequestContext(TaintedObjects)} instead as we require more @@ -121,6 +122,15 @@ public void setAuthorization(final String authorization) { this.authorization = authorization; } + @Nullable + public String getRoute() { + return route; + } + + public void setRoute(final String route) { + this.route = route; + } + public OverheadContext getOverheadContext() { return overheadContext; } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java index ec63532127b..26bd081cbc2 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/IastSystem.java @@ -122,6 +122,7 @@ public static void start( registerRequestStartedCallback(ss, addTelemetry, dependencies); registerRequestEndedCallback(ss, addTelemetry, dependencies); registerHeadersCallback(ss); + registerHttpRouteCallback(ss); registerGrpcServerRequestMessageCallback(ss); maybeApplySecurityControls(instrumentation); LOGGER.debug("IAST started"); @@ -246,6 +247,10 @@ private static void registerHeadersCallback(final SubscriptionService ss) { ss.registerCallback(event, handler); } + private static void registerHttpRouteCallback(final SubscriptionService ss) { + ss.registerCallback(Events.get().httpRoute(), new HttpRouteHandler()); + } + private static void registerGrpcServerRequestMessageCallback(final SubscriptionService ss) { ss.registerCallback(Events.get().grpcServerRequestMessage(), new GrpcRequestMessageHandler()); } diff --git a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java index 0a9e9fa56ea..344ae25226f 100644 --- a/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java +++ b/dd-java-agent/agent-iast/src/main/java/com/datadog/iast/overhead/OverheadController.java @@ -233,7 +233,7 @@ public boolean consumeQuota( Object methodTag = rootSpan.getTag(Tags.HTTP_METHOD); method = (methodTag == null) ? "" : methodTag.toString(); Object routeTag = rootSpan.getTag(Tags.HTTP_ROUTE); - path = (routeTag == null) ? "" : routeTag.toString(); + path = (routeTag == null) ? getHttpRouteFromRequestContext(span) : routeTag.toString(); } if (!maybeSkipVulnerability(ctx, type, method, path)) { return operation.consumeQuota(ctx); @@ -316,6 +316,19 @@ public OverheadContext getContext(@Nullable final AgentSpan span) { return globalContext; } + @Nullable + public String getHttpRouteFromRequestContext(@Nullable final AgentSpan span) { + String httpRoute = null; + final RequestContext requestContext = span != null ? span.getRequestContext() : null; + if (requestContext != null) { + IastRequestContext iastRequestContext = requestContext.getData(RequestContextSlot.IAST); + if (iastRequestContext != null) { + httpRoute = iastRequestContext.getRoute(); + } + } + return httpRoute == null ? "" : httpRoute; + } + static int computeSamplingParameter(final float pct) { if (pct >= 100) { return 100; diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/HttpRouteHandlerTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/HttpRouteHandlerTest.groovy new file mode 100644 index 00000000000..71b5002cead --- /dev/null +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/HttpRouteHandlerTest.groovy @@ -0,0 +1,39 @@ +package com.datadog.iast + +import datadog.trace.api.gateway.RequestContext +import datadog.trace.api.gateway.RequestContextSlot +import datadog.trace.test.util.DDSpecification +import groovy.transform.CompileDynamic + +@CompileDynamic +class HttpRouteHandlerTest extends DDSpecification { + void 'route is set'() { + given: + final handler = new HttpRouteHandler() + final iastCtx = Mock(IastRequestContext) + final ctx = Mock(RequestContext) + ctx.getData(RequestContextSlot.IAST) >> iastCtx + + when: + handler.accept(ctx, '/foo') + + then: + 1 * ctx.getData(RequestContextSlot.IAST) >> iastCtx + 1 * iastCtx.setRoute('/foo') + 0 * _ + } + + void 'does nothing when context missing'() { + given: + final handler = new HttpRouteHandler() + final ctx = Mock(RequestContext) + ctx.getData(RequestContextSlot.IAST) >> null + + when: + handler.accept(ctx, '/foo') + + then: + 1 * ctx.getData(RequestContextSlot.IAST) >> null + 0 * _ + } +} diff --git a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy index 34fed3fadb8..a09eaef3eda 100644 --- a/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy +++ b/dd-java-agent/agent-iast/src/test/groovy/com/datadog/iast/IastSystemTest.groovy @@ -60,6 +60,7 @@ class IastSystemTest extends DDSpecification { 1 * ss.registerCallback(Events.get().requestStarted(), _) 1 * ss.registerCallback(Events.get().requestEnded(), _) 1 * ss.registerCallback(Events.get().requestHeader(), _) + 1 * ss.registerCallback(Events.get().httpRoute(), _) 1 * ss.registerCallback(Events.get().grpcServerRequestMessage(), _) 0 * _ TestLogCollector.drainCapturedLogs().any { it.message.contains('IAST is starting') } diff --git a/dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java index a53c5a739eb..9144fb8fe1e 100644 --- a/dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/play-2.3/src/main/java/datadog/trace/instrumentation/play23/PlayHttpServerDecorator.java @@ -112,13 +112,21 @@ private void dispatchRoute(final AgentSpan span, final String route) { if (ctx == null) { return; } + // Send event to AppSec provider final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - if (cbp == null) { - return; + if (cbp != null) { + final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, route); + } } - final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, route); + // Send event to IAST provider + final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpIast != null) { + final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, route); + } } } catch (final Throwable t) { LOG.debug("Failed to dispatch route", t); diff --git a/dd-smoke-tests/play-2.5/app/controllers/IastController.scala b/dd-smoke-tests/play-2.5/app/controllers/IastController.scala new file mode 100644 index 00000000000..cfe0c2e3189 --- /dev/null +++ b/dd-smoke-tests/play-2.5/app/controllers/IastController.scala @@ -0,0 +1,47 @@ +package controllers + +import play.api.mvc._ +import java.security.MessageDigest +import java.nio.charset.StandardCharsets + +class IastController extends Controller { + + def multipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def postMultipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def multipleVulns2(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } +} diff --git a/dd-smoke-tests/play-2.5/build.gradle b/dd-smoke-tests/play-2.5/build.gradle index 7e363639b28..84cb1027d05 100644 --- a/dd-smoke-tests/play-2.5/build.gradle +++ b/dd-smoke-tests/play-2.5/build.gradle @@ -66,6 +66,9 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') + testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) + + implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { diff --git a/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy new file mode 100644 index 00000000000..59e7ab08cf0 --- /dev/null +++ b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy @@ -0,0 +1,115 @@ +package datadog.smoketest + +import static java.util.concurrent.TimeUnit.SECONDS +import okhttp3.FormBody +import okhttp3.Request +import spock.lang.Shared + +import java.nio.file.Files + + +class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { + + @Shared + File playDirectory = new File("${buildDirectory}/stage/main") + + @Shared + protected String[] defaultIastProperties = [ + "-Ddd.iast.enabled=true", + "-Ddd.iast.detection.mode=DEFAULT", + "-Ddd.iast.debug.enabled=true", + "-Ddd.iast.request-sampling=100", + ] + + @Override + ProcessBuilder createProcessBuilder() { + // If the server is not shut down correctly, this file can be left there and will block + // the start of a new test + def runningPid = new File(playDirectory.getPath(), "RUNNING_PID") + if (runningPid.exists()) { + runningPid.delete() + } + def command = isWindows() ? 'main.bat' : 'main' + ProcessBuilder processBuilder = new ProcessBuilder("${playDirectory}/bin/${command}") + processBuilder.directory(playDirectory) + processBuilder.environment().put("JAVA_OPTS", + '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 '+ + (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + + " -Dconfig.file=${playDirectory}/conf/application.conf" + + " -Dhttp.port=${httpPort}" + + " -Dhttp.address=127.0.0.1" + + " -Dplay.server.provider=play.core.server.NettyServerProvider" + + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter") + return processBuilder + } + + @Override + File createTemporaryFile() { + return new File("${buildDirectory}/tmp/trace-structure-play-2.5-appsec-netty.out") + } + + void 'Test that the server starts'() { + expect: + def request = new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/1/?param=value1") + .get() + .build() + def response = client.newCall(request as Request).execute() + assert response.code() == 200, "Response code was ${response.code()} for request ${request}" + } + + // void 'Test that all the vulnerabilities are detected'() { + // given: + // // prepare a list of exactly three GET requests with path and query param + // def requests = [] + // for (int i = 1; i <= 3; i++) { + // requests.add(new Request.Builder() + // .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}/?param=value${i}") + // .get() + // .build()) + // requests.add(new Request.Builder() + // .url("http://localhost:${httpPort}/iast/multiple_vulns-2/${i}/?param=value${i}") + // .get() + // .build()) + // requests.add(new Request.Builder() + // .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}") + // .post(new FormBody.Builder().add('param', "value${i}").build()) + // .build()) + // } + // + // + // when: + // requests.each { req -> + // def response = client.newCall(req as Request).execute() + // assert response.code() == 200, "Response code was ${response.code()} for request ${req}" + // } + // + // then: 'check first get mapping' + // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'SHA1' } + // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'SHA-1' } + // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'MD2'} + // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'MD5'} + // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'RIPEMD128'} + //} + + // Ensure to clean up server and not only the shell script that starts it + def cleanupSpec() { + def pid = runningServerPid() + if (pid) { + def commands = isWindows() ? ['taskkill', '/PID', pid, '/T', '/F'] : ['kill', '-9', pid] + new ProcessBuilder(commands).start().waitFor(10, SECONDS) + } + } + + def runningServerPid() { + def runningPid = new File(playDirectory.getPath(), 'RUNNING_PID') + if (runningPid.exists()) { + return Files.lines(runningPid.toPath()).findAny().orElse(null) + } + } + + static isWindows() { + return System.getProperty('os.name').toLowerCase().contains('win') + } + +} From 025939164228686539a37203550e70599cbe429f Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Fri, 4 Jul 2025 14:46:03 +0200 Subject: [PATCH 02/11] WIP --- .../play24/PlayHttpServerDecorator.java | 18 +++++++++++++----- .../play26/PlayHttpServerDecorator.java | 18 +++++++++++++----- dd-smoke-tests/play-2.5/conf/routes | 6 ++++++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java index efd55a5bb23..4aa8a27cf98 100644 --- a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java @@ -112,13 +112,21 @@ private void dispatchRoute(final AgentSpan span, final String route) { if (ctx == null) { return; } + // Send event to AppSec provider final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - if (cbp == null) { - return; + if (cbp != null) { + final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, route); + } } - final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, route); + // Send event to IAST provider + final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpIast != null) { + final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, route); + } } } catch (final Throwable t) { LOG.debug("Failed to dispatch route", t); diff --git a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java index 494546754cf..5f31d3d5e36 100644 --- a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java @@ -168,13 +168,21 @@ private void dispatchRoute(final AgentSpan span, final CharSequence route) { if (ctx == null) { return; } + // Send event to AppSec provider final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - if (cbp == null) { - return; + if (cbp != null) { + final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, URIUtils.decode(route.toString())); + } } - final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, URIUtils.decode(route.toString())); + // Send event to IAST provider + final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpIast != null) { + final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, URIUtils.decode(route.toString())); + } } } catch (final Throwable t) { LOG.debug("Failed to dispatch route", t); diff --git a/dd-smoke-tests/play-2.5/conf/routes b/dd-smoke-tests/play-2.5/conf/routes index d7f24a29a24..58215f48dc7 100644 --- a/dd-smoke-tests/play-2.5/conf/routes +++ b/dd-smoke-tests/play-2.5/conf/routes @@ -10,3 +10,9 @@ GET /welcomes controllers.SController.doGet(id: Op # AppSec endpoints for testing GET /api_security/sampling/:statusCode controllers.AppSecController.apiSecuritySampling(statusCode: Int, test: String) POST /api_security/response controllers.AppSecController.apiResponse() + +# IAST Sampling endpoints +GET /iast/multiple_vulns/:id controllers.IastController.multipleVulns(id: String) +POST /iast/multiple_vulns/:id controllers.IastController.postMultipleVulns(id: String) +GET /iast/multiple_vulns-2/:id controllers.IastController.multipleVulns2(id: String) + From 2a56b37caa7629861d2fe336b457768d873fa1d4 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Fri, 4 Jul 2025 16:15:53 +0200 Subject: [PATCH 03/11] WIP --- .../app/controllers/IastController.scala | 48 +++++++ dd-smoke-tests/play-2.4/build.gradle | 4 + dd-smoke-tests/play-2.4/conf/routes | 5 + .../smoketest/IastPlayNettySmokeTest.groovy | 104 ++++++++++++++ .../smoketest/IastPlayNettySmokeTest.groovy | 77 +++++----- .../app/controllers/IastController.scala | 48 +++++++ dd-smoke-tests/play-2.6/build.gradle | 1 + dd-smoke-tests/play-2.6/conf/routes | 5 + .../smoketest/IastPlaySmokeTest.groovy | 131 ++++++++++++++++++ .../app/controllers/IastController.scala | 48 +++++++ dd-smoke-tests/play-2.7/build.gradle | 3 + dd-smoke-tests/play-2.7/conf/routes | 5 + .../smoketest/IastPlaySmokeTest.groovy | 131 ++++++++++++++++++ .../app/controllers/IastController.scala | 50 +++++++ dd-smoke-tests/play-2.8/build.gradle | 3 + dd-smoke-tests/play-2.8/conf/routes | 5 + .../smoketest/IastPlaySmokeTest.groovy | 131 ++++++++++++++++++ 17 files changed, 755 insertions(+), 44 deletions(-) create mode 100644 dd-smoke-tests/play-2.4/app/controllers/IastController.scala create mode 100644 dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy create mode 100644 dd-smoke-tests/play-2.6/app/controllers/IastController.scala create mode 100644 dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy create mode 100644 dd-smoke-tests/play-2.7/app/controllers/IastController.scala create mode 100644 dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy create mode 100644 dd-smoke-tests/play-2.8/app/controllers/IastController.scala create mode 100644 dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy diff --git a/dd-smoke-tests/play-2.4/app/controllers/IastController.scala b/dd-smoke-tests/play-2.4/app/controllers/IastController.scala new file mode 100644 index 00000000000..e94fb95a8d9 --- /dev/null +++ b/dd-smoke-tests/play-2.4/app/controllers/IastController.scala @@ -0,0 +1,48 @@ +package controllers + +import play.api.mvc._ + +import java.nio.charset.StandardCharsets +import java.security.MessageDigest + +class IastController extends Controller { + + def multipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def postMultipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def multipleVulns2(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } +} diff --git a/dd-smoke-tests/play-2.4/build.gradle b/dd-smoke-tests/play-2.4/build.gradle index 13d374d2a24..68671ef52fa 100644 --- a/dd-smoke-tests/play-2.4/build.gradle +++ b/dd-smoke-tests/play-2.4/build.gradle @@ -64,6 +64,10 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') + testImplementation project(':dd-smoke-tests:appsec') + testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) + + implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { diff --git a/dd-smoke-tests/play-2.4/conf/routes b/dd-smoke-tests/play-2.4/conf/routes index 0520cfe1842..d557afc17e7 100644 --- a/dd-smoke-tests/play-2.4/conf/routes +++ b/dd-smoke-tests/play-2.4/conf/routes @@ -9,3 +9,8 @@ GET /welcomes controllers.SController.doGet(id: Op # AppSec endpoints for testing GET /api_security/sampling/:statusCode controllers.AppSecController.apiSecuritySampling(statusCode: Int, test: String) + +# IAST Sampling endpoints +GET /iast/multiple_vulns/:id controllers.IastController.multipleVulns(id: String) +POST /iast/multiple_vulns/:id controllers.IastController.postMultipleVulns(id: String) +GET /iast/multiple_vulns-2/:id controllers.IastController.multipleVulns2(id: String) diff --git a/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy b/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy new file mode 100644 index 00000000000..d26b0dd1ecb --- /dev/null +++ b/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy @@ -0,0 +1,104 @@ +package datadog.smoketest + +import static java.util.concurrent.TimeUnit.SECONDS +import okhttp3.FormBody +import okhttp3.Request +import spock.lang.Shared + +import java.nio.file.Files + + +class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { + + @Shared + File playDirectory = new File("${buildDirectory}/stage/main") + + @Shared + protected String[] defaultIastProperties = [ + "-Ddd.iast.enabled=true", + "-Ddd.iast.detection.mode=DEFAULT", + "-Ddd.iast.debug.enabled=true", + "-Ddd.iast.request-sampling=100", + ] + + @Override + ProcessBuilder createProcessBuilder() { + // If the server is not shut down correctly, this file can be left there and will block + // the start of a new test + def runningPid = new File(playDirectory.getPath(), "RUNNING_PID") + if (runningPid.exists()) { + runningPid.delete() + } + def command = isWindows() ? 'main.bat' : 'main' + ProcessBuilder processBuilder = new ProcessBuilder("${playDirectory}/bin/${command}") + processBuilder.directory(playDirectory) + processBuilder.environment().put("JAVA_OPTS", + //'-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 '+ + (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + + " -Dconfig.file=${playDirectory}/conf/application.conf" + + " -Dhttp.port=${httpPort}" + + " -Dhttp.address=127.0.0.1" + + " -Dplay.server.provider=play.core.server.NettyServerProvider" + + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter") + return processBuilder + } + + @Override + File createTemporaryFile() { + return new File("${buildDirectory}/tmp/trace-structure-play-2.4-iast-netty.out") + } + + void 'Test that all the vulnerabilities are detected'() { + given: + // prepare a list of exactly three GET requests with path and query param + def requests = [] + for (int i = 1; i <= 3; i++) { + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns-2/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}") + .post(new FormBody.Builder().add('param', "value${i}").build()) + .build()) + } + + + when: + requests.each { req -> + client.newCall(req as Request).execute() + } + + then: 'check first get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'RIPEMD128'} + } + + // Ensure to clean up server and not only the shell script that starts it + def cleanupSpec() { + def pid = runningServerPid() + if (pid) { + def commands = isWindows() ? ['taskkill', '/PID', pid, '/T', '/F'] : ['kill', '-9', pid] + new ProcessBuilder(commands).start().waitFor(10, SECONDS) + } + } + + def runningServerPid() { + def runningPid = new File(playDirectory.getPath(), 'RUNNING_PID') + if (runningPid.exists()) { + return Files.lines(runningPid.toPath()).findAny().orElse(null) + } + } + + static isWindows() { + return System.getProperty('os.name').toLowerCase().contains('win') + } + +} diff --git a/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy index 59e7ab08cf0..11491e8d37f 100644 --- a/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy +++ b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy @@ -33,7 +33,7 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { ProcessBuilder processBuilder = new ProcessBuilder("${playDirectory}/bin/${command}") processBuilder.directory(playDirectory) processBuilder.environment().put("JAVA_OPTS", - '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 '+ + //'-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 '+ (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + " -Dconfig.file=${playDirectory}/conf/application.conf" + " -Dhttp.port=${httpPort}" @@ -45,52 +45,41 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { @Override File createTemporaryFile() { - return new File("${buildDirectory}/tmp/trace-structure-play-2.5-appsec-netty.out") + return new File("${buildDirectory}/tmp/trace-structure-play-2.5-iast-netty.out") } - void 'Test that the server starts'() { - expect: - def request = new Request.Builder() - .url("http://localhost:${httpPort}/iast/multiple_vulns/1/?param=value1") - .get() - .build() - def response = client.newCall(request as Request).execute() - assert response.code() == 200, "Response code was ${response.code()} for request ${request}" - } + void 'Test that all the vulnerabilities are detected'() { + given: + // prepare a list of exactly three GET requests with path and query param + def requests = [] + for (int i = 1; i <= 3; i++) { + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns-2/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}") + .post(new FormBody.Builder().add('param', "value${i}").build()) + .build()) + } + - // void 'Test that all the vulnerabilities are detected'() { - // given: - // // prepare a list of exactly three GET requests with path and query param - // def requests = [] - // for (int i = 1; i <= 3; i++) { - // requests.add(new Request.Builder() - // .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}/?param=value${i}") - // .get() - // .build()) - // requests.add(new Request.Builder() - // .url("http://localhost:${httpPort}/iast/multiple_vulns-2/${i}/?param=value${i}") - // .get() - // .build()) - // requests.add(new Request.Builder() - // .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}") - // .post(new FormBody.Builder().add('param', "value${i}").build()) - // .build()) - // } - // - // - // when: - // requests.each { req -> - // def response = client.newCall(req as Request).execute() - // assert response.code() == 200, "Response code was ${response.code()} for request ${req}" - // } - // - // then: 'check first get mapping' - // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'SHA1' } - // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'SHA-1' } - // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'MD2'} - // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'MD5'} - // hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'multipleVulns' && vul.evidence.value == 'RIPEMD128'} - //} + when: + requests.each { req -> + client.newCall(req as Request).execute() + } + + then: 'check first get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'RIPEMD128'} + } // Ensure to clean up server and not only the shell script that starts it def cleanupSpec() { diff --git a/dd-smoke-tests/play-2.6/app/controllers/IastController.scala b/dd-smoke-tests/play-2.6/app/controllers/IastController.scala new file mode 100644 index 00000000000..e94fb95a8d9 --- /dev/null +++ b/dd-smoke-tests/play-2.6/app/controllers/IastController.scala @@ -0,0 +1,48 @@ +package controllers + +import play.api.mvc._ + +import java.nio.charset.StandardCharsets +import java.security.MessageDigest + +class IastController extends Controller { + + def multipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def postMultipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def multipleVulns2(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } +} diff --git a/dd-smoke-tests/play-2.6/build.gradle b/dd-smoke-tests/play-2.6/build.gradle index 48a6b161a3f..1df31bd2c9d 100644 --- a/dd-smoke-tests/play-2.6/build.gradle +++ b/dd-smoke-tests/play-2.6/build.gradle @@ -66,6 +66,7 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') + testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) } configurations.testImplementation { diff --git a/dd-smoke-tests/play-2.6/conf/routes b/dd-smoke-tests/play-2.6/conf/routes index d7f24a29a24..01f31900923 100644 --- a/dd-smoke-tests/play-2.6/conf/routes +++ b/dd-smoke-tests/play-2.6/conf/routes @@ -10,3 +10,8 @@ GET /welcomes controllers.SController.doGet(id: Op # AppSec endpoints for testing GET /api_security/sampling/:statusCode controllers.AppSecController.apiSecuritySampling(statusCode: Int, test: String) POST /api_security/response controllers.AppSecController.apiResponse() + +# IAST Sampling endpoints +GET /iast/multiple_vulns/:id controllers.IastController.multipleVulns(id: String) +POST /iast/multiple_vulns/:id controllers.IastController.postMultipleVulns(id: String) +GET /iast/multiple_vulns-2/:id controllers.IastController.multipleVulns2(id: String) diff --git a/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy new file mode 100644 index 00000000000..173da197bc3 --- /dev/null +++ b/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -0,0 +1,131 @@ +package datadog.smoketest + +import static java.util.concurrent.TimeUnit.SECONDS +import okhttp3.FormBody +import okhttp3.Request +import spock.lang.Shared + +import java.nio.file.Files + +abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { + + @Shared + File playDirectory = new File("${buildDirectory}/stage/main") + + @Shared + protected String[] defaultIastProperties = [ + "-Ddd.iast.enabled=true", + "-Ddd.iast.detection.mode=DEFAULT", + "-Ddd.iast.debug.enabled=true", + "-Ddd.iast.request-sampling=100", + ] + + @Override + ProcessBuilder createProcessBuilder() { + // If the server is not shut down correctly, this file can be left there and will block + // the start of a new test + def runningPid = new File(playDirectory.getPath(), "RUNNING_PID") + if (runningPid.exists()) { + runningPid.delete() + } + def command = isWindows() ? 'main.bat' : 'main' + ProcessBuilder processBuilder = + new ProcessBuilder("${playDirectory}/bin/${command}") + processBuilder.directory(playDirectory) + processBuilder.environment().put("JAVA_OPTS", + (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + + " -Dconfig.file=${playDirectory}/conf/application.conf" + + " -Dhttp.port=${httpPort}" + + " -Dhttp.address=127.0.0.1" + + " -Dplay.server.provider=${serverProvider()}" + + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter") + return processBuilder + } + + @Override + File createTemporaryFile() { + new File("${buildDirectory}/tmp/trace-structure-play-2.6-iast-${serverProviderName()}.out") + } + + abstract String serverProviderName() + + abstract String serverProvider() + + void 'Test that all the vulnerabilities are detected'() { + given: + // prepare a list of exactly three GET requests with path and query param + def requests = [] + for (int i = 1; i <= 3; i++) { + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns-2/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}") + .post(new FormBody.Builder().add('param', "value${i}").build()) + .build()) + } + + + when: + requests.each { req -> + client.newCall(req as Request).execute() + } + + then: 'check first get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + } + + // Ensure to clean up server and not only the shell script that starts it + def cleanupSpec() { + def pid = runningServerPid() + if (pid) { + def commands = isWindows() ? ['taskkill', '/PID', pid, '/T', '/F'] : ['kill', '-9', pid] + new ProcessBuilder(commands).start().waitFor(10, SECONDS) + } + } + + def runningServerPid() { + def runningPid = new File(playDirectory.getPath(), 'RUNNING_PID') + if (runningPid.exists()) { + return Files.lines(runningPid.toPath()).findAny().orElse(null) + } + } + + static isWindows() { + return System.getProperty('os.name').toLowerCase().contains("win") + } + + static class Akka extends IastPlaySmokeTest { + + @Override + String serverProviderName() { + return "akka-http" + } + + @Override + String serverProvider() { + return "play.core.server.AkkaHttpServerProvider" + } + } + + static class Netty extends IastPlaySmokeTest { + @Override + String serverProviderName() { + return "netty" + } + + @Override + String serverProvider() { + return "play.core.server.NettyServerProvider" + } + } +} diff --git a/dd-smoke-tests/play-2.7/app/controllers/IastController.scala b/dd-smoke-tests/play-2.7/app/controllers/IastController.scala new file mode 100644 index 00000000000..e94fb95a8d9 --- /dev/null +++ b/dd-smoke-tests/play-2.7/app/controllers/IastController.scala @@ -0,0 +1,48 @@ +package controllers + +import play.api.mvc._ + +import java.nio.charset.StandardCharsets +import java.security.MessageDigest + +class IastController extends Controller { + + def multipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def postMultipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def multipleVulns2(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } +} diff --git a/dd-smoke-tests/play-2.7/build.gradle b/dd-smoke-tests/play-2.7/build.gradle index eaa36eaaccf..61ec25b4b98 100644 --- a/dd-smoke-tests/play-2.7/build.gradle +++ b/dd-smoke-tests/play-2.7/build.gradle @@ -66,6 +66,9 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') + testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) + + implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { diff --git a/dd-smoke-tests/play-2.7/conf/routes b/dd-smoke-tests/play-2.7/conf/routes index d7f24a29a24..01f31900923 100644 --- a/dd-smoke-tests/play-2.7/conf/routes +++ b/dd-smoke-tests/play-2.7/conf/routes @@ -10,3 +10,8 @@ GET /welcomes controllers.SController.doGet(id: Op # AppSec endpoints for testing GET /api_security/sampling/:statusCode controllers.AppSecController.apiSecuritySampling(statusCode: Int, test: String) POST /api_security/response controllers.AppSecController.apiResponse() + +# IAST Sampling endpoints +GET /iast/multiple_vulns/:id controllers.IastController.multipleVulns(id: String) +POST /iast/multiple_vulns/:id controllers.IastController.postMultipleVulns(id: String) +GET /iast/multiple_vulns-2/:id controllers.IastController.multipleVulns2(id: String) diff --git a/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy new file mode 100644 index 00000000000..e8ece50abd7 --- /dev/null +++ b/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -0,0 +1,131 @@ +package datadog.smoketest + +import static java.util.concurrent.TimeUnit.SECONDS +import okhttp3.FormBody +import okhttp3.Request +import spock.lang.Shared + +import java.nio.file.Files + +abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { + + @Shared + File playDirectory = new File("${buildDirectory}/stage/main") + + @Shared + protected String[] defaultIastProperties = [ + "-Ddd.iast.enabled=true", + "-Ddd.iast.detection.mode=DEFAULT", + "-Ddd.iast.debug.enabled=true", + "-Ddd.iast.request-sampling=100", + ] + + @Override + ProcessBuilder createProcessBuilder() { + // If the server is not shut down correctly, this file can be left there and will block + // the start of a new test + def runningPid = new File(playDirectory.getPath(), "RUNNING_PID") + if (runningPid.exists()) { + runningPid.delete() + } + def command = isWindows() ? 'main.bat' : 'main' + ProcessBuilder processBuilder = + new ProcessBuilder("${playDirectory}/bin/${command}") + processBuilder.directory(playDirectory) + processBuilder.environment().put("JAVA_OPTS", + (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + + " -Dconfig.file=${playDirectory}/conf/application.conf" + + " -Dhttp.port=${httpPort}" + + " -Dhttp.address=127.0.0.1" + + " -Dplay.server.provider=${serverProvider()}" + + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter") + return processBuilder + } + + @Override + File createTemporaryFile() { + new File("${buildDirectory}/tmp/trace-structure-play-2.7-iast-${serverProviderName()}.out") + } + + abstract String serverProviderName() + + abstract String serverProvider() + + void 'Test that all the vulnerabilities are detected'() { + given: + // prepare a list of exactly three GET requests with path and query param + def requests = [] + for (int i = 1; i <= 3; i++) { + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns-2/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}") + .post(new FormBody.Builder().add('param', "value${i}").build()) + .build()) + } + + + when: + requests.each { req -> + client.newCall(req as Request).execute() + } + + then: 'check first get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + } + + // Ensure to clean up server and not only the shell script that starts it + def cleanupSpec() { + def pid = runningServerPid() + if (pid) { + def commands = isWindows() ? ['taskkill', '/PID', pid, '/T', '/F'] : ['kill', '-9', pid] + new ProcessBuilder(commands).start().waitFor(10, SECONDS) + } + } + + def runningServerPid() { + def runningPid = new File(playDirectory.getPath(), 'RUNNING_PID') + if (runningPid.exists()) { + return Files.lines(runningPid.toPath()).findAny().orElse(null) + } + } + + static isWindows() { + return System.getProperty('os.name').toLowerCase().contains("win") + } + + static class Akka extends IastPlaySmokeTest { + + @Override + String serverProviderName() { + return "akka-http" + } + + @Override + String serverProvider() { + return "play.core.server.AkkaHttpServerProvider" + } + } + + static class Netty extends IastPlaySmokeTest { + @Override + String serverProviderName() { + return "netty" + } + + @Override + String serverProvider() { + return "play.core.server.NettyServerProvider" + } + } +} diff --git a/dd-smoke-tests/play-2.8/app/controllers/IastController.scala b/dd-smoke-tests/play-2.8/app/controllers/IastController.scala new file mode 100644 index 00000000000..194405d439c --- /dev/null +++ b/dd-smoke-tests/play-2.8/app/controllers/IastController.scala @@ -0,0 +1,50 @@ +package controllers + +import play.api.mvc._ + +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import javax.inject.{Inject, Singleton} + +@Singleton +class IastController @Inject() (cc: ControllerComponents) extends AbstractController(cc) { + + def multipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def postMultipleVulns(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } + + def multipleVulns2(id: String): Action[AnyContent] = Action { + try { + MessageDigest.getInstance("SHA1").digest("hash1".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("SHA-1").digest("hash2".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD2").digest("hash3".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("MD5").digest("hash4".getBytes(StandardCharsets.UTF_8)) + MessageDigest.getInstance("RIPEMD128").digest("hash5".getBytes(StandardCharsets.UTF_8)) + Ok("ok") + } catch { + case e: Exception => InternalServerError(e.getMessage) + } + } +} diff --git a/dd-smoke-tests/play-2.8/build.gradle b/dd-smoke-tests/play-2.8/build.gradle index 60381e29daa..e6f35115a71 100644 --- a/dd-smoke-tests/play-2.8/build.gradle +++ b/dd-smoke-tests/play-2.8/build.gradle @@ -65,6 +65,9 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') + testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) + + implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { diff --git a/dd-smoke-tests/play-2.8/conf/routes b/dd-smoke-tests/play-2.8/conf/routes index d7f24a29a24..01f31900923 100644 --- a/dd-smoke-tests/play-2.8/conf/routes +++ b/dd-smoke-tests/play-2.8/conf/routes @@ -10,3 +10,8 @@ GET /welcomes controllers.SController.doGet(id: Op # AppSec endpoints for testing GET /api_security/sampling/:statusCode controllers.AppSecController.apiSecuritySampling(statusCode: Int, test: String) POST /api_security/response controllers.AppSecController.apiResponse() + +# IAST Sampling endpoints +GET /iast/multiple_vulns/:id controllers.IastController.multipleVulns(id: String) +POST /iast/multiple_vulns/:id controllers.IastController.postMultipleVulns(id: String) +GET /iast/multiple_vulns-2/:id controllers.IastController.multipleVulns2(id: String) diff --git a/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy new file mode 100644 index 00000000000..62bf10d0077 --- /dev/null +++ b/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -0,0 +1,131 @@ +package datadog.smoketest + +import static java.util.concurrent.TimeUnit.SECONDS +import okhttp3.FormBody +import okhttp3.Request +import spock.lang.Shared + +import java.nio.file.Files + +abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { + + @Shared + File playDirectory = new File("${buildDirectory}/stage/main") + + @Shared + protected String[] defaultIastProperties = [ + "-Ddd.iast.enabled=true", + "-Ddd.iast.detection.mode=DEFAULT", + "-Ddd.iast.debug.enabled=true", + "-Ddd.iast.request-sampling=100", + ] + + @Override + ProcessBuilder createProcessBuilder() { + // If the server is not shut down correctly, this file can be left there and will block + // the start of a new test + def runningPid = new File(playDirectory.getPath(), "RUNNING_PID") + if (runningPid.exists()) { + runningPid.delete() + } + def command = isWindows() ? 'main.bat' : 'main' + ProcessBuilder processBuilder = + new ProcessBuilder("${playDirectory}/bin/${command}") + processBuilder.directory(playDirectory) + processBuilder.environment().put("JAVA_OPTS", + (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + + " -Dconfig.file=${playDirectory}/conf/application.conf" + + " -Dhttp.port=${httpPort}" + + " -Dhttp.address=127.0.0.1" + + " -Dplay.server.provider=${serverProvider()}" + + " -Ddd.writer.type=MultiWriter:TraceStructureWriter:${output.getAbsolutePath()},DDAgentWriter") + return processBuilder + } + + @Override + File createTemporaryFile() { + new File("${buildDirectory}/tmp/trace-structure-play-2.8-iast-${serverProviderName()}.out") + } + + abstract String serverProviderName() + + abstract String serverProvider() + + void 'Test that all the vulnerabilities are detected'() { + given: + // prepare a list of exactly three GET requests with path and query param + def requests = [] + for (int i = 1; i <= 3; i++) { + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns-2/${i}?param=value${i}") + .get() + .build()) + requests.add(new Request.Builder() + .url("http://localhost:${httpPort}/iast/multiple_vulns/${i}") + .post(new FormBody.Builder().add('param', "value${i}").build()) + .build()) + } + + + when: + requests.each { req -> + client.newCall(req as Request).execute() + } + + then: 'check first get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + } + + // Ensure to clean up server and not only the shell script that starts it + def cleanupSpec() { + def pid = runningServerPid() + if (pid) { + def commands = isWindows() ? ['taskkill', '/PID', pid, '/T', '/F'] : ['kill', '-9', pid] + new ProcessBuilder(commands).start().waitFor(10, SECONDS) + } + } + + def runningServerPid() { + def runningPid = new File(playDirectory.getPath(), 'RUNNING_PID') + if (runningPid.exists()) { + return Files.lines(runningPid.toPath()).findAny().orElse(null) + } + } + + static isWindows() { + return System.getProperty('os.name').toLowerCase().contains("win") + } + + static class Akka extends IastPlaySmokeTest { + + @Override + String serverProviderName() { + return "akka-http" + } + + @Override + String serverProvider() { + return "play.core.server.AkkaHttpServerProvider" + } + } + + static class Netty extends IastPlaySmokeTest { + @Override + String serverProviderName() { + return "netty" + } + + @Override + String serverProvider() { + return "play.core.server.NettyServerProvider" + } + } +} From 22fbd3fa61dd2050d8129ae794ed530defcad897 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Mon, 7 Jul 2025 07:49:20 +0200 Subject: [PATCH 04/11] WIP --- .../smoketest/IastPlayNettySmokeTest.groovy | 26 ++++++++++++++----- .../smoketest/IastPlayNettySmokeTest.groovy | 26 ++++++++++++++----- .../smoketest/IastPlaySmokeTest.groovy | 21 ++++++++++++--- .../smoketest/IastPlaySmokeTest.groovy | 22 ++++++++++++---- .../smoketest/IastPlaySmokeTest.groovy | 21 ++++++++++++--- 5 files changed, 89 insertions(+), 27 deletions(-) diff --git a/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy b/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy index d26b0dd1ecb..854a4b149de 100644 --- a/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy +++ b/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy @@ -33,7 +33,6 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { ProcessBuilder processBuilder = new ProcessBuilder("${playDirectory}/bin/${command}") processBuilder.directory(playDirectory) processBuilder.environment().put("JAVA_OPTS", - //'-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 '+ (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + " -Dconfig.file=${playDirectory}/conf/application.conf" + " -Dhttp.port=${httpPort}" @@ -50,7 +49,6 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { void 'Test that all the vulnerabilities are detected'() { given: - // prepare a list of exactly three GET requests with path and query param def requests = [] for (int i = 1; i <= 3; i++) { requests.add(new Request.Builder() @@ -74,11 +72,25 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { } then: 'check first get mapping' - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA-1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD2'} - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD5'} - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'RIPEMD128'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check first post mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check second get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'RIPEMD128'} } // Ensure to clean up server and not only the shell script that starts it diff --git a/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy index 11491e8d37f..bbf93600738 100644 --- a/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy +++ b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy @@ -33,7 +33,6 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { ProcessBuilder processBuilder = new ProcessBuilder("${playDirectory}/bin/${command}") processBuilder.directory(playDirectory) processBuilder.environment().put("JAVA_OPTS", - //'-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 '+ (defaultIastProperties + defaultJavaProperties).collect({ it.replace(' ', '\\ ')}).join(" ") + " -Dconfig.file=${playDirectory}/conf/application.conf" + " -Dhttp.port=${httpPort}" @@ -50,7 +49,6 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { void 'Test that all the vulnerabilities are detected'() { given: - // prepare a list of exactly three GET requests with path and query param def requests = [] for (int i = 1; i <= 3; i++) { requests.add(new Request.Builder() @@ -74,11 +72,25 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { } then: 'check first get mapping' - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'SHA-1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD2'} - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'MD5'} - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == 'apply' && vul.evidence.value == 'RIPEMD128'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check first post mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$postMultipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check second get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns2$1' && vul.evidence.value == 'RIPEMD128'} } // Ensure to clean up server and not only the shell script that starts it diff --git a/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy index 173da197bc3..507af4a8da6 100644 --- a/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy +++ b/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -53,7 +53,6 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { void 'Test that all the vulnerabilities are detected'() { given: - // prepare a list of exactly three GET requests with path and query param def requests = [] for (int i = 1; i <= 3; i++) { requests.add(new Request.Builder() @@ -78,10 +77,24 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check first post mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check second get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'RIPEMD128'} } // Ensure to clean up server and not only the shell script that starts it diff --git a/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy index e8ece50abd7..2cd5a35bb25 100644 --- a/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy +++ b/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -53,7 +53,6 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { void 'Test that all the vulnerabilities are detected'() { given: - // prepare a list of exactly three GET requests with path and query param def requests = [] for (int i = 1; i <= 3; i++) { requests.add(new Request.Builder() @@ -78,11 +77,24 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} - } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check first post mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check second get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'RIPEMD128'}} // Ensure to clean up server and not only the shell script that starts it def cleanupSpec() { diff --git a/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy index 62bf10d0077..0fa7964eff7 100644 --- a/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy +++ b/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -53,7 +53,6 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { void 'Test that all the vulnerabilities are detected'() { given: - // prepare a list of exactly three GET requests with path and query param def requests = [] for (int i = 1; i <= 3; i++) { requests.add(new Request.Builder() @@ -78,10 +77,24 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD2'} hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'MD5'} - hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check first post mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$postMultipleVulns$1' && vul.evidence.value == 'RIPEMD128'} + + then: 'check second get mapping' + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'SHA-1' } + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD2'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'MD5'} + hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns2$1' && vul.evidence.value == 'RIPEMD128'} } // Ensure to clean up server and not only the shell script that starts it From 418be3b2458af85a161f4e2a88b14ec0ed1cae01 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Mon, 7 Jul 2025 09:14:08 +0200 Subject: [PATCH 05/11] WIP --- dd-smoke-tests/play-2.4/build.gradle | 2 -- dd-smoke-tests/play-2.5/build.gradle | 2 -- dd-smoke-tests/play-2.7/build.gradle | 2 -- dd-smoke-tests/play-2.8/build.gradle | 2 -- 4 files changed, 8 deletions(-) diff --git a/dd-smoke-tests/play-2.4/build.gradle b/dd-smoke-tests/play-2.4/build.gradle index 68671ef52fa..c58f368e4df 100644 --- a/dd-smoke-tests/play-2.4/build.gradle +++ b/dd-smoke-tests/play-2.4/build.gradle @@ -64,9 +64,7 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') - testImplementation project(':dd-smoke-tests:appsec') testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) - implementation project(':dd-smoke-tests:iast-util') } diff --git a/dd-smoke-tests/play-2.5/build.gradle b/dd-smoke-tests/play-2.5/build.gradle index 84cb1027d05..aab6591da6b 100644 --- a/dd-smoke-tests/play-2.5/build.gradle +++ b/dd-smoke-tests/play-2.5/build.gradle @@ -67,8 +67,6 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) - - implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { diff --git a/dd-smoke-tests/play-2.7/build.gradle b/dd-smoke-tests/play-2.7/build.gradle index 61ec25b4b98..a89aed0fdad 100644 --- a/dd-smoke-tests/play-2.7/build.gradle +++ b/dd-smoke-tests/play-2.7/build.gradle @@ -67,8 +67,6 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) - - implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { diff --git a/dd-smoke-tests/play-2.8/build.gradle b/dd-smoke-tests/play-2.8/build.gradle index e6f35115a71..3a114595721 100644 --- a/dd-smoke-tests/play-2.8/build.gradle +++ b/dd-smoke-tests/play-2.8/build.gradle @@ -66,8 +66,6 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) - - implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { From 58a6f399c40974612ae4aefc241c2df5cb176fe0 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Mon, 7 Jul 2025 15:10:18 +0200 Subject: [PATCH 06/11] change approach to on request enter --- .../instrumentation/play24/PlayAdvice.java | 1 + .../play24/PlayHttpServerDecorator.java | 53 +++++++++------- .../instrumentation/play26/PlayAdvice.java | 1 + .../play26/PlayHttpServerDecorator.java | 63 ++++++++++++------- .../AbstractIastServerSmokeTest.groovy | 11 ++++ .../smoketest/IastPlayNettySmokeTest.groovy | 3 + .../smoketest/IastPlayNettySmokeTest.groovy | 3 + .../smoketest/IastPlaySmokeTest.groovy | 3 + .../smoketest/IastPlaySmokeTest.groovy | 3 + .../smoketest/IastPlaySmokeTest.groovy | 3 + 10 files changed, 100 insertions(+), 44 deletions(-) diff --git a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java index 380eb2b9f91..59a6187da31 100644 --- a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java +++ b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java @@ -45,6 +45,7 @@ public static ContextScope onEnter(@Advice.Argument(value = 0, readOnly = false) DECORATE.afterStart(span); req = RequestHelper.withTag(req, "_dd_HasPlayRequestSpan", "true"); + DECORATE.dispatchRoute(span, req); return scope; } diff --git a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java index 4aa8a27cf98..4e648ac0724 100644 --- a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java @@ -96,7 +96,6 @@ public AgentSpan onRequest( if (!pathOption.isEmpty()) { final String path = (String) pathOption.get(); HTTP_RESOURCE_DECORATOR.withRoute(span, request.method(), path); - dispatchRoute(span, path); } } return span; @@ -106,30 +105,38 @@ public AgentSpan onRequest( * Play does not set the http.route in the local root span so we need to store it in the context * for API security */ - private void dispatchRoute(final AgentSpan span, final String route) { - try { - final RequestContext ctx = span.getRequestContext(); - if (ctx == null) { - return; - } - // Send event to AppSec provider - final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - if (cbp != null) { - final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, route); - } - } - // Send event to IAST provider - final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); - if (cbpIast != null) { - final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, route); + public void dispatchRoute(final AgentSpan span, final Request request) { + if (request != null) { + // more about routes here: + // https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md#router-tags-are-now-attributes + final Option pathOption = request.tags().get("ROUTE_PATTERN"); + if (!pathOption.isEmpty()) { + final String path = (String) pathOption.get(); + try { + final RequestContext ctx = span.getRequestContext(); + if (ctx == null) { + return; + } + // Send event to AppSec provider + final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); + if (cbp != null) { + final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, path); + } + } + // Send event to IAST provider + final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpIast != null) { + final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, path); + } + } + } catch (final Throwable t) { + LOG.debug("Failed to dispatch route", t); } } - } catch (final Throwable t) { - LOG.debug("Failed to dispatch route", t); } } diff --git a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java index 62fe3b1dece..376b7c8a796 100644 --- a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java +++ b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java @@ -46,6 +46,7 @@ public static ContextScope onEnter( DECORATE.afterStart(span); req = req.addAttr(HasPlayRequestSpan.KEY, HasPlayRequestSpan.INSTANCE); + DECORATE.dispatchRoute(span, req); return scope; } diff --git a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java index 5f31d3d5e36..82773af9427 100644 --- a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java @@ -152,7 +152,6 @@ public AgentSpan onRequest( PATH_CACHE.computeIfAbsent( defOption.get().path(), p -> addMissingSlash(p, request.path())); HTTP_RESOURCE_DECORATOR.withRoute(span, request.method(), path, true); - dispatchRoute(span, path); } } return span; @@ -162,30 +161,51 @@ public AgentSpan onRequest( * Play does not set the http.route in the local root span so we need to store it in the context * for API security */ - private void dispatchRoute(final AgentSpan span, final CharSequence route) { - try { - final RequestContext ctx = span.getRequestContext(); - if (ctx == null) { - return; - } - // Send event to AppSec provider - final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - if (cbp != null) { - final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, URIUtils.decode(route.toString())); + public void dispatchRoute(final AgentSpan span, final Request request) { + if (request != null) { + // more about routes here: + // https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md + Option defOption = Option.empty(); + if (TYPED_KEY_GET_UNDERLYING != null) { // Should always be non-null but just to make sure + try { + defOption = + request + .attrs() + .get( + (play.api.libs.typedmap.TypedKey) + TYPED_KEY_GET_UNDERLYING.invokeExact(Router.Attrs.HANDLER_DEF)); + } catch (final Throwable ignored) { } } - // Send event to IAST provider - final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); - if (cbpIast != null) { - final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, URIUtils.decode(route.toString())); + if (!defOption.isEmpty()) { + CharSequence path = + PATH_CACHE.computeIfAbsent( + defOption.get().path(), p -> addMissingSlash(p, request.path())); + try { + final RequestContext ctx = span.getRequestContext(); + if (ctx == null) { + return; + } + // Send event to IAST provider + final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpIast != null) { + final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, URIUtils.decode(path.toString())); + } + } + // Send event to AppSec provider + final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); + if (cbp != null) { + final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, URIUtils.decode(path.toString())); + } + } + } catch (final Throwable t) { + LOG.debug("Failed to dispatch route", t); } } - } catch (final Throwable t) { - LOG.debug("Failed to dispatch route", t); } } @@ -215,6 +235,7 @@ private CharSequence addMissingSlash(String routePath, String rawPath) { @Override public AgentSpan onError(final AgentSpan span, Throwable throwable) { + LOG.info("[TEST] onError called with throwable: {}", throwable.getMessage()); if (REPORT_HTTP_STATUS) { span.setHttpStatusCode(500); } diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy index d85d3468ef2..74f87d011e7 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy @@ -111,6 +111,17 @@ abstract class AbstractIastServerSmokeTest extends AbstractServerSmokeTest { } } + protected void hasMeta(final String name) { + final found = [] + try { + waitForSpan(pollingConditions()) { span -> + return span.meta.containsKey(name) + } + } catch (SpockTimeoutError toe) { + throw new AssertionError("No matching metric with name $name found. Metrics found: ${new JsonBuilder(found).toPrettyString()}") + } + } + protected void hasVulnerability(@ClosureParams(value = SimpleType, options = ['datadog.smoketest.model.Vulnerability']) final Closure matcher) { final found = [] diff --git a/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy b/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy index 854a4b149de..e213493dbb6 100644 --- a/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy +++ b/dd-smoke-tests/play-2.4/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy @@ -71,6 +71,9 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { client.newCall(req as Request).execute() } + then: 'check has route dispatched' + hasMeta('http.route') + then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } diff --git a/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy index bbf93600738..5cd1c795bbd 100644 --- a/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy +++ b/dd-smoke-tests/play-2.5/src/test/groovy/datadog/smoketest/IastPlayNettySmokeTest.groovy @@ -71,6 +71,9 @@ class IastPlayNettySmokeTest extends AbstractIastServerSmokeTest { client.newCall(req as Request).execute() } + then: 'check has route dispatched' + hasMeta('http.route') + then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.path == 'controllers.IastController$$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } diff --git a/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy index 507af4a8da6..7fc17183d62 100644 --- a/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy +++ b/dd-smoke-tests/play-2.6/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -75,6 +75,9 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { client.newCall(req as Request).execute() } + then: 'check has route dispatched' + hasMeta('http.route') + then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } diff --git a/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy index 2cd5a35bb25..c97f9682c02 100644 --- a/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy +++ b/dd-smoke-tests/play-2.7/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -75,6 +75,9 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { client.newCall(req as Request).execute() } + then: 'check has route dispatched' + hasMeta('http.route') + then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } diff --git a/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy b/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy index 0fa7964eff7..d518217fae6 100644 --- a/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy +++ b/dd-smoke-tests/play-2.8/src/test/groovy/datadog/smoketest/IastPlaySmokeTest.groovy @@ -75,6 +75,9 @@ abstract class IastPlaySmokeTest extends AbstractIastServerSmokeTest { client.newCall(req as Request).execute() } + then: 'check has route dispatched' + hasMeta('http.route') + then: 'check first get mapping' hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA1' } hasVulnerability { vul -> vul.type == 'WEAK_HASH' && vul.location.method == '$anonfun$multipleVulns$1' && vul.evidence.value == 'SHA-1' } From 411682ff7a449c21946130e448520f2e5c67854c Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Tue, 8 Jul 2025 09:43:56 +0200 Subject: [PATCH 07/11] try simple approach moving dispatchRoute to on request enter --- .../instrumentation/play24/PlayAdvice.java | 6 +- .../play24/PlayHttpServerDecorator.java | 53 +++++++--------- .../instrumentation/play26/PlayAdvice.java | 7 +-- .../play26/PlayHttpServerDecorator.java | 63 +++++++------------ 4 files changed, 49 insertions(+), 80 deletions(-) diff --git a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java index 59a6187da31..09af8dabcc2 100644 --- a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java +++ b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java @@ -45,7 +45,8 @@ public static ContextScope onEnter(@Advice.Argument(value = 0, readOnly = false) DECORATE.afterStart(span); req = RequestHelper.withTag(req, "_dd_HasPlayRequestSpan", "true"); - DECORATE.dispatchRoute(span, req); + // Call onRequest on return after tags are populated. + DECORATE.onRequest(span, req, req, (AgentSpanContext.Extracted) null); return scope; } @@ -64,9 +65,6 @@ public static void stopTraceOnResponse( final AgentSpan playControllerSpan = spanFromContext(playControllerScope.context()); - // Call onRequest on return after tags are populated. - DECORATE.onRequest(playControllerSpan, req, req, (AgentSpanContext.Extracted) null); - if (throwable == null) { responseFuture.onComplete( new RequestCompleteCallback(playControllerSpan), diff --git a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java index 4e648ac0724..4aa8a27cf98 100644 --- a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayHttpServerDecorator.java @@ -96,6 +96,7 @@ public AgentSpan onRequest( if (!pathOption.isEmpty()) { final String path = (String) pathOption.get(); HTTP_RESOURCE_DECORATOR.withRoute(span, request.method(), path); + dispatchRoute(span, path); } } return span; @@ -105,38 +106,30 @@ public AgentSpan onRequest( * Play does not set the http.route in the local root span so we need to store it in the context * for API security */ - public void dispatchRoute(final AgentSpan span, final Request request) { - if (request != null) { - // more about routes here: - // https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md#router-tags-are-now-attributes - final Option pathOption = request.tags().get("ROUTE_PATTERN"); - if (!pathOption.isEmpty()) { - final String path = (String) pathOption.get(); - try { - final RequestContext ctx = span.getRequestContext(); - if (ctx == null) { - return; - } - // Send event to AppSec provider - final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - if (cbp != null) { - final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, path); - } - } - // Send event to IAST provider - final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); - if (cbpIast != null) { - final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, path); - } - } - } catch (final Throwable t) { - LOG.debug("Failed to dispatch route", t); + private void dispatchRoute(final AgentSpan span, final String route) { + try { + final RequestContext ctx = span.getRequestContext(); + if (ctx == null) { + return; + } + // Send event to AppSec provider + final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); + if (cbp != null) { + final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, route); + } + } + // Send event to IAST provider + final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpIast != null) { + final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, route); } } + } catch (final Throwable t) { + LOG.debug("Failed to dispatch route", t); } } diff --git a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java index 376b7c8a796..c7c2a958be6 100644 --- a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java +++ b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java @@ -46,7 +46,9 @@ public static ContextScope onEnter( DECORATE.afterStart(span); req = req.addAttr(HasPlayRequestSpan.KEY, HasPlayRequestSpan.INSTANCE); - DECORATE.dispatchRoute(span, req); + + // Call onRequest on return after tags are populated. + DECORATE.onRequest(span, req, req, extractedContext); return scope; } @@ -66,9 +68,6 @@ public static void stopTraceOnResponse( final AgentSpan playControllerSpan = spanFromContext(playControllerScope.context()); - // Call onRequest on return after tags are populated. - DECORATE.onRequest(playControllerSpan, req, req, extractedContext); - if (throwable == null) { responseFuture.onComplete( new RequestCompleteCallback(playControllerSpan), diff --git a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java index 82773af9427..1979bebad98 100644 --- a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayHttpServerDecorator.java @@ -152,6 +152,7 @@ public AgentSpan onRequest( PATH_CACHE.computeIfAbsent( defOption.get().path(), p -> addMissingSlash(p, request.path())); HTTP_RESOURCE_DECORATOR.withRoute(span, request.method(), path, true); + dispatchRoute(span, path); } } return span; @@ -161,51 +162,30 @@ public AgentSpan onRequest( * Play does not set the http.route in the local root span so we need to store it in the context * for API security */ - public void dispatchRoute(final AgentSpan span, final Request request) { - if (request != null) { - // more about routes here: - // https://github.com/playframework/playframework/blob/master/documentation/manual/releases/release26/migration26/Migration26.md - Option defOption = Option.empty(); - if (TYPED_KEY_GET_UNDERLYING != null) { // Should always be non-null but just to make sure - try { - defOption = - request - .attrs() - .get( - (play.api.libs.typedmap.TypedKey) - TYPED_KEY_GET_UNDERLYING.invokeExact(Router.Attrs.HANDLER_DEF)); - } catch (final Throwable ignored) { + private void dispatchRoute(final AgentSpan span, final CharSequence route) { + try { + final RequestContext ctx = span.getRequestContext(); + if (ctx == null) { + return; + } + // Send event to IAST provider + final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); + if (cbpIast != null) { + final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, URIUtils.decode(route.toString())); } } - if (!defOption.isEmpty()) { - CharSequence path = - PATH_CACHE.computeIfAbsent( - defOption.get().path(), p -> addMissingSlash(p, request.path())); - try { - final RequestContext ctx = span.getRequestContext(); - if (ctx == null) { - return; - } - // Send event to IAST provider - final CallbackProvider cbpIast = tracer().getCallbackProvider(RequestContextSlot.IAST); - if (cbpIast != null) { - final BiConsumer cb = cbpIast.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, URIUtils.decode(path.toString())); - } - } - // Send event to AppSec provider - final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); - if (cbp != null) { - final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); - if (cb != null) { - cb.accept(ctx, URIUtils.decode(path.toString())); - } - } - } catch (final Throwable t) { - LOG.debug("Failed to dispatch route", t); + // Send event to AppSec provider + final CallbackProvider cbp = tracer().getCallbackProvider(RequestContextSlot.APPSEC); + if (cbp != null) { + final BiConsumer cb = cbp.getCallback(EVENTS.httpRoute()); + if (cb != null) { + cb.accept(ctx, URIUtils.decode(route.toString())); } } + } catch (final Throwable t) { + LOG.debug("Failed to dispatch route", t); } } @@ -235,7 +215,6 @@ private CharSequence addMissingSlash(String routePath, String rawPath) { @Override public AgentSpan onError(final AgentSpan span, Throwable throwable) { - LOG.info("[TEST] onError called with throwable: {}", throwable.getMessage()); if (REPORT_HTTP_STATUS) { span.setHttpStatusCode(500); } From ecab2f537df0c000fe9db6f47e016f3ef2f732c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez=20Garc=C3=ADa?= Date: Tue, 8 Jul 2025 11:15:18 +0200 Subject: [PATCH 08/11] Update dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy --- .../groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy index 74f87d011e7..1759e648150 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy @@ -118,7 +118,7 @@ abstract class AbstractIastServerSmokeTest extends AbstractServerSmokeTest { return span.meta.containsKey(name) } } catch (SpockTimeoutError toe) { - throw new AssertionError("No matching metric with name $name found. Metrics found: ${new JsonBuilder(found).toPrettyString()}") + throw new AssertionError("No matching meta with name $name found") } } From 664ea78c1365dfc1e49b8eba312d3db6055501b8 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Tue, 8 Jul 2025 12:37:24 +0200 Subject: [PATCH 09/11] fix codenarc --- .../groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy index 1759e648150..adbad18dac7 100644 --- a/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy +++ b/dd-smoke-tests/iast-util/src/testFixtures/groovy/datadog/smoketest/AbstractIastServerSmokeTest.groovy @@ -112,7 +112,6 @@ abstract class AbstractIastServerSmokeTest extends AbstractServerSmokeTest { } protected void hasMeta(final String name) { - final found = [] try { waitForSpan(pollingConditions()) { span -> return span.meta.containsKey(name) From 39bf456bd7394c6db39111b9b3cba7f65be35f09 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 9 Jul 2025 10:24:57 +0200 Subject: [PATCH 10/11] remove dependency --- dd-smoke-tests/play-2.4/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/dd-smoke-tests/play-2.4/build.gradle b/dd-smoke-tests/play-2.4/build.gradle index c58f368e4df..648246b815e 100644 --- a/dd-smoke-tests/play-2.4/build.gradle +++ b/dd-smoke-tests/play-2.4/build.gradle @@ -65,7 +65,6 @@ dependencies { testImplementation project(':dd-smoke-tests') testImplementation project(':dd-smoke-tests:appsec') testImplementation(testFixtures(project(":dd-smoke-tests:iast-util"))) - implementation project(':dd-smoke-tests:iast-util') } configurations.testImplementation { From ff31a0f8288c66b025064bb1920e96b85fc4540d Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Thu, 10 Jul 2025 11:00:38 +0200 Subject: [PATCH 11/11] Add comments --- .../java/datadog/trace/instrumentation/play24/PlayAdvice.java | 2 ++ .../java/datadog/trace/instrumentation/play26/PlayAdvice.java | 1 + 2 files changed, 3 insertions(+) diff --git a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java index 09af8dabcc2..79f4ad4fcd2 100644 --- a/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java +++ b/dd-java-agent/instrumentation/play-2.4/src/main/java/datadog/trace/instrumentation/play24/PlayAdvice.java @@ -45,6 +45,8 @@ public static ContextScope onEnter(@Advice.Argument(value = 0, readOnly = false) DECORATE.afterStart(span); req = RequestHelper.withTag(req, "_dd_HasPlayRequestSpan", "true"); + + // Moved from OnMethodExit // Call onRequest on return after tags are populated. DECORATE.onRequest(span, req, req, (AgentSpanContext.Extracted) null); diff --git a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java index c7c2a958be6..e967ccc97e9 100644 --- a/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java +++ b/dd-java-agent/instrumentation/play-2.6/src/main/java/datadog/trace/instrumentation/play26/PlayAdvice.java @@ -47,6 +47,7 @@ public static ContextScope onEnter( req = req.addAttr(HasPlayRequestSpan.KEY, HasPlayRequestSpan.INSTANCE); + // Moved from OnMethodExit // Call onRequest on return after tags are populated. DECORATE.onRequest(span, req, req, extractedContext);