Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c1a27c6
Instrument Jetty for server.request.body.filenames
jandro996 Mar 23, 2026
5287130
Fix GetFilenamesAdvice double-firing and extend coverage to getParts(…
jandro996 Apr 6, 2026
b9f3a0e
spotless
jandro996 Apr 7, 2026
10deae5
Extend testBodyFilenamesCalledOnce coverage to Jetty 9.x and 10.x
jandro996 Apr 7, 2026
b13202c
Add BODY_MULTIPART_COMBINED test to cover GetFilenamesFromMultiPartAd…
jandro996 Apr 8, 2026
a7b2f6a
spotless
jandro996 Apr 8, 2026
dea7714
Fix missing static imports for BODY_MULTIPART_REPEATED and BODY_MULTI…
jandro996 Apr 8, 2026
2a316df
Fix GetFilenamesAdvice double-fire for Jetty 9.4+ where _multiParts r…
jandro996 Apr 8, 2026
858d76b
Fix GetFilenamesAdvice double-fire in jetty-appsec-8.1.3
jandro996 Apr 8, 2026
8c3d467
Fix GetFilenamesAdvice double-fire for all Jetty 9.3–11 versions
jandro996 Apr 8, 2026
8bf5030
Simplify GetFilenamesAdvice in jetty-appsec-8.1.3: remove dead Servle…
jandro996 Apr 9, 2026
d882c06
Remove unnecessary casts in Jetty AppSec GetFilenamesAdvice
jandro996 Apr 9, 2026
1c569fe
Extract Content-Disposition parsing to MultipartHelper + unit tests
jandro996 Apr 9, 2026
c5454af
Extract filename extraction to MultipartHelper in jetty-appsec-9.2 + …
jandro996 Apr 9, 2026
49e579e
Split jetty-appsec-9.3 [9.3,12) into three clean modules: 9.3, 9.4, 11.0
jandro996 Apr 10, 2026
2f1ec31
Add Jetty 8.x integration tests for multipart body and filenames
jandro996 Apr 10, 2026
42c1d93
Fix muzzle range for jetty-appsec-9.3/9.4: split at 9.4.10
jandro996 Apr 10, 2026
735479c
Replace MultiPartsFieldMatcher with typed module splits for Jetty 9.4…
jandro996 Apr 10, 2026
4ed88de
Fix CI failures and reduce agent JAR size for Jetty filenames PR
jandro996 Apr 13, 2026
2bc1a54
Remove redundant .or(named("getParts")) from ExtractContentParameters…
jandro996 Apr 13, 2026
23a412e
Disable testBodyFilenames in jetty-server-9.0 and jetty-server-9.0.4
jandro996 Apr 13, 2026
810280e
Restore .or(named("getParts")) in ExtractContentParametersAdvice for …
jandro996 Apr 15, 2026
84f677b
fix(appsec/jetty): use _dispatcherType bytecode discriminator for jet…
jandro996 Apr 16, 2026
5030de0
Skip multipart test on Jetty 9.0.x where jetty-appsec-8.1.3 causes HT…
jandro996 Apr 16, 2026
3004efb
Use @Ignore to skip multipart test on Jetty 9.0.x where jetty-appsec-…
jandro996 Apr 16, 2026
9c6141d
Add supportsMultipart() hook to skip multipart test on Jetty 9.0.x
jandro996 Apr 16, 2026
9e0030a
fix(appsec/jetty): disable multipart test on Jetty 9.0.x and bump tes…
jandro996 Apr 17, 2026
06d73c3
fix(appsec/jetty): replace bytecode injection in jetty-appsec-8.1.3 w…
jandro996 Apr 17, 2026
ca7d35e
test(appsec/jetty): add unit tests for PartHelper (jetty-appsec-8.1.3)
jandro996 Apr 20, 2026
48ace04
fix(appsec/jetty): cover Jetty 10.0.0-10.0.9 and close PartHelper Inp…
jandro996 Apr 20, 2026
0ef2c2a
fix(appsec/jetty): quote-aware Content-Disposition parser in PartHelper
jandro996 Apr 20, 2026
e0cc785
fix(appsec/jetty): use Request bytecode discriminator for jetty-appse…
jandro996 Apr 20, 2026
5a83b59
feat(appsec): advise getPart(String) in Jetty 8 to catch single-part …
jandro996 Apr 20, 2026
889e5e2
fix(appsec): guard Jetty 8 getParts against repeated calls; disable f…
jandro996 Apr 20, 2026
b98d33b
fix(appsec): distinguish empty filename from absent filename in PartH…
jandro996 Apr 20, 2026
976b474
test(appsec): add earlyDep10ForkedTest to cover Jetty 10.0.0–10.0.9
jandro996 Apr 21, 2026
00f5e11
refactor(appsec/jetty): extract fire-event logic into helpers; fix AS…
jandro996 Apr 23, 2026
035c987
fix(appsec/jetty8): prevent double-firing filename event when getPart…
jandro996 Apr 23, 2026
db757f0
fix(appsec/jetty8): correct partPeek condition — use == 0 instead of …
jandro996 Apr 24, 2026
c2389df
fix(appsec/jetty8): inspect all cached parts in GetPartAdvice, not ju…
jandro996 Apr 24, 2026
551aca2
fix(appsec/jetty8): guard GetPartAdvice against repeated getPart() calls
jandro996 Apr 24, 2026
ba46253
fix(appsec/jetty8): respect part Content-Type charset in extractFormF…
jandro996 Apr 24, 2026
0e2cde8
fix(appsec/jetty10): regenerate lockfile and force 10.0.9 for earlyDe…
jandro996 Apr 24, 2026
0c5e684
fix(appsec/jetty): pendingBlock pattern, jetty9.2 filenames, tryCommi…
jandro996 Apr 30, 2026
274a688
test(appsec/jetty): migrate 8 new Groovy test files to Java/JUnit 5
jandro996 May 4, 2026
bc94c1f
test(appsec/jetty): delete the 8 replaced Groovy test files
jandro996 May 4, 2026
8a6ea3a
fix(appsec/jetty): add catch comments and remove dead call-depth decr…
jandro996 Jun 8, 2026
9cc2ada
refactor(appsec/jetty): address review comments from amarziali
jandro996 Jun 15, 2026
c9581c7
fix(appsec/jetty8): duck-typed reflection fallback in getAllParts() w…
jandro996 Jun 15, 2026
5453ee0
fix(appsec/jetty11): revert GetFilenamesAdvice skip-increment optimiz…
jandro996 Jun 15, 2026
bb8c73f
refactor(appsec/jetty8): remove duck-typed fallback from getAllParts,…
jandro996 Jun 16, 2026
4efec4e
fix(appsec/jetty8): add log.debug to all PartHelper catch blocks
jandro996 Jun 16, 2026
95dbe3d
fix(appsec/jetty8): replace increment+decrement peek with getCallDepth
jandro996 Jun 17, 2026
d9d7c56
fix(appsec/jetty): use MultipartHelper.class as CallDepthThreadLocalM…
jandro996 Jun 19, 2026
5b9c778
Merge branch 'master' into alejandro.gonzalez/APPSEC-61873-3
jandro996 Jun 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,13 @@ abstract class AppSecInactiveHttpServerTest extends WithHttpServer {
}
}

