Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
230cb87
Rename ContextInterpreter.clientIpWithoutAppSec to collectClientIp
jandro996 Sep 22, 2026
9788474
Move MediaType out of the appsec package into datadog.trace.api.http
jandro996 Sep 22, 2026
9fbe040
Add reusable AppSec block failure e2e coverage to HttpServerTest
jandro996 Sep 22, 2026
2d0901d
Use RequestContext overload of tryCommitBlockingResponse in jax-rs/ja…
jandro996 Sep 22, 2026
d0ffa17
Migrate RASP LFI/SSRF blocking to RequestContext tryCommitBlockingRes…
jandro996 Sep 22, 2026
c99be1d
Report AppSec block failures from ratpack blocking call sites
jandro996 Sep 22, 2026
cb8aa4e
Migrate internal-api blocking glue to the RequestContext overload
jandro996 Sep 22, 2026
8e99eaf
Report AppSec block failures in Play instrumentation
jandro996 Sep 22, 2026
678a75b
Report AppSec block failures from vertx-web instrumentation
jandro996 Sep 22, 2026
47ba643
Move Play 2.5 response body blocking glue into a static helper
jandro996 Sep 22, 2026
7bdf5b5
Maximize @DataDog/asm-java sole ownership for AppSec blocking glue in…
jandro996 Sep 22, 2026
aea500b
Ensure RASP blocking always aborts and dedupe blocking-glue call sites
jandro996 Sep 22, 2026
48a528a
Exclude datadog.trace.agent.test.base from SpotBugs stale-thread-writ…
jandro996 Sep 22, 2026
a39067d
Restore blocking rethrow through @Advice.Thrown and unconditional bod…
jandro996 Sep 23, 2026
49176a6
Merge branch 'master' into docuple-appsec
jandro996 Sep 23, 2026
e2b5656
Extract tryCommitBlockingResponseAndMarkBlocked to dedupe commit-and-…
jandro996 Sep 23, 2026
7706abf
Merge remote-tracking branch 'origin/docuple-appsec' into docuple-appsec
jandro996 Sep 23, 2026
97a5d3f
Extend tryCommitBlockingResponseAndMarkBlocked reuse to RASP/AppSec c…
jandro996 Sep 23, 2026
a0abb67
Extract handleResponseBody helper in play26 BodyParserHelpers
jandro996 Sep 23, 2026
b6032cf
Merge branch 'master' into docuple-appsec
jandro996 Sep 23, 2026
6e0c149
Fix commitBlockAndThrow to honor commit success and cover markAndCommit
jandro996 Sep 23, 2026
d68cedc
Trigger CI rerun
jandro996 Sep 23, 2026
589c247
Remove premature effectivelyBlocked marking from BlockResponseFunctio…
jandro996 Sep 23, 2026
c6ae6d9
Add unit test coverage for PathExtractionHelpers fail-open contract
jandro996 Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,44 @@
/dd-java-agent/instrumentation/spring/spring-security/ @DataDog/asm-java @DataDog/apm-idm-java
/dd-java-agent/instrumentation/velocity-1.5/ @DataDog/asm-java @DataDog/apm-idm-java

# AppSec blocking glue - exclusive asm-java ownership (must stay after the dual-owned
# /dd-java-agent/instrumentation/**/*appsec* patterns above to win last-match-wins)

# vertx-web (3.4 / 4.0 / 5.0)
/dd-java-agent/instrumentation/vertx/vertx-web/*/src/main/java/**/RoutingContext*Advice.java @DataDog/asm-java
/dd-java-agent/instrumentation/vertx/vertx-web/*/src/main/java/**/FileUploadHelper.java @DataDog/asm-java
/dd-java-agent/instrumentation/vertx/vertx-web/*/src/main/java/**/PathParameterPublishingHelper.java @DataDog/asm-java
/dd-java-agent/instrumentation/vertx/vertx-web/*/src/main/java/**/BlockingExceptionHandler.java @DataDog/asm-java
/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-3.4/src/main/java/**/Vertx*Instrumentation.java @DataDog/asm-java
/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-4.0/src/main/java/**/Vertx*Instrumentation.java @DataDog/asm-java
/dd-java-agent/instrumentation/vertx/vertx-web/vertx-web-5.0/src/main/java/**/Vertx*Instrumentation.java @DataDog/asm-java

# ratpack-1.5 (flat package, no appsec/ subpackage)
/dd-java-agent/instrumentation/ratpack-1.5/src/main/java/**/ContextParseAdvice.java @DataDog/asm-java
/dd-java-agent/instrumentation/ratpack-1.5/src/main/java/**/JsonRendererAdvice.java @DataDog/asm-java
/dd-java-agent/instrumentation/ratpack-1.5/src/main/java/**/PathBindingPublishingHandler.java @DataDog/asm-java
/dd-java-agent/instrumentation/ratpack-1.5/src/main/java/**/RatpackRequestBody*Advice.java @DataDog/asm-java
/dd-java-agent/instrumentation/ratpack-1.5/src/main/java/**/RequestBodyCollectionPublisher.java @DataDog/asm-java

# jax-rs / jakarta-rs annotations
/dd-java-agent/instrumentation/rs/**/MessageBodyWriterInstrumentation.java @DataDog/asm-java

# netty-4.1 (blocking glue already migrated by #12519, currently apm-idm-java only)
/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/**/NettyMultipartHelper.java @DataDog/asm-java
/dd-java-agent/instrumentation/netty/netty-4.1/src/main/java/**/HttpPostRequestDecoderInstrumentation.java @DataDog/asm-java
/dd-java-agent/instrumentation/netty/**/server/BlockingResponseHandler.java @DataDog/asm-java
/dd-java-agent/instrumentation/netty/**/server/MaybeBlockResponseHandler.java @DataDog/asm-java

# cross-framework blocking helpers and BlockResponseFunction implementations
/dd-java-agent/instrumentation/**/*BlockingHelper.java @DataDog/asm-java
/dd-java-agent/instrumentation/**/*BlockResponseFunction.java @DataDog/asm-java
/dd-java-agent/instrumentation/undertow/undertow-common/src/main/java/**/UndertowBlockingHandler.java @DataDog/asm-java
/dd-java-agent/instrumentation/jetty/jetty-server/*/src/main/*/**/JettyCommitResponse*.java @DataDog/asm-java

# bootstrap / internal-api blocking and RASP glue
/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/blocking/ @DataDog/asm-java
/internal-api/src/*/*/datadog/trace/bootstrap/instrumentation/api/java/lang/ProcessImplInstrumentationHelpers.java @DataDog/asm-java

# @DataDog/ci-app-libraries
/dd-java-agent/agent-ci-visibility/ @DataDog/ci-app-libraries
/dd-smoke-tests/backend-mock/ @DataDog/ci-app-libraries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.squareup.moshi.JsonDataException;
import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter;
import datadog.trace.api.appsec.MediaType;
import datadog.trace.api.http.MediaType;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import datadog.trace.api.ProductTraceSource
import datadog.trace.api.TagMap
import datadog.trace.api.appsec.HttpClientRequest
import datadog.trace.api.appsec.HttpClientResponse
import datadog.trace.api.appsec.MediaType
import datadog.trace.api.http.MediaType
import datadog.trace.api.config.GeneralConfig
import datadog.trace.api.function.TriConsumer
import datadog.trace.api.function.TriFunction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import datadog.trace.api.Config
import datadog.trace.api.DDSpanTypes
import datadog.trace.api.DDTags
import datadog.trace.api.ProductActivation
import datadog.trace.api.appsec.AppSecContext
import datadog.trace.api.config.GeneralConfig
import datadog.trace.api.config.TracerConfig
import datadog.trace.api.datastreams.DataStreamsContext
Expand All @@ -26,6 +27,7 @@ import datadog.trace.api.gateway.RequestContext
import datadog.trace.api.gateway.RequestContextSlot
import datadog.trace.api.http.StoredBodySupplier
import datadog.trace.api.iast.IastContext
import datadog.trace.api.internal.TraceSegment
import datadog.trace.api.normalize.SimpleHttpPathNormalizer
import datadog.trace.api.rum.RumInjector
import datadog.trace.api.telemetry.Endpoint
Expand Down Expand Up @@ -416,6 +418,21 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
true
}

