Skip to content

Commit afbe420

Browse files
committed
resteasy: recognize filename* in collectFilesContent too
hasFilename() now excludes filename*-only parts from the body map, but collectFilesContent() still gated on rawFilenameFromContentDisposition(), which is filename*-blind — such a part reached neither AppSec callback. Switch the gate to hasFilenameParam() so content inspection still covers it (Codex P1 follow-up on PR #12085).
1 parent 6348c70 commit afbe420

2 files changed

Lines changed: 21 additions & 5 deletions

File tree

dd-java-agent/instrumentation/resteasy/resteasy-appsec-3.0/src/main/java/datadog/trace/instrumentation/resteasy/MultipartHelper.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,9 @@ private static String contentTypeOf(Map<String, List<String>> headers) {
125125
// regardless of its declared content-type: a file can be declared text/plain and would otherwise
126126
// pass isTextPlain() and consume the body-map budget meant for genuine form fields. Uses
127127
// hasFilenameParam() rather than rawFilenameFromContentDisposition(), which deliberately ignores
128-
// the RFC 5987 "filename*" form: a part carrying only "filename*" must still be excluded here,
129-
// even though its filename value isn't decoded/reported elsewhere in this file.
128+
// the RFC 5987 "filename*" form: a part carrying only "filename*" must still be excluded here.
129+
// collectFilesContent() uses the same hasFilenameParam() gate, so such a part is still inspected
130+
// there even though this file never decodes its filename* value.
130131
private static boolean hasFilename(Map<String, List<String>> headers) {
131132
if (headers == null) {
132133
return false;
@@ -228,9 +229,9 @@ public static List<String> collectFilesContent(MultipartFormDataInput ret) {
228229
if (cdHeaders == null || cdHeaders.isEmpty()) {
229230
continue;
230231
}
231-
// rawFilenameFromContentDisposition returns null if filename attr absent,
232-
// otherwise returns the value (possibly empty) — both cases warrant content inspection
233-
if (rawFilenameFromContentDisposition(cdHeaders.get(0)) == null) {
232+
// hasFilenameParam recognizes both filename and filename* (see its javadoc): a part with
233+
// neither is a plain form field, already covered by collectBodyMap, and skipped here.
234+
if (!hasFilenameParam(cdHeaders.get(0))) {
234235
continue;
235236
}
236237
List<String> ctHeaders = getHeaderCaseInsensitive(headers, "Content-Type");

dd-java-agent/instrumentation/resteasy/resteasy-appsec-3.0/src/test/groovy/MultipartHelperTest.groovy

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,21 @@ class MultipartHelperTest extends Specification {
448448
result == ['anonymous']
449449
}
450450
451+
def "collectFilesContent includes part with only an RFC 5987 filename* (security fix)"() {
452+
given: "a file declaring filename* only, no plain filename"
453+
def part = Mock(InputPart)
454+
part.getHeaders() >> headers('form-data; name="upload"; filename*=UTF-8\'\'notes.txt', 'text/plain')
455+
part.getBody(_, _) >> new ByteArrayInputStream('<script>'.bytes)
456+
def ret = Mock(MultipartFormDataInput)
457+
ret.getFormDataMap() >> ['upload': [part]]
458+
459+
when:
460+
def result = MultipartHelper.collectFilesContent(ret)
461+
462+
then: "content is still inspected here even though collectBodyMap now excludes this part"
463+
result == ['<script>']
464+
}
465+
451466
def "collectFilesContent skips part without filename attribute"() {
452467
given:
453468
def part = Mock(InputPart)

0 commit comments

Comments
 (0)