protected boolean supportsMultipart() {
true
}

void multipart() {
setup:
assumeTrue supportsMultipart()
def body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart('a', 'x')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import java.util.function.Supplier

import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_JSON
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART_COMBINED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_MULTIPART_REPEATED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.BODY_URLENCODED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.CREATED
import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.CREATED_IS
Expand Down Expand Up @@ -369,6 +371,14 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
false
}

boolean testBodyFilenamesCalledOnce() {
false
}

boolean testBodyFilenamesCalledOnceCombined() {
false
}

boolean testBodyFilenames() {
false
}
Expand Down Expand Up @@ -481,6 +491,8 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
CREATED_IS("created_input_stream", 201, "created"),
BODY_URLENCODED("body-urlencoded?ignore=pair", 200, '[a:[x]]'),
BODY_MULTIPART("body-multipart?ignore=pair", 200, '[a:[x]]'),
BODY_MULTIPART_REPEATED("body-multipart-repeated", 200, "ok"),
BODY_MULTIPART_COMBINED("body-multipart-combined", 200, "ok"),
BODY_JSON("body-json", 200, '{"a":"x"}'),
BODY_XML("body-xml", 200, '<foo attr="attr_value">mytext<bar/></foo>'),
REDIRECT("redirect", 302, "/redirected"),
Expand Down Expand Up @@ -1657,6 +1669,54 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
response.close()
}

def 'test instrumentation gateway file upload filenames called once'() {
setup:
assumeTrue(testBodyFilenamesCalledOnce())
RequestBody fileBody = RequestBody.create(MediaType.parse('application/octet-stream'), 'file content')
def body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart('file', 'evil.php', fileBody)
.build()
def httpRequest = request(BODY_MULTIPART_REPEATED, 'POST', body).build()
def response = client.newCall(httpRequest).execute()

when:
TEST_WRITER.waitForTraces(1)

then:
TEST_WRITER.get(0).any {
it.getTag('request.body.filenames') == "[evil.php]"
&& it.getTag('_dd.appsec.filenames.cb.calls') == 1
}

cleanup:
response.close()
}

def 'test instrumentation gateway file upload filenames called once via parameter map'() {
setup:
assumeTrue(testBodyFilenamesCalledOnceCombined())
RequestBody fileBody = RequestBody.create(MediaType.parse('application/octet-stream'), 'file content')
def body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart('file', 'evil.php', fileBody)
.build()
def httpRequest = request(BODY_MULTIPART_COMBINED, 'POST', body).build()
def response = client.newCall(httpRequest).execute()

when:
TEST_WRITER.waitForTraces(1)

then:
TEST_WRITER.get(0).any {
it.getTag('request.body.filenames') == "[evil.php]"
&& it.getTag('_dd.appsec.filenames.cb.calls') == 1
}

cleanup:
response.close()
}

def 'test instrumentation gateway file upload content'() {
setup:
assumeTrue(testBodyFilesContent())
Expand Down Expand Up @@ -2667,6 +2727,7 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
Object responseBody
List<String> uploadedFilenames
List<String> uploadedFilesContent
int uploadedFilenamesCallCount = 0
}

static final String stringOrEmpty(String string) {
Expand Down Expand Up @@ -2840,6 +2901,8 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
rqCtxt.traceSegment.setTagTop('request.body.filenames', filenames as String)
Context context = rqCtxt.getData(RequestContextSlot.APPSEC)
context.uploadedFilenames = filenames
context.uploadedFilenamesCallCount++
rqCtxt.traceSegment.setTagTop('_dd.appsec.filenames.cb.calls', context.uploadedFilenamesCallCount)
Flow.ResultFlow.empty()
} as BiFunction<RequestContext, List<String>, Flow<Void>>)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ dependencies {
testRuntimeOnly project(':dd-java-agent:instrumentation:jetty:jetty-server:jetty-server-9.0')
testRuntimeOnly(project(':dd-java-agent:instrumentation:jetty:jetty-util-9.4.31'))
testRuntimeOnly project(':dd-java-agent:instrumentation:jetty:jetty-appsec:jetty-appsec-9.3')
testRuntimeOnly project(':dd-java-agent:instrumentation:jetty:jetty-appsec:jetty-appsec-9.4')
testRuntimeOnly project(':dd-java-agent:instrumentation:jetty:jetty-appsec:jetty-appsec-11.0')
testRuntimeOnly project(':dd-java-agent:instrumentation:servlet:jakarta-servlet-5.0')
testRuntimeOnly project(':dd-java-agent:instrumentation:servlet:javax-servlet:javax-servlet-3.0')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
muzzle {
pass {
group = 'org.eclipse.jetty'
module = 'jetty-server'
versions = '[11.0,12.0)'
assertInverse = true
javaVersion = 11
}
}

apply from: "$rootDir/gradle/java.gradle"

dependencies {
compileOnly(group: 'org.eclipse.jetty', name: 'jetty-server', version: '11.0.26') {
exclude group: 'org.slf4j', module: 'slf4j-api'
}
testImplementation(group: 'org.eclipse.jetty.toolchain', name: 'jetty-jakarta-servlet-api', version: '5.0.1')
testImplementation libs.bundles.mockito
}

tasks.withType(JavaCompile).configureEach {
configureCompiler(it, 11, JavaVersion.VERSION_1_8)
}

tasks.withType(Test).configureEach {
javaLauncher = getJavaLauncherFor(11)
}

// testing happens in the jetty-* modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
cafe.cryptography:curve25519-elisabeth:0.1.0=testRuntimeClasspath
cafe.cryptography:ed25519-elisabeth:0.1.0=testRuntimeClasspath
ch.qos.logback:logback-classic:1.2.13=testCompileClasspath,testRuntimeClasspath
ch.qos.logback:logback-core:1.2.13=testCompileClasspath,testRuntimeClasspath
com.blogspot.mydailyjava:weak-lock-free:0.17=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.datadoghq.okhttp3:okhttp:3.12.15=testCompileClasspath,testRuntimeClasspath
com.datadoghq.okio:okio:1.17.6=testCompileClasspath,testRuntimeClasspath
com.datadoghq:dd-instrument-java:0.0.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.datadoghq:dd-javac-plugin-client:0.2.2=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.datadoghq:java-dogstatsd-client:4.4.3=testRuntimeClasspath
com.datadoghq:sketches-java:0.8.3=testRuntimeClasspath
com.github.javaparser:javaparser-core:3.25.6=codenarc
com.github.jnr:jffi:1.3.14=testRuntimeClasspath
com.github.jnr:jnr-a64asm:1.0.0=testRuntimeClasspath
com.github.jnr:jnr-constants:0.10.4=testRuntimeClasspath
com.github.jnr:jnr-enxio:0.32.19=testRuntimeClasspath
com.github.jnr:jnr-ffi:2.2.18=testRuntimeClasspath
com.github.jnr:jnr-posix:3.1.21=testRuntimeClasspath
com.github.jnr:jnr-unixsocket:0.38.24=testRuntimeClasspath
com.github.jnr:jnr-x86asm:1.0.2=testRuntimeClasspath
com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
com.github.spotbugs:spotbugs:4.9.8=spotbugs
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,testAnnotationProcessor,testCompileClasspath
com.google.auto.service:auto-service:1.1.1=annotationProcessor,testAnnotationProcessor
com.google.auto:auto-common:1.2.1=annotationProcessor,testAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,compileClasspath,spotbugs,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.13.2=spotbugs
com.google.errorprone:error_prone_annotations:2.18.0=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.41.0=spotbugs
com.google.guava:failureaccess:1.0.1=annotationProcessor,testAnnotationProcessor
com.google.guava:guava:20.0=testCompileClasspath,testRuntimeClasspath
com.google.guava:guava:32.0.1-jre=annotationProcessor,testAnnotationProcessor
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,testAnnotationProcessor
com.google.j2objc:j2objc-annotations:2.8=annotationProcessor,testAnnotationProcessor
com.google.re2j:re2j:1.7=testRuntimeClasspath
com.squareup.moshi:moshi:1.11.0=testCompileClasspath,testRuntimeClasspath
com.squareup.okhttp3:logging-interceptor:3.12.12=testCompileClasspath,testRuntimeClasspath
com.squareup.okhttp3:okhttp:3.12.12=testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio:1.17.5=testCompileClasspath,testRuntimeClasspath
com.thoughtworks.qdox:qdox:1.12.1=codenarc
commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testRuntimeClasspath
commons-io:commons-io:2.11.0=testCompileClasspath,testRuntimeClasspath
commons-io:commons-io:2.20.0=spotbugs
de.thetaphi:forbiddenapis:3.10=compileClasspath
io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath
io.sqreen:libsqreen:17.3.0=testRuntimeClasspath
javax.servlet:javax.servlet-api:3.1.0=testCompileClasspath,testRuntimeClasspath
jaxen:jaxen:2.0.0=spotbugs
junit:junit:4.13.2=testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.18.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
net.bytebuddy:byte-buddy:1.18.3=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
net.java.dev.jna:jna-platform:5.8.0=testRuntimeClasspath
net.java.dev.jna:jna:5.8.0=testRuntimeClasspath
net.sf.saxon:Saxon-HE:12.9=spotbugs
org.apache.ant:ant-antlr:1.10.14=codenarc
org.apache.ant:ant-junit:1.10.14=codenarc
org.apache.bcel:bcel:6.11.0=spotbugs
org.apache.commons:commons-lang3:3.19.0=spotbugs
org.apache.commons:commons-text:1.14.0=spotbugs
org.apache.logging.log4j:log4j-api:2.25.2=spotbugs
org.apache.logging.log4j:log4j-core:2.25.2=spotbugs
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
org.checkerframework:checker-qual:3.33.0=annotationProcessor,testAnnotationProcessor
org.codehaus.groovy:groovy-ant:3.0.23=codenarc
org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc
org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc
org.codehaus.groovy:groovy-json:3.0.23=codenarc
org.codehaus.groovy:groovy-json:3.0.25=testCompileClasspath,testRuntimeClasspath
org.codehaus.groovy:groovy-templates:3.0.23=codenarc
org.codehaus.groovy:groovy-xml:3.0.23=codenarc
org.codehaus.groovy:groovy:3.0.23=codenarc
org.codehaus.groovy:groovy:3.0.25=testCompileClasspath,testRuntimeClasspath
org.codenarc:CodeNarc:3.7.0=codenarc
org.dom4j:dom4j:2.2.0=spotbugs
org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.1=compileClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-http:11.0.0=compileClasspath
org.eclipse.jetty:jetty-io:11.0.0=compileClasspath
org.eclipse.jetty:jetty-server:11.0.0=compileClasspath
org.eclipse.jetty:jetty-util:11.0.0=compileClasspath
org.gmetrics:GMetrics:2.1.0=codenarc
org.hamcrest:hamcrest-core:1.3=testRuntimeClasspath
org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath
org.jctools:jctools-core-jdk11:4.0.6=testRuntimeClasspath
org.jctools:jctools-core:4.0.6=testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.14.1=testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.14.1=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.1=testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.14.1=testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:1.14.1=testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.14.1=testRuntimeClasspath
org.junit:junit-bom:5.14.0=spotbugs
org.junit:junit-bom:5.14.1=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:4.4.0=testRuntimeClasspath
org.objenesis:objenesis:3.3=testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-analysis:9.7.1=testRuntimeClasspath
org.ow2.asm:asm-analysis:9.9=spotbugs
org.ow2.asm:asm-commons:9.9=spotbugs
org.ow2.asm:asm-commons:9.9.1=testRuntimeClasspath
org.ow2.asm:asm-tree:9.9=spotbugs
org.ow2.asm:asm-tree:9.9.1=testRuntimeClasspath
org.ow2.asm:asm-util:9.7.1=testRuntimeClasspath
org.ow2.asm:asm-util:9.9=spotbugs
org.ow2.asm:asm:9.9=spotbugs
org.ow2.asm:asm:9.9.1=buildTimeInstrumentationPlugin,compileClasspath,muzzleTooling,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
org.slf4j:log4j-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:1.7.30=buildTimeInstrumentationPlugin,compileClasspath,muzzleBootstrap,muzzleTooling,runtimeClasspath
org.slf4j:slf4j-api:1.7.32=testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.snakeyaml:snakeyaml-engine:2.9=buildTimeInstrumentationPlugin,muzzleTooling,runtimeClasspath,testRuntimeClasspath
org.spockframework:spock-bom:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath
org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath
org.tabletest:tabletest-junit:1.2.1=testCompileClasspath,testRuntimeClasspath
org.tabletest:tabletest-parser:1.2.0=testCompileClasspath,testRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=spotbugs
empty=spotbugsPlugins
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package datadog.trace.instrumentation.jetty11;

import static datadog.trace.api.gateway.Events.EVENTS;

import datadog.appsec.api.blocking.BlockingException;
import datadog.trace.api.gateway.BlockResponseFunction;
import datadog.trace.api.gateway.CallbackProvider;
import datadog.trace.api.gateway.Flow;
import datadog.trace.api.gateway.RequestContext;
import datadog.trace.api.gateway.RequestContextSlot;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
import jakarta.servlet.http.Part;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;

public class MultipartHelper {

private MultipartHelper() {}

/**
* Extracts non-null, non-empty filenames from a collection of multipart {@link Part}s using
* {@link Part#getSubmittedFileName()} (Servlet 5.0+, Jetty 11.x).
*
* @return list of filenames; never {@code null}, may be empty
*/
public static List<String> extractFilenames(Collection<Part> parts) {
if (parts == null || parts.isEmpty()) {
return Collections.emptyList();
}
List<String> filenames = new ArrayList<>(parts.size());
for (Part part : parts) {
try {
String name = part.getSubmittedFileName();
if (name != null && !name.isEmpty()) {
filenames.add(name);
}
} catch (Exception ignored) {
// malformed or inaccessible part — skip and continue with remaining parts
}
}
return filenames;
}

/**
* Fires the {@code requestFilesFilenames} IG event and returns a {@link BlockingException} if the
* WAF requests blocking, or {@code null} otherwise.
*/
public static BlockingException fireFilenamesEvent(
Collection<Part> parts, RequestContext reqCtx) {
CallbackProvider cbp = AgentTracer.get().getCallbackProvider(RequestContextSlot.APPSEC);
BiFunction<RequestContext, List<String>, Flow<Void>> callback =
cbp.getCallback(EVENTS.requestFilesFilenames());
if (callback == null) {
return null;
}
Comment on lines +53 to +58

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't know if relevant but you can also check that before extracting the filenames

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — moved cbp.getCallback(EVENTS.requestFilesFilenames()) before extractFilenames() so we skip Part iteration entirely when no AppSec listener is registered. Same pattern applied to fireBodyProcessedEvent().

List<String> filenames = extractFilenames(parts);
if (filenames.isEmpty()) {
return null;
}
Flow<Void> flow = callback.apply(reqCtx, filenames);
Flow.Action action = flow.getAction();
if (action instanceof Flow.Action.RequestBlockingAction) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
BlockResponseFunction brf = reqCtx.getBlockResponseFunction();
if (brf != null) {
if (brf.tryCommitBlockingResponse(reqCtx.getTraceSegment(), rba)) {
reqCtx.getTraceSegment().effectivelyBlocked();
return new BlockingException("Blocked request (multipart file upload)");
}
}
}
return null;
}
}
Loading