Skip to content

Migrate AppSec blocking glue to the RequestContext tryCommitBlockingResponse overload - #12601

Draft
jandro996 wants to merge 24 commits into
masterfrom
docuple-appsec
Draft

jandro996 wants to merge 24 commits into
masterfrom
docuple-appsec

Conversation

@jandro996

@jandro996 jandro996 commented Sep 22, 2026

Copy link
Copy Markdown
Member

What Does This Do

Centralizes ~59 AppSec blocking call sites onto BlockResponseFunction#tryCommitBlockingResponse(RequestContext, RequestBlockingAction), replacing the older (TraceSegment, int, BlockingContentType, Map, String) overload, and reports block failures (a WAF-triggered block whose response could not actually be committed) consistently across frameworks.

Test infrastructure

  • Ports the e2e block_failure mechanism from the resteasy AppSec test fixture into the shared HttpServerTest.groovy, so every server instrumentation under test can opt in via testBlockFailure()/blockFailureVariant() and assert that AppSecContext#reportBlockFailure() is called when the blocking response cannot be committed.

Migrated call sites

  • vertx-web 3.4/4.0/5.0: RoutingContextJsonAdvice, RoutingContextSessionAdvice, WafPublishingBodyHandler, FileUploadHelper, PathParameterPublishingHelper now use the RequestContext overload and call effectivelyBlocked() explicitly where the old overload used to do it implicitly.
  • play 2.5/2.6: BodyParserHelpers, PathExtractionHelpers, ResultsStatusApplyAdvice/ResultsStatusInstrumentation, StatusHeaderInstrumentation/StatusHeaderSendJsonAdvice migrated. PathExtractionHelpers now returns null (fail-open, request proceeds unblocked) when no BlockResponseFunction is available, instead of throwing BlockingException unconditionally as before: without a BlockResponseFunction there is no way to commit a blocking response consistent with the rest of the pipeline (headers, status, template), so we let the request through rather than abort without a controlled response. This intentionally differs from the RASP case below, where aborting without a BlockResponseFunction prevents an actual command/file/network operation, not just an HTTP response.
  • ratpack-1.5: ContextParseAdvice, JsonRendererAdvice, PathBindingPublishingHandler, RatpackRequestBodyCallGetBufferAdvice, RatpackRequestBodyGetTextCalledAdvice, RequestBodyCollectionPublisher.
  • jax-rs-annotations-2.0 / jakarta-rs-annotations-3.0: MessageBodyWriterInstrumentation in both.
  • RASP (java-io-1.8, java-net-1.8): FileIORaspHelper (LFI) and URLSinkCallSite (SSRF) migrated to the new overload; both now throw BlockingException even when no BlockResponseFunction is available, so the attempt is always aborted, matching the existing CMDI/SHI invariant in ProcessImplInstrumentationHelpers.
  • internal-api: StoredCharBody, AppSecEventTracker, ProcessImplInstrumentationHelpers (exec/shell command blocking), with a shared commitBlockingResponse() helper to remove duplication between the two ProcessImplInstrumentationHelpers call sites.
  • okhttp-2.2 / okhttp-3.0: AppSecInterceptor updated for the MediaType package move (see below).