/**
* Whether the server instrumentation reports a block failure (see {@code
* AppSecContext#reportBlockFailure()}) when the blocking response cannot be committed. Opt in by
* overriding this once the framework call sites go through {@code
* BlockResponseFunction#tryCommitBlockingResponse(RequestContext, RequestBlockingAction)}.
*/
boolean testBlockFailure() {
false
}

/** The blocking point exercised by the block failure test. */
BlockFailureVariant blockFailureVariant() {
BlockFailureVariant.REQUEST_HEADERS
}

/** Tomcat 5.5 can't seem to handle the encoded URIs */
boolean testEncodedPath() {
true
Expand Down Expand Up @@ -2072,6 +2089,46 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
}
}

def 'test block failure is reported when the blocking response cannot be committed'() {
setup:
assumeTrue(testBlockFailure())
def variant = blockFailureVariant()
assumeTrue(variant != BlockFailureVariant.PATH_PARAMS || testPathParam() != null)
IGCallbacks.Context.blockFailureReported = false

def request = request(variant.endpoint, 'GET', null)
.header(variant.header, variant.headerValue)
.header(IG_BLOCK_FAIL_HEADER, 'true')
.build()

when:
def response = executeIgnoringIoErrors(request)

then: 'no blocking response was committed'
response == null || !(response.code() in [301, 413, 418])

and: 'the failure to block was reported to the AppSec context'
IGCallbacks.Context.blockFailureReported
}

/**
* Executes a request that is expected not to produce a blocking response. When the blocking
* response cannot be committed the server may have nothing left to write, so the connection can
* be closed without a complete HTTP response.
*/
protected Response executeIgnoringIoErrors(Request request) {
Response response = null
try {
response = client.newCall(request).execute()
response.body().bytes()
response
} catch (IOException ignored) {
null
} finally {
response?.close()
}
}

