Add server.request.body.filenames support for Jetty#10988
Add server.request.body.filenames support for Jetty#10988gh-worker-dd-mergequeue-cf854d[bot] merged 55 commits into
Conversation
BenchmarksStartupParameters
See matching parameters
SummaryFound 0 performance improvements and 0 performance regressions! Performance is the same for 64 metrics, 7 unstable metrics. Startup time reports for insecure-bankgantt
title insecure-bank - global startup overhead: candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section tracing
Agent [baseline] (1.062 s) : 0, 1062389
Total [baseline] (8.876 s) : 0, 8875655
Agent [candidate] (1.068 s) : 0, 1068271
Total [candidate] (8.908 s) : 0, 8907523
section iast
Agent [baseline] (1.24 s) : 0, 1240195
Total [baseline] (9.577 s) : 0, 9577286
Agent [candidate] (1.235 s) : 0, 1234803
Total [candidate] (9.528 s) : 0, 9527909
gantt
title insecure-bank - break down per module: candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section tracing
crashtracking [baseline] (1.255 ms) : 0, 1255
crashtracking [candidate] (1.242 ms) : 0, 1242
BytebuddyAgent [baseline] (639.515 ms) : 0, 639515
BytebuddyAgent [candidate] (642.042 ms) : 0, 642042
AgentMeter [baseline] (29.588 ms) : 0, 29588
AgentMeter [candidate] (29.715 ms) : 0, 29715
GlobalTracer [baseline] (250.334 ms) : 0, 250334
GlobalTracer [candidate] (251.25 ms) : 0, 251250
AppSec [baseline] (32.529 ms) : 0, 32529
AppSec [candidate] (32.72 ms) : 0, 32720
Debugger [baseline] (59.935 ms) : 0, 59935
Debugger [candidate] (60.245 ms) : 0, 60245
Remote Config [baseline] (606.238 µs) : 0, 606
Remote Config [candidate] (602.292 µs) : 0, 602
Telemetry [baseline] (8.012 ms) : 0, 8012
Telemetry [candidate] (8.154 ms) : 0, 8154
Flare Poller [baseline] (4.332 ms) : 0, 4332
Flare Poller [candidate] (5.959 ms) : 0, 5959
section iast
crashtracking [baseline] (1.252 ms) : 0, 1252
crashtracking [candidate] (1.222 ms) : 0, 1222
BytebuddyAgent [baseline] (816.817 ms) : 0, 816817
BytebuddyAgent [candidate] (812.794 ms) : 0, 812794
AgentMeter [baseline] (11.5 ms) : 0, 11500
AgentMeter [candidate] (11.409 ms) : 0, 11409
GlobalTracer [baseline] (240.264 ms) : 0, 240264
GlobalTracer [candidate] (239.458 ms) : 0, 239458
IAST [baseline] (29.038 ms) : 0, 29038
IAST [candidate] (30.121 ms) : 0, 30121
AppSec [baseline] (29.149 ms) : 0, 29149
AppSec [candidate] (29.231 ms) : 0, 29231
Debugger [baseline] (64.101 ms) : 0, 64101
Debugger [candidate] (62.85 ms) : 0, 62850
Remote Config [baseline] (538.745 µs) : 0, 539
Remote Config [candidate] (528.611 µs) : 0, 529
Telemetry [baseline] (7.828 ms) : 0, 7828
Telemetry [candidate] (7.705 ms) : 0, 7705
Flare Poller [baseline] (3.481 ms) : 0, 3481
Flare Poller [candidate] (3.411 ms) : 0, 3411
Startup time reports for petclinicgantt
title petclinic - global startup overhead: candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section tracing
Agent [baseline] (1.061 s) : 0, 1061332
Total [baseline] (11.066 s) : 0, 11065511
Agent [candidate] (1.057 s) : 0, 1057252
Total [candidate] (11.041 s) : 0, 11041446
section appsec
Agent [baseline] (1.267 s) : 0, 1266839
Total [baseline] (11.05 s) : 0, 11050431
Agent [candidate] (1.261 s) : 0, 1261356
Total [candidate] (11.066 s) : 0, 11065620
section iast
Agent [baseline] (1.232 s) : 0, 1232242
Total [baseline] (11.243 s) : 0, 11243326
Agent [candidate] (1.234 s) : 0, 1233920
Total [candidate] (11.35 s) : 0, 11350091
section profiling
Agent [baseline] (1.195 s) : 0, 1194902
Total [baseline] (11.058 s) : 0, 11058385
Agent [candidate] (1.184 s) : 0, 1183963
Total [candidate] (11.046 s) : 0, 11045662
gantt
title petclinic - break down per module: candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section tracing
crashtracking [baseline] (1.252 ms) : 0, 1252
crashtracking [candidate] (1.226 ms) : 0, 1226
BytebuddyAgent [baseline] (638.76 ms) : 0, 638760
BytebuddyAgent [candidate] (635.801 ms) : 0, 635801
AgentMeter [baseline] (29.337 ms) : 0, 29337
AgentMeter [candidate] (29.453 ms) : 0, 29453
GlobalTracer [baseline] (249.891 ms) : 0, 249891
GlobalTracer [candidate] (248.588 ms) : 0, 248588
AppSec [baseline] (32.663 ms) : 0, 32663
AppSec [candidate] (32.235 ms) : 0, 32235
Debugger [baseline] (60.819 ms) : 0, 60819
Debugger [candidate] (60.665 ms) : 0, 60665
Remote Config [baseline] (602.287 µs) : 0, 602
Remote Config [candidate] (603.281 µs) : 0, 603
Telemetry [baseline] (8.1 ms) : 0, 8100
Telemetry [candidate] (8.853 ms) : 0, 8853
Flare Poller [baseline] (3.583 ms) : 0, 3583
Flare Poller [candidate] (3.566 ms) : 0, 3566
section appsec
crashtracking [baseline] (1.225 ms) : 0, 1225
crashtracking [candidate] (1.218 ms) : 0, 1218
BytebuddyAgent [baseline] (677.745 ms) : 0, 677745
BytebuddyAgent [candidate] (674.9 ms) : 0, 674900
AgentMeter [baseline] (12.386 ms) : 0, 12386
AgentMeter [candidate] (12.308 ms) : 0, 12308
GlobalTracer [baseline] (249.663 ms) : 0, 249663
GlobalTracer [candidate] (249.14 ms) : 0, 249140
IAST [baseline] (24.271 ms) : 0, 24271
IAST [candidate] (24.243 ms) : 0, 24243
AppSec [baseline] (187.128 ms) : 0, 187128
AppSec [candidate] (187.695 ms) : 0, 187695
Debugger [baseline] (65.925 ms) : 0, 65925
Debugger [candidate] (63.847 ms) : 0, 63847
Remote Config [baseline] (580.033 µs) : 0, 580
Remote Config [candidate] (571.177 µs) : 0, 571
Telemetry [baseline] (7.958 ms) : 0, 7958
Telemetry [candidate] (7.735 ms) : 0, 7735
Flare Poller [baseline] (3.477 ms) : 0, 3477
Flare Poller [candidate] (3.434 ms) : 0, 3434
section iast
crashtracking [baseline] (1.225 ms) : 0, 1225
crashtracking [candidate] (1.238 ms) : 0, 1238
BytebuddyAgent [baseline] (809.553 ms) : 0, 809553
BytebuddyAgent [candidate] (809.698 ms) : 0, 809698
AgentMeter [baseline] (11.378 ms) : 0, 11378
AgentMeter [candidate] (11.404 ms) : 0, 11404
GlobalTracer [baseline] (238.51 ms) : 0, 238510
GlobalTracer [candidate] (238.921 ms) : 0, 238921
IAST [baseline] (28.347 ms) : 0, 28347
IAST [candidate] (27.68 ms) : 0, 27680
AppSec [baseline] (30.353 ms) : 0, 30353
AppSec [candidate] (29.587 ms) : 0, 29587
Debugger [baseline] (64.924 ms) : 0, 64924
Debugger [candidate] (67.133 ms) : 0, 67133
Remote Config [baseline] (535.877 µs) : 0, 536
Remote Config [candidate] (558.104 µs) : 0, 558
Telemetry [baseline] (7.811 ms) : 0, 7811
Telemetry [candidate] (7.945 ms) : 0, 7945
Flare Poller [baseline] (3.526 ms) : 0, 3526
Flare Poller [candidate] (3.572 ms) : 0, 3572
section profiling
crashtracking [baseline] (1.185 ms) : 0, 1185
crashtracking [candidate] (1.181 ms) : 0, 1181
BytebuddyAgent [baseline] (699.262 ms) : 0, 699262
BytebuddyAgent [candidate] (691.419 ms) : 0, 691419
AgentMeter [baseline] (9.07 ms) : 0, 9070
AgentMeter [candidate] (8.933 ms) : 0, 8933
GlobalTracer [baseline] (208.663 ms) : 0, 208663
GlobalTracer [candidate] (207.639 ms) : 0, 207639
AppSec [baseline] (32.756 ms) : 0, 32756
AppSec [candidate] (32.451 ms) : 0, 32451
Debugger [baseline] (66.0 ms) : 0, 66000
Debugger [candidate] (65.41 ms) : 0, 65410
Remote Config [baseline] (574.953 µs) : 0, 575
Remote Config [candidate] (570.796 µs) : 0, 571
Telemetry [baseline] (7.829 ms) : 0, 7829
Telemetry [candidate] (7.73 ms) : 0, 7730
Flare Poller [baseline] (3.536 ms) : 0, 3536
Flare Poller [candidate] (3.493 ms) : 0, 3493
ProfilingAgent [baseline] (94.038 ms) : 0, 94038
ProfilingAgent [candidate] (93.623 ms) : 0, 93623
Profiling [baseline] (94.601 ms) : 0, 94601
Profiling [candidate] (94.183 ms) : 0, 94183
LoadParameters
See matching parameters
SummaryFound 0 performance improvements and 2 performance regressions! Performance is the same for 18 metrics, 16 unstable metrics.
Request duration reports for petclinicgantt
title petclinic - request duration [CI 0.99] : candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section baseline
no_agent (19.044 ms) : 18853, 19236
. : milestone, 19044,
appsec (18.724 ms) : 18532, 18916
. : milestone, 18724,
code_origins (17.881 ms) : 17704, 18058
. : milestone, 17881,
iast (17.707 ms) : 17531, 17884
. : milestone, 17707,
profiling (18.937 ms) : 18745, 19129
. : milestone, 18937,
tracing (17.495 ms) : 17323, 17666
. : milestone, 17495,
section candidate
no_agent (18.287 ms) : 18098, 18476
. : milestone, 18287,
appsec (19.426 ms) : 19230, 19621
. : milestone, 19426,
code_origins (18.015 ms) : 17835, 18195
. : milestone, 18015,
iast (18.222 ms) : 18043, 18400
. : milestone, 18222,
profiling (18.315 ms) : 18132, 18499
. : milestone, 18315,
tracing (17.728 ms) : 17554, 17903
. : milestone, 17728,
Request duration reports for insecure-bankgantt
title insecure-bank - request duration [CI 0.99] : candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section baseline
no_agent (1.257 ms) : 1244, 1270
. : milestone, 1257,
iast (3.314 ms) : 3267, 3362
. : milestone, 3314,
iast_FULL (5.948 ms) : 5888, 6008
. : milestone, 5948,
iast_GLOBAL (3.716 ms) : 3654, 3778
. : milestone, 3716,
profiling (2.293 ms) : 2271, 2314
. : milestone, 2293,
tracing (1.959 ms) : 1943, 1975
. : milestone, 1959,
section candidate
no_agent (1.329 ms) : 1315, 1343
. : milestone, 1329,
iast (3.336 ms) : 3290, 3381
. : milestone, 3336,
iast_FULL (6.059 ms) : 5997, 6122
. : milestone, 6059,
iast_GLOBAL (3.742 ms) : 3676, 3808
. : milestone, 3742,
profiling (2.294 ms) : 2272, 2316
. : milestone, 2294,
tracing (1.989 ms) : 1969, 2008
. : milestone, 1989,
DacapoParameters
See matching parameters
SummaryFound 0 performance improvements and 0 performance regressions! Performance is the same for 11 metrics, 1 unstable metrics. Execution time for tomcatgantt
title tomcat - execution time [CI 0.99] : candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section baseline
no_agent (1.483 ms) : 1471, 1494
. : milestone, 1483,
appsec (2.535 ms) : 2481, 2590
. : milestone, 2535,
iast (2.274 ms) : 2204, 2344
. : milestone, 2274,
iast_GLOBAL (2.319 ms) : 2249, 2390
. : milestone, 2319,
profiling (2.104 ms) : 2049, 2159
. : milestone, 2104,
tracing (2.083 ms) : 2029, 2137
. : milestone, 2083,
section candidate
no_agent (1.486 ms) : 1475, 1498
. : milestone, 1486,
appsec (3.821 ms) : 3600, 4042
. : milestone, 3821,
iast (2.277 ms) : 2207, 2347
. : milestone, 2277,
iast_GLOBAL (2.327 ms) : 2256, 2398
. : milestone, 2327,
profiling (2.109 ms) : 2054, 2165
. : milestone, 2109,
tracing (2.075 ms) : 2022, 2129
. : milestone, 2075,
Execution time for biojavagantt
title biojava - execution time [CI 0.99] : candidate=1.62.0-SNAPSHOT~9b0ff19a18, baseline=1.62.0-SNAPSHOT~0d1c44d515b
dateFormat X
axisFormat %s
section baseline
no_agent (15.044 s) : 15044000, 15044000
. : milestone, 15044000,
appsec (14.946 s) : 14946000, 14946000
. : milestone, 14946000,
iast (18.241 s) : 18241000, 18241000
. : milestone, 18241000,
iast_GLOBAL (18.239 s) : 18239000, 18239000
. : milestone, 18239000,
profiling (15.006 s) : 15006000, 15006000
. : milestone, 15006000,
tracing (15.107 s) : 15107000, 15107000
. : milestone, 15107000,
section candidate
no_agent (15.11 s) : 15110000, 15110000
. : milestone, 15110000,
appsec (14.987 s) : 14987000, 14987000
. : milestone, 14987000,
iast (18.57 s) : 18570000, 18570000
. : milestone, 18570000,
iast_GLOBAL (17.755 s) : 17755000, 17755000
. : milestone, 17755000,
profiling (14.923 s) : 14923000, 14923000
. : milestone, 14923000,
tracing (15.129 s) : 15129000, 15129000
. : milestone, 15129000,
|
f2998c3 to
629f074
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c732823549
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
3ab9ff7 to
77ec572
Compare
2e72584 to
d37e03e
Compare
d37e03e to
d8a92f8
Compare
Jetty multipart file upload instrumentation fixed in DataDog/dd-trace-java#10988 (Jetty 8.1.3, 9.3–11).
|
Hi! 👋 Looks like you updated a Git Submodule.
|
3 similar comments
|
Hi! 👋 Looks like you updated a Git Submodule.
|
|
Hi! 👋 Looks like you updated a Git Submodule.
|
|
Hi! 👋 Looks like you updated a Git Submodule.
|
…st the singleton
When getPart("field") is the app's first multipart access, Jetty 8 parses the entire
multipart stream and caches all parts in _multiPartInputStream, but only returns the
one requested part. The previous advice forwarded just that singleton to AppSec, so any
co-uploaded file parts were invisible to requestFilesFilenames — a WAF bypass if the
app never called getParts() explicitly.
Fix: read all cached parts via MultiPartInputStream.getParts() (reflected, cached handle)
and fall back to the singleton only when reflection fails. Also remove the part==null
early return: even if the requested field was not found, other file parts may have parsed.
Add PartHelper.getAllParts(Object, Part) + FakeMpi unit tests.
The Part.class depth check only prevents re-entry within a single getPart() invocation;
after the call returns the depth is 0 again, so a second getPart("file") call on the
same request would re-fire requestBodyProcessed and requestFilesFilenames with the same
cached parts. Add the same _multiPartInputStream == null guard that GetFilenamesAdvice
already uses: once the field is set the multipart body was parsed and events were already
dispatched — skip.
…ields Hard-coded UTF-8 in readPartContent() caused mojibake for fields with Content-Type: text/plain; charset=ISO-8859-1 (or any non-UTF-8 charset). Now parses the charset from the part's Content-Type header and falls back to UTF-8 only when absent or unrecognised.
…p10ForkedTest The lockfile was out of sync: testCompileClasspath was locked to 10.0.0 instead of 10.0.10, and earlyDep10ForkedTest configurations were missing entirely. Regenerated with --write-locks. Also added a resolutionStrategy to force Jetty 10.0.9 for earlyDep10ForkedTest: without it, Gradle picks 10.0.10 (inherited via testImplementation), defeating the purpose of the suite which exercises the MultiPartFormInputStream path present in [10.0.0, 10.0.10).
…t check, per-part try/catch - Fix body+filenames ordering bug in jetty8 GetFilenamesAdvice and GetPartAdvice: both callbacks now always fire even when body blocks (pendingBlock pattern, appsec-ig-events §7) - Add requestFilesFilenames support for jetty-appsec-9.2 ([9.2, 9.3)): new MultipartHelper, GetFilenamesAdvice, GetFilenamesFromMultiPartAdvice, and integration test Jetty92LatestDepForkedTest - Fix tryCommitBlockingResponse() return not checked before effectivelyBlocked() in PartHelper (jetty8) and MultipartHelper (jetty9.3, 9.4, 11.0) - Add per-part try/catch in extractFilenames() and extractFormFields() loops in PartHelper and all MultipartHelper implementations
Satisfies the enforce-groovy-migration CI check. All 8 Groovy test files introduced in this PR are replaced by Java equivalents: - jetty-appsec-8.1.3: PartHelperTest (36 unit tests via Mockito) - jetty-appsec-9.2/9.3/9.4: MultipartHelperTest (8 unit tests each) - jetty-appsec-11.0: MultipartHelperTest using jakarta.servlet.http.Part - jetty-server-7.6: Jetty8LatestDepForkedTest (extends Jetty76Test) - jetty-server-9.0.4: Jetty92LatestDepForkedTest (extends Jetty9Test) - jetty-server-10.0: Jetty10EarlyDepForkedTest (extends Jetty10Test) Integration tests extending Groovy Spock base classes are placed in src/test/groovy/ as .java files so groovyc compiles them together with the Groovy classes they extend, avoiding the compileTestJava ordering issue. This pattern is already used elsewhere in the repo.
…ement - Add explanatory comments to all catch(Exception ignored) blocks in PartHelper and MultipartHelper across jetty8/9.2/9.3/9.4/11.0 modules, consistent with the pattern used in Jersey and GlassFish helpers - Remove spurious CallDepthThreadLocalMap.decrementCallDepth(Request.class) from jetty92 GetPartsAdvice.after() which had no matching increment and was corrupting the thread-local counter
- ArrayList(parts.size()) in extractFilenames() across all 5 modules to pre-size the list and avoid rehashing - Callback-before-extract optimization in fireFilenamesEvent() and fireBodyProcessedEvent(): check callback != null before calling extractFilenames()/extractFormFields() so we skip Part iteration when AppSec is not listening - WithTypeStructure.structureMatcher() in jetty-appsec-11.0 to replace additionalMuzzleReferences() for javax vs jakarta DispatcherType discrimination; evaluated per-class at transformation time - Skip-increment optimization in ExtractContentParametersAdvice and GetFilenamesAdvice (jetty-appsec-11.0): return false early when guard fields are already non-null, avoiding CallDepthThreadLocalMap overhead on cached repeat calls - IODH + MethodHandle in PartHelper (jetty-appsec-8.1.3) replacing volatile Method for MultiPartInputStream.getParts() resolution; inner class MpiGetPartsHolder added to helperClassNames()
…hen IODH handle is null
MpiGetPartsHolder resolves MultiPartInputStream.getParts() at class-load time via
Class.forName(). In test environments where jetty-server is not on the test classpath
(compileOnly only), GET_PARTS is null and the MethodHandle path is unavailable.
Add duck-typed reflection as fallback: when GET_PARTS is null, call getParts() via
getClass().getMethod("getParts").invoke(...). In production the fast MethodHandle path
always wins; the reflection fallback only activates when the class could not be resolved.
This restores getAllPartsReturnsAllPartsFromMultiPartInputStream and
getAllPartsPrefersFullCollectionOverSingleton unit tests that regressed after the
IODH refactor.
…ation The skip-increment optimization introduced in the amarziali review commit broke the shared Collection.class depth counter that GetFilenamesAdvice and GetFilenamesFromMultiPartAdvice rely on to coordinate. In Jetty 11.0.0, public getParts() delegates to the private getParts(MultiMap) overload. GetFilenamesAdvice increments Collection depth before the public method runs, which raises the depth so GetFilenamesFromMultiPartAdvice (also applied to the private overload) sees depth > 0 and skips firing. When the optimization skipped the increment because _multiParts != null, the private getParts(MultiMap) call found Collection depth = 0 on the second and third repeated calls and fired the requestFilesFilenames callback again, causing _dd.appsec.filenames.cb.calls = 3 (BODY_MULTIPART_REPEATED) and = 2 (BODY_MULTIPART_COMBINED) instead of the expected 1. Fix: restore the original always-increment/always-decrement pattern in GetFilenamesAdvice so the depth counter remains balanced on every code path.
… use real MultiPartInputStream in tests
The reflection fallback (getClass().getMethod("getParts").invoke(...)) was
only needed because MultiPartInputStream was not on the test classpath,
causing the IODH MethodHandle to initialize to null. Fix the root cause
instead: add testImplementation for jetty-server 8.1.3.v20120416 and
replace the FakeMpi stub with mock(MultiPartInputStream.class).
205bb1b to
bb8c73f
Compare
- getAllParts: log.debug on MethodHandle invocation failure - extractFilenames: log.debug when skipping a malformed part - extractFormFields: log.debug when skipping a malformed part - readPartContent: log.debug on IOException before returning null IODH static block and charsetFromContentType left comment-only: static initializer context and attacker-controlled input respectively.
Use CallDepthThreadLocalMap.getCallDepth(Part.class) to read the current depth without modifying it, instead of the equivalent incrementCallDepth + decrementCallDepth pair.
| @Advice.FieldValue("_contentParameters") final MultiMap<String> contentParameters, | ||
| @Advice.FieldValue(value = "_multiParts", typing = Assigner.Typing.DYNAMIC) | ||
| final Object multiParts) { | ||
| final int callDepth = CallDepthThreadLocalMap.incrementCallDepth(Collection.class); |
There was a problem hiding this comment.
Out of curiosity, why Collection.class instead of more meaningful class?
There was a problem hiding this comment.
Thanks Manu!
Changed to MultipartHelper.class in all four modules (jetty92, jetty93, jetty94, jetty11). Each module has its own MultipartHelper in a different package, so they are independent classes created specifically for this PR — no risk of collision with other instrumentations.
…ap key Replace Collection.class with MultipartHelper.class in all four jetty appsec modules (9.2, 9.3, 9.4, 11.0). Collection.class is a generic JDK class that any other instrumentation could use as a key on the same thread, silently corrupting call depth tracking. MultipartHelper.class is specific to each module and cannot collide with other instrumentations.
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
|
What Does This Do
Instruments Jetty's multipart request handling to fire both
requestFilesFilenamesandrequestBodyProcessedAppSec gateway events, enabling WAF rules that act on uploaded filenames and multipart form-field values.Jetty parses multipart bodies through two code paths depending on how the application accesses the request:
getParameterMap()→extractContentParameters()→ internalgetParts(MultiMap)getParts()/getPart(String)call from user codeBoth paths are instrumented where they exist. Guards on internal state fields (
_contentParameters,_multiParts,_multiPartInputStream) prevent double-firing when the same method is called more than once per request.Module coverage
jetty-appsec-7.0getParts()does not exist in Jetty 7.xjetty-appsec-8.1.3[8.1.3, 9.2.0.RC0)jetty-appsec-9.2[9.2, 9.3)requestBodyProcessed; no filename support needed at this rangejetty-appsec-9.3[9.3, 9.4.10)[9.3, 12). AddedGetFilenamesAdvice+GetFilenamesFromMultiPartAdvice. Double-fire guard uses_multiPartInputStream.jetty-appsec-9.4[9.4.10, 11.0)_multiParts: MultiParts(9.4.10+, 10.0.10+) and_multiParts: MultiPartFormInputStream(10.0.0–10.0.9). Bytecode discriminator:_dispatcherType: Ljavax/servlet/DispatcherType;.jetty-appsec-11.0[11.0, 12.0)_dispatcherType: Ljakarta/servlet/DispatcherType;inRequest.classbytecode — checking classpath presence ofjakarta.servletwas unreliable when bothjavaxandjakartawere present simultaneously.jetty-appsec-8.1.3 details
Replaced brittle ASM bytecode injection (
GetPartsMethodVisitor) with clean exit advice:GetFilenamesAdviceongetParts()— firesrequestBodyProcessed(form fields) +requestFilesFilenamesGetPartAdviceongetPart(String)— same, for single-part uploads that never callgetParts(); in Jetty 8.xgetPart(String)calls_multiPartInputStream.getPart()directly_multiPartInputStream == nullguard prevents re-firing on repeatedgetParts()callsRequest.classbytecode at agent startup, detecting thegetParameters()call insidegetParts()that is unique to Jetty 8.xPartHelper(replacingParameterCollector):filenameFromPart(): quote-awareContent-Dispositionparser;Part.getSubmittedFileName()is Servlet 3.1+ and unavailable hereextractFormFields(): reads form-field body values, skips file-upload partsfireBodyProcessedEvent()/fireFilenamesEvent(): encapsulate the full IG callback + blocking-commit logic, returningBlockingException|nullfor use with@Advice.Thrown(readOnly=false)Helpers and tests
PartHelper(8.1.3) andMultipartHelper(9.3/9.4/11.0) are unit-tested independently inPartHelperTest/MultipartHelperTestjetty-server-7.6,jetty-server-9.3,jetty-server-9.4.21,jetty-server-10.0,jetty-server-11.0cover both thegetParts()andgetParameterMap()code paths; a dedicatedJetty10EarlyDepForkedTestcovers Jetty 10.0.0–10.0.9Motivation
Part of APPSEC-61873 —
server.request.body.filenamesimplementation across server frameworks.Additional Notes
Bytecode discriminator rationale (9.4 vs 11.0): checking for
jakarta.servlet.http.Parton the classpath was unreliable when a Jetty 9.4/10 app ships bothjavaxandjakartaon the classpath (common with Spring Boot 3 migration tooling). The_dispatcherTypefield descriptor insideRequest.classbytecode is unambiguous regardless of classpath contents.GetFilenamesFromMultiPartAdvicevsGetFilenamesAdvice: in all 9.3/9.4/11.0 modules,extractContentParameters()sets_contentParametersbefore callinggetParts(MultiMap), so the_contentParameters != nullguard inGetFilenamesAdvicecorrectly prevents double-firing when thegetParameterMap()path is used.Part.getSubmittedFileName()availability: Servlet 3.1+ (Jetty 9.1+). For Jetty 8.x (Servlet 3.0),PartHelper.filenameFromPart()parses theContent-Dispositionheader manually with a quote-aware parser to handle filenames likefilename="shell;evil.php"correctly.Depends on #10973 (merged)
Contributor Checklist
type:and (comp:orinst:) labels in addition to any other useful labelsclose,fix, or any linking keywords when referencing an issueUse
solvesinstead, and assign the PR milestone to the issueJira ticket: APPSEC-61873
Note: Once your PR is ready to merge, add it to the merge queue by commenting
/merge./merge -ccancels the queue request./merge -f --reason "reason"skips all merge queue checks; please use this judiciously, as some checks do not run at the PR-level. For more information, see this doc.