Other changes bundled with this migration

  • Moves MediaType from datadog.trace.api.appsec to datadog.trace.api.http, since it's a generic HTTP concept, not AppSec-specific.
  • Renames ContextInterpreter#clientIpWithoutAppSec to collectClientIp for clarity.
  • Splits the .github/CODEOWNERS vertx-web wildcard glob (vertx-web/*/.../Vertx*Instrumentation.java) into explicit per-version entries (3.4/4.0/5.0), and maximizes exclusive @DataDog/asm-java ownership across the rest of the AppSec blocking-glue domain covered by this PR (ratpack-1.5, jax-rs/jakarta-rs MessageBodyWriterInstrumentation, netty-4.1 blocking glue, cross-framework *BlockingHelper/*BlockResponseFunction helpers, agent-bootstrap/blocking/, ProcessImplInstrumentationHelpers.java). Scope explicitly excludes the ~40 dual-owned files under *-appsec-*/appsec/ directories (team decision, would partially revert Fix CODEOWNERS routing for AppSec, IAST, and RASP instrumentation modules #12583) and 8 nested *BlockResponseFunction classes unreachable by path glob.

Motivation

Consolidates blocking-response commit logic onto a single, RequestContext-based API so that a failure to commit a blocking response (e.g. the response was already partially written) is observable and reported consistently (AppSecContext#reportBlockFailure()) across every server integration, instead of silently doing nothing in some frameworks and throwing unconditionally in others.

Additional Notes

  • play-appsec-2.5/play-appsec-2.6 and vertx-web-3.4/vertx-web-4.0 test tasks are gated by testJvmConstraints (max Java 8 for Play, max Java 15 for vertx-web 3.4/4.0) — verified BUILD SUCCESSFUL for all four under the correct JVM (-PtestJvm=8 / -PtestJvm=11).
  • Muzzle check passes on all 13 affected instrumentation modules.
  • Two pre-existing, environment-related dd-trace-core test failures (TracerConnectionReliabilityTest.testLateAgentStart/testAgentRestart) were reproduced identically on a clean master checkout and are unrelated to this change.

Contributor Checklist

Jira ticket: APPSEC-70201

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.

MediaType is a general-purpose media type parsing utility with no AppSec
specific logic, so it does not belong under datadog.trace.api.appsec.
Switch every ratpack-1.5 blocking call site to the
BlockResponseFunction.tryCommitBlockingResponse(RequestContext,
RequestBlockingAction) overload so a blocking response that cannot be
committed is reported via AppSecContext.reportBlockFailure().

effectivelyBlocked() is not called at these call sites: ratpack blocks
through Netty's NettyBlockResponseFunction, whose BlockingResponseHandler
already marks the trace segment.

Enable the shared HttpServerTest block failure coverage for ratpack using
the PATH_PARAMS variant, which exercises PathBindingPublishingHandler (the
request header blocking point belongs to the netty instrumentation).
Switch the four blocking call sites in internal-api (StoredCharBody
request-body block, AppSecEventTracker#dispatch, and the CMDI/SHI RASP
checks in ProcessImplInstrumentationHelpers) from
BlockResponseFunction#tryCommitBlockingResponse(TraceSegment, ...) to the
(RequestContext, RequestBlockingAction) overload, so a silently failed
commit is reported through AppSecContext#reportBlockFailure().

TraceSegment#effectivelyBlocked() is now called explicitly at each call
site when the commit succeeds, following the jersey/resteasy pattern.

StoredCharBody now throws BlockingException only when a
BlockResponseFunction is present, matching the WAF request-body pattern
used by jersey. The RASP checks keep throwing unconditionally: aborting
the exec/shell attempt must not depend on being able to commit a
blocking response.
Migrate every Play blocking call site to
BlockResponseFunction#tryCommitBlockingResponse(RequestContext,
RequestBlockingAction) so a failed commit is reported through
AppSecContext#reportBlockFailure().

PathExtractionHelpers threw the BlockingException even when no block
response function was available, blocking the request with no chance of
a blocking response ever being committed; the throw now happens strictly
inside the guard.

Play runs on the Netty server, whose block response function commits
synchronously and calls TraceSegment#effectivelyBlocked() itself, so the
call sites must not call it again.

Enable the shared HttpServerTest block failure coverage for the Play 2.5
and 2.6 AppSec suites on the path params blocking point.
Migrate every vertx-web 3.4/4.0/5.0 blocking call site to
BlockResponseFunction#tryCommitBlockingResponse(RequestContext,
RequestBlockingAction), which centralises AppSecContext#reportBlockFailure()
when the blocking response cannot be committed.

effectivelyBlocked() is deliberately not called at these call sites: vertx-web
reuses Netty's block response function, which already finishes the span
synchronously when it commits the response.

Also fix RoutingContextJsonAdvice, which only assigned a BlockingException when
the instrumented method had not thrown. A concurrent exception would silently
swallow the block, so the BlockingException is now thrown unconditionally
inside the block response function guard.

Enable the shared HttpServerTest block failure test in the three vertx-web
server test suites, using the path params variant since path params are
published by the vertx-web instrumentation itself.
The Results$Status/apply and StatusHeader/sendJson advices carried the
WAF callback lookup and the block response function call inline in their
advice bodies, which get inlined into the instrumented Play classes.

Both now delegate to BodyParserHelpers#handleResponseBody, reusing the
existing executeCallback glue, so the blocking logic lives in a single
static helper and stays testable, matching the pattern of the rest of
the AppSec blocking call sites.
… CODEOWNERS

Adds sole-asm-java routing for blocking-glue files across vertx-web,
ratpack, jax-rs/jakarta-rs, netty, and cross-framework helpers that
were previously apm-idm-java-only or shadowed by the dual-owned
appsec/* patterns (last-match-wins). Appended after the existing dual
block so it wins; does not touch the ~40-file dual category left as a
team decision.
- FileIORaspHelper/URLSinkCallSite: throw BlockingException even when no
  BlockResponseFunction is available, so LFI/SSRF attempts are always
  aborted, matching the existing CMDI/SHI invariant
- ProcessImplInstrumentationHelpers: extract shared commitBlockingResponse()
  helper for the CMDI/SHI call sites (pure dedup, no behavior change)
- play-appsec-2.5/2.6 BodyParserHelpers: extract shared commitBlockAndThrow()
  helper for the three multipart call sites (pure dedup, no behavior change)
- RatpackRequestBodyGetTextCalledAdvice: drop the unused
  @Advice.Thrown(readOnly=false) parameter and throw unconditionally,
  matching the RoutingContextSessionAdvice/JsonAdvice pattern
- HttpServerTest.groovy: close the response in a finally block in
  executeIgnoringIoErrors to avoid a resource leak
- CODEOWNERS: split the vertx-web wildcard glob into explicit per-version
  entries (3.4/4.0/5.0) for Vertx*Instrumentation.java
@jandro996 jandro996 added type: refactoring comp: asm waf Application Security Management (WAF) tag: no release notes Changes to exclude from release notes labels Sep 22, 2026
@jandro996

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 22, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-23T13:30:51.471708Z 6e0c149 Draft marked ready
🔒 Security Review Completed 2026-09-23T13:35:45.502905Z 6e0c149 Draft marked ready

Security findings

Advisory findings (1)

ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: aea500b01e

ℹ️ 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 thread internal-api/src/main/java/datadog/trace/api/http/StoredCharBody.java Outdated
@datadog-datadog-prod-us1-2

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Sep 22, 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.94 s 14.02 s [-1.3%; +0.1%] (no difference)
startup:insecure-bank:tracing:Agent 13.00 s 13.01 s [-1.0%; +0.9%] (no difference)
startup:petclinic:appsec:Agent 17.68 s 17.56 s [-0.1%; +1.5%] (no difference)
startup:petclinic:iast:Agent 17.46 s 17.62 s [-1.5%; -0.2%] (maybe better)
startup:petclinic:profiling:Agent 17.50 s 17.46 s [-1.0%; +1.4%] (no difference)
startup:petclinic:sca:Agent 17.67 s 17.37 s [+0.8%; +2.7%] (maybe worse)
startup:petclinic:tracing:Agent 16.59 s 16.78 s [-2.1%; -0.3%] (maybe better)

Commit: c6ae6d9b · 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.

…e analysis

HttpServerTest$IGCallbacks$Context implementing AppSecContext made SpotBugs
newly flag every primitive setter in the class as AT_STALE_THREAD_WRITE_OF_PRIMITIVE,
failing CI. This is test infrastructure shared across instrumentation suites, not
production code, matching the existing exclusion pattern for smoke test controllers.
…y-read abort

Three OnMethodExit advices (Ratpack getText, vertx-web 3.4/4.0 getBodyAsJson) had
suppress = Throwable.class, so a bare `throw new BlockingException(...)` inside the
advice body was swallowed by ByteBuddy's suppression wrapper instead of propagating
to the caller, silently disabling blocking on those call sites. Restored the
@Advice.Thrown(readOnly = false) reassignment, the only mechanism ByteBuddy respects
to rethrow despite suppress.

StoredCharBody.maybeNotifyAndBlock() had moved the abort throw inside the
blockResponseFunction != null guard, so a request with no response function
available no longer aborted the body read at all. Moved the throw back outside
the guard to always abort when the WAF requests blocking.

Found by Codex review on PR #12601.
…mark pattern

StoredCharBody and AppSecEventTracker both live in internal-api and repeated
the same "commit blocking response, then mark segment on success" sequence.
…all sites

FileIORaspHelper, URLSinkCallSite, and both MessageBodyWriterInstrumentation
classes (jax-rs2, jakarta3) had the same inline
tryCommitBlockingResponse+effectivelyBlocked pattern already deduped in
internal-api. ProcessImplInstrumentationHelpers' own private helper now
delegates to the shared default method instead of duplicating the logic.
ResultsStatusInstrumentation and StatusHeaderInstrumentation duplicated the
same callback-lookup + block-and-throw logic inline. play25's
BodyParserHelpers already extracted this to handleResponseBody() in this
PR; apply the same extraction here, reusing the existing executeCallback
helper.
@jandro996

Copy link
Copy Markdown
Member Author

@codex review

@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: b6032cf24e

ℹ️ 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 thread internal-api/src/main/java/datadog/trace/api/gateway/BlockResponseFunction.java Outdated

@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 Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: b6032cf24e

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

- Restore the success check in play25/play26 commitBlockAndThrow before
  throwing BlockingException, dropped by the earlier dedup extraction
- Add unit tests for BlockResponseFunction.tryCommitBlockingResponseAndMarkBlocked
  covering both branches (marks effectivelyBlocked on success, skips on failure),
  fixing internal-api jacocoTestCoverageVerification
@jandro996

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 6e0c149bc5

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

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 6e0c149bc5

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@jandro996
jandro996 marked this pull request as ready for review September 23, 2026 13:26
@jandro996
jandro996 requested review from a team as code owners September 23, 2026 13:26
@jandro996
jandro996 requested review from ValentinZakharov, claponcet, dougqh, dromanol and mcculls and removed request for a team September 23, 2026 13:26
@jandro996
jandro996 marked this pull request as draft September 23, 2026 13:31
Empty commit to retrigger the pipeline after the transient muzzle
shard [3/8] network timeout.

@datadog-datadog-prod-us1-2 datadog-datadog-prod-us1-2 Bot left a comment

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.

Bits Code Review: FAIL

The new helper treats a scheduled block as a completed block. Netty and Undertow can then mark a failed asynchronous response as blocked and omit the failure report.

Open Bits AI session

🤖 Bits Code Review · Commit 6e0c149 · @DataDog review to ask questions

RequestContext ctx, Flow.Action.RequestBlockingAction action) {
boolean committed = tryCommitBlockingResponse(ctx, action);
if (committed) {
ctx.getTraceSegment().effectivelyBlocked();

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.

P2 Mark the trace only after an asynchronous block commits

AppSec telemetry can record a failed blocking attempt as successful and hide the block failure.

Assertion details
  • Input: A migrated caller uses this helper with Netty or Undertow, and the response starts or becomes unavailable before the scheduled blocking handler runs.
  • Expected: Mark the trace as blocked only after the response handler commits the blocking response. Report a block failure when the handler cannot commit it.
  • Actual: The helper receives true when Netty or Undertow accepts asynchronous work. It calls effectivelyBlocked() before the response handler commits the block. A later failure does not call reportBlockFailure().

Was this helpful? React 👍 or 👎
🤖 Bits Code Review · @DataDog review to ask questions · Open Bits AI session

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.

Good catch, thanks. Confirmed both async implementations you mentioned actually hit this: UndertowBlockResponseFunction#tryCommitBlockingResponse always returns true right after dispatching to UndertowBlockingHandler, and NettyHttpServerDecorator#tryCommitBlockingResponse (netty-4.1) returns true immediately when it schedules the commit on the event loop from a non-event-loop thread — in both cases the actual write happens later.

Fixed in 589c247 by removing tryCommitBlockingResponseAndMarkBlocked entirely rather than trying to special-case it: none of the migrated call sites called effectivelyBlocked() before this PR, so the helper was introducing the premature mark rather than deduplicating an existing pattern. The 7 call sites now call tryCommitBlockingResponse(ctx, action) directly, leaving the async implementation solely responsible for both effectivelyBlocked() and any later failure reporting, matching pre-PR semantics. Also documented on the interface javadoc that a synchronous true only means the commit was scheduled, not that it was already committed.

Added asynchronousImplementationOwnsMarkingAndFailureReporting in BlockResponseFunctionTest to cover exactly this schedule-then-commit-later scenario.

Note: UndertowBlockingHandler and the Netty scheduled runnable still don't call reportBlockFailure() if the deferred write itself fails or times out — that's the other half of this finding, but it's pre-existing behavior unrelated to this PR's migration, so I'm leaving it out of scope here and will file a follow-up.

…n helper

tryCommitBlockingResponseAndMarkBlocked called effectivelyBlocked() as
soon as tryCommitBlockingResponse returned true, but for async
implementations (Netty off-event-loop, Undertow dispatch) true only
means the commit was scheduled, not that it actually happened. A later
async failure would leave the trace marked as blocked with no
reportBlockFailure() call. None of the migrated call sites called
effectivelyBlocked() before this PR, so the helper was introducing the
premature call rather than deduplicating an existing pattern.

Removes the helper, restores the 7 call sites to call
tryCommitBlockingResponse(ctx, rba) directly (matching pre-PR
semantics), and documents on the interface javadoc that a synchronous
true does not guarantee the response was already committed. Adds a
BlockResponseFunctionTest case reproducing the async
schedule-then-commit-later scenario from the review comment.
PathExtractionHelpers had zero test coverage. Its behavior when
BlockResponseFunction is unavailable (fail-open: return null instead of
throwing BlockingException) is an intentional design decision already
discussed on PR review (a WAF block cannot be committed consistently
without a BlockResponseFunction, unlike the RASP case which fails
closed to prevent an actual command/file/network operation), but
nothing guarded it against an accidental flip in a future refactor.

Adds PathExtractionHelpersTest covering: empty/null params, missing
callback, non-blocking actions, the fail-open branch, the commit branch
(including a failed commit still throwing, since the boolean result is
intentionally ignored), and callback exceptions being swallowed. Adds
the mockito test dependency the module was missing.

This branch has not been deployed

No deployments
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) tag: no release notes Changes to exclude from release notes type: refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant