Skip to content

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

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

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

datadog-datadog-us1-prod Bot commented Jul 14, 2026

Copy link
Copy Markdown

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 57.02% (-0.15%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 06683b8 | Docs | Datadog PR Page | Give us feedback!

@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 13.93 s 13.92 s [-0.5%; +0.6%] (no difference)
startup:insecure-bank:tracing:Agent 12.83 s 12.96 s [-1.7%; -0.2%] (maybe better)
startup:petclinic:appsec:Agent 16.88 s 16.83 s [-0.7%; +1.3%] (no difference)
startup:petclinic:iast:Agent 16.43 s 16.95 s [-7.4%; +1.3%] (no difference)
startup:petclinic:profiling:Agent 16.38 s 16.29 s [-3.9%; +5.0%] (no difference)
startup:petclinic:sca:Agent 16.87 s 16.75 s [-0.3%; +1.8%] (no difference)
startup:petclinic:tracing:Agent 15.97 s 15.61 s [-1.9%; +6.4%] (no difference)

Commit: 06683b84 · 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.
@jandro996 jandro996 marked this pull request as ready for review July 14, 2026 13:28
@jandro996 jandro996 requested a review from a team as a code owner July 14, 2026 13:28
@jandro996 jandro996 requested review from ValentinZakharov, claponcet, manuel-alvarez-alvarez and vandonr and removed request for a team July 14, 2026 13:28

@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: 06683b8439

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

Comment on lines +75 to +76
return MultipartContentDecoder.readInputStream(
is, MAX_CONTENT_BYTES, contentTypeWithDefaultUtf8(bodyPart.getMediaType()));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Limit UTF-8 fallback to body-map text fields

Fresh evidence after the charset follow-up is that contentTypeWithDefaultUtf8 now sits in readContent, which is also invoked by the filesContent branch. When requestFilesContent is enabled for a Jersey upload whose file part has no charset and is encoded with the JVM/default legacy charset, this forces UTF-8 instead of the previous MultipartContentDecoder fallback used by Jersey file content and the other multipart helpers, so non-ASCII bytes are replaced before WAF inspection. Keep the UTF-8 default on the text field/body-map path only.

Useful? React with 👍 / 👎.

@datadog-datadog-us1-prod datadog-datadog-us1-prod 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.

Datadog Autotest: PASS

More details

Validated charset defaulting and cap boundary: contentTypeWithDefaultUtf8 correctly appends UTF-8 when no charset is declared and preserves explicit charsets unchanged; the totalBodyMapValues cap blocks ALL additions once the total reaches MAX_FILES_TO_INSPECT — including repeated field names — which is more restrictive than the PR description's "distinct-field-name" framing, but intentional per the in-code comment and test assertions.

Was this helpful? React 👍 or 👎

📊 Validated against 28 scenarios · Open Bits AI session

🤖 Datadog Autotest · Commit 06683b8 · What is Autotest? · Any feedback? Reach out in #autotest

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