@Flaky(value = "https://github.com/DataDog/dd-trace-java/issues/7061", suites = ["JettyContinuationHandlerV0ForkedTest", "JettyContinuationHandlerV1ForkedTest"])
def 'test blocking of request for request body variant #variant'() {
setup:
Expand Down Expand Up @@ -2699,6 +2756,7 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
static final String IG_EXTRA_SPAN_NAME_HEADER = "x-ig-write-tags"
static final String IG_TEST_HEADER = "x-ig-test-header"
static final String IG_BLOCK_HEADER = "x-block"
static final String IG_BLOCK_FAIL_HEADER = "x-block-fail"
static final String IG_BLOCK_RESPONSE_HEADER = "x-block-response"
static final String IG_PARAMETERS_BLOCK_HEADER = "x-block-parameters"
static final String IG_BODY_END_BLOCK_HEADER = "x-block-body-end"
Expand All @@ -2714,8 +2772,60 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
static final String IG_PATH_PARAMS_TAG = "ig-path-params"
static final String IG_SESSION_ID_TAG = "ig-session-id"

/**
* The blocking point at which a test suite wants the block failure test to be exercised. Each
* variant pairs the endpoint to hit with the instrumentation gateway header that makes the fake
* AppSec callbacks block there.
*/
static enum BlockFailureVariant {
/** Blocks on {@code requestHeaderDone}, supported by every blocking instrumentation. */
REQUEST_HEADERS(SUCCESS, IG_BLOCK_HEADER, 'json'),
/** Blocks on {@code requestPathParams}, for instrumentations that only publish path params. */
PATH_PARAMS(PATH_PARAM, IG_PARAMETERS_BLOCK_HEADER, 'true')

final ServerEndpoint endpoint
final String header
final String headerValue

private BlockFailureVariant(ServerEndpoint endpoint, String header, String headerValue) {
this.endpoint = endpoint
this.header = header
this.headerValue = headerValue
}
}

/** Simulates a server that cannot commit the blocking response. */
static enum FailingBlockResponseFunction implements BlockResponseFunction {
INSTANCE

@Override
boolean tryCommitBlockingResponse(TraceSegment segment, int statusCode,
BlockingContentType templateType, Map<String, String> extraHeaders, String securityResponseId) {
false
}
}

class IGCallbacks {
static class Context {
static class Context implements AppSecContext {
/**
* Set by the last request that reported a block failure. Tests that read it reset it first;
* it has to be static because the assertion happens outside the request context.
*/
static volatile boolean blockFailureReported

/** Replaces the server's block response function with one that fails to commit. */
boolean failBlocking

@Override
boolean isManuallyKept() {
false
}

@Override
void reportBlockFailure() {
blockFailureReported = true
}

String matchingHeaderValue
String doneHeaderValue
String extraSpanName
Expand All @@ -2739,6 +2849,20 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
string == null ? "" : string
}

/**
* Builds the blocking flow for a blocking point. When the request asked for a block failure
* (see {@link HttpServerTest#IG_BLOCK_FAIL_HEADER}), the server's block response function is
* first replaced by one that cannot commit, so the instrumentation is expected to report a
* block failure on the AppSec context.
*/
static final Flow<Void> blockingFlow(RequestContext rqCtxt, Flow.Action.RequestBlockingAction action) {
Context context = rqCtxt.getData(RequestContextSlot.APPSEC)
if (context?.failBlocking) {
rqCtxt.blockResponseFunction = FailingBlockResponseFunction.INSTANCE
}
new RbaFlow(action)
}

final Supplier<Flow<Context>> requestStartedCb =
({
->
Expand Down Expand Up @@ -2779,6 +2903,9 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
if (IG_BLOCK_HEADER.equalsIgnoreCase(key)) {
context.blockingContentType = value
}
if (IG_BLOCK_FAIL_HEADER.equalsIgnoreCase(key)) {
context.failBlocking = true
}
if (IG_BLOCK_RESPONSE_HEADER.equalsIgnoreCase(key)) {
context.responseBlock = value
}
Expand Down Expand Up @@ -2808,11 +2935,11 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
}

if (context.blockingContentType && context.blockingContentType != 'none') {
new RbaFlow(
blockingFlow(rqCtxt,
new Flow.Action.RequestBlockingAction(418,
BlockingContentType.valueOf(context.blockingContentType.toUpperCase(Locale.ROOT))))
} else if (context.blockingContentType && context.blockingContentType == 'none') {
new RbaFlow(
blockingFlow(rqCtxt,
Flow.Action.RequestBlockingAction.forRedirect(301, 'https://www.google.com/'))
} else {
Flow.ResultFlow.empty()
Expand Down Expand Up @@ -2862,7 +2989,7 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
}
activeSpan().localRootSpan.setTag('request.body', supplier.get() as String)
if (context.bodyEndBlock) {
new RbaFlow(
blockingFlow(rqCtxt,
new Flow.Action.RequestBlockingAction(413, BlockingContentType.JSON)
)
} else {
Expand Down Expand Up @@ -2892,7 +3019,7 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
rqCtxt.traceSegment.setTagTop('request.body.converted', obj as String)
Context context = rqCtxt.getData(RequestContextSlot.APPSEC)
if (context.bodyConvertedBlock) {
new RbaFlow(
blockingFlow(rqCtxt,
new Flow.Action.RequestBlockingAction(413, BlockingContentType.JSON)
)
} else {
Expand Down Expand Up @@ -2936,7 +3063,7 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
Context context = rqCtxt.getData(RequestContextSlot.APPSEC)
context.responseBody = body
if (context.responseBlock) {
new RbaFlow(
blockingFlow(rqCtxt,
new Flow.Action.RequestBlockingAction(413, BlockingContentType.JSON)
)
} else {
Expand Down Expand Up @@ -2971,12 +3098,12 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
context.tags.put(IG_RESPONSE_HEADER_TAG, context.igResponseHeaderValue)
}
if (context.responseBlock == 'none') {
new RbaFlow(
blockingFlow(rqCtxt,
new Flow.Action.RequestBlockingAction(301, BlockingContentType.NONE,
[Location: 'https://www.google.com/'])
)
} else if (context.responseBlock == 'json') {
new RbaFlow(
blockingFlow(rqCtxt,
new Flow.Action.RequestBlockingAction(413, BlockingContentType.JSON)
)
} else {
Expand All @@ -2988,7 +3115,7 @@ abstract class HttpServerTest<SERVER> extends WithHttpServer<SERVER> {
RequestContext rqCtxt, Map<String, ?> map ->
Context context = rqCtxt.getData(RequestContextSlot.APPSEC)
if (context.parametersBlock) {
return new RbaFlow(
return blockingFlow(rqCtxt,
new Flow.Action.RequestBlockingAction(413, BlockingContentType.JSON)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,10 @@ private void invokeRaspCallback(
BlockResponseFunction brf = ctx.getBlockResponseFunction();
if (brf != null) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
brf.tryCommitBlockingResponse(ctx.getTraceSegment(), rba);
brf.tryCommitBlockingResponse(ctx, rba);
}
// Thrown even without a BlockResponseFunction: RASP must abort the LFI attempt even when
// no blocking response can be committed.
throw new BlockingException("Blocked request (for LFI attempt)");
}
} catch (final BlockingException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ private static void raspCallback(@Nonnull final URL url) {
BlockResponseFunction brf = ctx.getBlockResponseFunction();
if (brf != null) {
Flow.Action.RequestBlockingAction rba = (Flow.Action.RequestBlockingAction) action;
brf.tryCommitBlockingResponse(ctx.getTraceSegment(), rba);
brf.tryCommitBlockingResponse(ctx, rba);
}
// Thrown even without a BlockResponseFunction: RASP must abort the SSRF attempt even when
// no blocking response can be committed.
throw new BlockingException("Blocked request (for SSRF attempt)");
}
} catch (final BlockingException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
import datadog.trace.api.appsec.HttpClientPayload;
import datadog.trace.api.appsec.HttpClientRequest;
import datadog.trace.api.appsec.HttpClientResponse;
import datadog.trace.api.appsec.MediaType;
import datadog.trace.api.gateway.BlockResponseFunction;
import datadog.trace.api.gateway.CallbackProvider;
import datadog.trace.api.gateway.Flow;
import datadog.trace.api.gateway.RequestContext;
import datadog.trace.api.gateway.RequestContextSlot;
import datadog.trace.api.http.MediaType;
import datadog.trace.api.internal.VisibleForTesting;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
import datadog.trace.api.appsec.HttpClientPayload;
import datadog.trace.api.appsec.HttpClientRequest;
import datadog.trace.api.appsec.HttpClientResponse;
import datadog.trace.api.appsec.MediaType;
import datadog.trace.api.gateway.BlockResponseFunction;
import datadog.trace.api.gateway.CallbackProvider;
import datadog.trace.api.gateway.Flow;
import datadog.trace.api.gateway.RequestContext;
import datadog.trace.api.gateway.RequestContextSlot;
import datadog.trace.api.http.MediaType;
import datadog.trace.api.internal.VisibleForTesting;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.AgentTracer;
Expand Down
Loading
Loading