Skip to content

Bound multipart text/plain field materialization in Jersey AppSec#11944

Draft
jandro996 wants to merge 5 commits into
masterfrom
APMSP-3237
Draft

Bound multipart text/plain field materialization in Jersey AppSec#11944
jandro996 wants to merge 5 commits into
masterfrom
APMSP-3237

Conversation

@jandro996

@jandro996 jandro996 commented Jul 14, 2026

Copy link
Copy Markdown
Member

What Does This Do

  • Replaces the unbounded bodyPart.getValue() call in MultiPartHelper.collectBodyPart (jersey2 and jersey3 AppSec modules) with readContent(bodyPart), which reads the part through the existing byte-capped MultipartContentDecoder (bounded by MAX_CONTENT_BYTES, backed by Config.get().getAppSecMaxFileContentBytes()).
  • Adds a distinct-field-name cap on the bodyMap collected from text/plain multipart fields, symmetric to the existing MAX_FILES_TO_INSPECT cap already applied to filesContent. Values for a field name already present in the map keep accumulating; only new distinct field names are capped once MAX_FILES_TO_INSPECT is reached.
  • Applies the identical change to both dd-java-agent/instrumentation/jersey/jersey-appsec/jersey-appsec-2.0 (javax.ws.rs) and jersey-appsec-3.0 (jakarta.ws.rs) — the two MultiPartHelper.java files remain byte-for-byte identical modulo the namespace.
  • Updates MultiPartHelperTest.groovy in both modules to mock bodyPart.getEntityAs(InputStream) instead of bodyPart.getValue() for the paths affected by the fix, and adds new test cases: truncation of a text/plain field value longer than MAX_CONTENT_BYTES, the field-name cap (MAX_FILES_TO_INSPECT) limiting distinct body-map keys, and accumulation of additional values for an already-seen field name even once the field-name cap is reached.

Motivation

Jersey 2/3 AppSec instrumentation materialized text/plain multipart field values via the unbounded getValue() and accumulated an unlimited number of distinct field names into the body map before any WAF limit was applied. A crafted multipart request with a very large text/plain field or a very large number of distinct field names could exhaust heap/CPU before hitting any bound, since neither content size nor field count was capped on this path.

Additional Notes

Scope is intentionally limited to Jersey. RESTEasy's MultipartFormDataReaderInstrumentation has a related unbounded-read pattern (getBodyAsString() on all parts) that is out of scope for this PR and should be addressed as a follow-up.

Contributor Checklist

Jira ticket: APMSP-3237

Note: Once your PR is ready to merge, add it to the merge queue by commenting /merge. /merge -c cancels 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.

Jersey 2/3 AppSec instrumentation read text/plain multipart field
values via the unbounded getValue() and accumulated an unlimited
number of distinct field names, allowing a crafted multipart request
to exhaust heap/CPU before WAF limits apply. Reuse the existing
byte-capped MultipartContentDecoder and the configured file-content
limits (MAX_CONTENT_BYTES, MAX_FILES_TO_INSPECT) to bound both the
size of each field value and the number of distinct field names
collected.
@jandro996

Copy link
Copy Markdown
Member Author

@codex review

@jandro996 jandro996 added type: bug fix Bug fix comp: asm waf Application Security Management (WAF) labels Jul 14, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 124e9188ab

ℹ️ 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".

@datadog-datadog-us1-prod

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🟢 Java Benchmark SLOs — All performance SLOs passed

Suite Status
Startup 🟢 pass

SLO thresholds are defined here based on automatically generated metrics. A warning is raised when results are within 5% of the threshold.

PR vs. master results
Scenario Candidate master Δ (95% CI of mean)
startup:insecure-bank:iast:Agent 14.00 s 13.91 s [-0.1%; +1.4%] (no difference)
startup:insecure-bank:tracing:Agent 12.99 s 13.01 s [-0.8%; +0.4%] (no difference)
startup:petclinic:appsec:Agent 16.82 s 16.56 s [+0.6%; +2.6%] (maybe worse)
startup:petclinic:iast:Agent 16.83 s 16.96 s [-1.8%; +0.2%] (no difference)
startup:petclinic:profiling:Agent 16.66 s 16.61 s [-0.8%; +1.4%] (no difference)
startup:petclinic:sca:Agent 16.90 s 16.75 s [-0.2%; +2.0%] (no difference)
startup:petclinic:tracing:Agent 16.09 s 16.24 s [-1.9%; +0.1%] (no difference)

Commit: e25666ac · CI Pipeline · Benchmarking Platform UI


Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion.

…d getName()

- Cap now bounds total accumulated values in the body map, not just distinct
  field names, closing a bypass where repeating a field name skipped the limit.
- Derive the body map key from FormDataContentDisposition.getName() (a safe
  field accessor) instead of FormDataBodyPart.getName(), which re-parses the
  disposition header and can throw on malformed input.

Addresses Codex review findings 1 and 3 on PR #11944.
…charset

Charset.defaultCharset() depends on the JVM/platform locale and can differ
from the UTF-8 default Jersey's own getValue() used, corrupting text
captured by AppSec on JVMs whose platform charset isn't UTF-8.

Addresses Codex review finding 2 on PR #11944.
@jandro996

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: e25666acf5

ℹ️ 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".

…ultipartContentDecoder

MultipartContentDecoder is shared by Tomcat, Netty, Vert.x, RESTEasy and
commons-fileupload. Its Charset.defaultCharset() fallback was a deliberate,
reviewed decision (Manuel, PR #11198) - changing it in e25666a silently
altered production behavior for all those integrations, not just Jersey.

Revert the shared decoder change and instead default to UTF-8 only in
Jersey's MultiPartHelper, matching Jersey's own getValue() default, by
appending charset=UTF-8 to the contentType before calling the shared decoder.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp: asm waf Application Security Management (WAF) type: bug fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant