From 92a6adffb351e1bc4391f59f881115f56a1e4175 Mon Sep 17 00:00:00 2001 From: Joey Zhao <234797088+joey-zhao_ddog@users.noreply.github.com> Date: Wed, 23 Sep 2026 23:27:23 -0400 Subject: [PATCH 1/2] Keep http.status_code when the Lambda AppSec context is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit processResponseData moved its RequestContext guard above the parse and tightened it to also require APPSEC slot data. Because DDSpanContext.getRequestContext() returns `this` and is never null for a real span, the live half of that guard is the new APPSEC check — so an exception inside processRequestStart (caught and swallowed there, while CURRENT_TRIGGER_TYPE is already set to the HTTP trigger) now costs the invocation span its http.status_code and error flag as well. Split the guard instead: compute the AppSec-context flag up front, keep publishing the status code, and gate only the WAF response callbacks on it. This is the previous ordering with the stricter condition retained. Also renames processResponseDataDoesNothingWhenSpanHasNoRequestContext, which asserted nothing and whose name described behaviour the span never had, and adds coverage for the non-null-RequestContext-without-APPSEC case that the regression actually goes through. Co-Authored-By: Claude Opus 5 (1M context) --- .../trace/lambda/LambdaAppSecHandler.java | 14 +++++--- .../trace/lambda/LambdaAppSecHandlerTest.java | 32 ++++++++++++++++--- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java b/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java index 5885a942e47..e4b8c261369 100644 --- a/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java +++ b/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java @@ -189,10 +189,8 @@ public static void processResponseData(AgentSpan span, Object result) { } RequestContext requestContext = span.getRequestContext(); - if (requestContext == null || requestContext.getData(RequestContextSlot.APPSEC) == null) { - log.debug("Span has no AppSec request context, skipping response processing"); - return; - } + boolean hasAppSecContext = + requestContext != null && requestContext.getData(RequestContextSlot.APPSEC) != null; try { byte[] bytes = ((ByteArrayOutputStream) result).toByteArray(); @@ -219,6 +217,14 @@ public static void processResponseData(AgentSpan span, Object result) { span.setError(isError, ErrorPriorities.HTTP_SERVER_DECORATOR); } + // http.status_code is a tracing tag and is published above whether or not AppSec ran: a + // failure inside processRequestStart must not also cost the span its status. The WAF + // callbacks below, in contrast, have nowhere to deliver without an AppSec request context. + if (!hasAppSecContext) { + log.debug("Span has no AppSec request context, skipping response WAF callbacks"); + return; + } + AgentTracer.TracerAPI tracer = AgentTracer.get(); CallbackProvider cbp = tracer.getCallbackProvider(RequestContextSlot.APPSEC); diff --git a/dd-trace-core/src/test/java/datadog/trace/lambda/LambdaAppSecHandlerTest.java b/dd-trace-core/src/test/java/datadog/trace/lambda/LambdaAppSecHandlerTest.java index ba292eff21a..f51ec9e4e78 100644 --- a/dd-trace-core/src/test/java/datadog/trace/lambda/LambdaAppSecHandlerTest.java +++ b/dd-trace-core/src/test/java/datadog/trace/lambda/LambdaAppSecHandlerTest.java @@ -1753,13 +1753,35 @@ void processResponseDataDoesNothingForNullResult() { } @Test - void processResponseDataDoesNothingWhenSpanHasNoRequestContext() { + void processResponseDataPublishesStatusButNoWafEventsWhenSpanHasNoRequestContext() { + LambdaAppSecHandler.setCurrentTriggerType(LambdaTriggerType.API_GATEWAY_V1_REST); AgentSpan span = mock(AgentSpan.class); when(span.getRequestContext()).thenReturn(null); - ByteArrayOutputStream result = createOutputStream("{\"statusCode\": 200}"); - setupMockResponseCallbacks(null, null, null, null); - LambdaAppSecHandler.processResponseData(span, result); - // no exception expected + AgentTracer.TracerAPI tracer = mock(AgentTracer.TracerAPI.class); + AgentTracer.forceRegister(tracer); + + LambdaAppSecHandler.processResponseData(span, createOutputStream("{\"statusCode\": 200}")); + + verify(span).setHttpStatusCode(200); + verify(tracer, never()).getCallbackProvider(RequestContextSlot.APPSEC); + } + + @Test + void processResponseDataStillPublishesStatusWhenSpanHasNoAppSecContext() { + // An exception inside processRequestStart leaves an HTTP trigger type recorded but no AppSec + // context on the span. http.status_code is a tracing tag and must survive that. + LambdaAppSecHandler.setCurrentTriggerType(LambdaTriggerType.API_GATEWAY_V1_REST); + RequestContext requestContext = mock(RequestContext.class); + AgentSpan span = mock(AgentSpan.class); + when(span.getRequestContext()).thenReturn(requestContext); + AgentTracer.TracerAPI tracer = mock(AgentTracer.TracerAPI.class); + AgentTracer.forceRegister(tracer); + + LambdaAppSecHandler.processResponseData( + span, createOutputStream("{\"statusCode\": 503, \"body\": \"boom\"}")); + + verify(span).setHttpStatusCode(503); + verify(tracer, never()).getCallbackProvider(RequestContextSlot.APPSEC); } @Test From 42f0e06469427a2510567b0155144a90fcfe684a Mon Sep 17 00:00:00 2001 From: Joey Zhao <234797088+joey-zhao_ddog@users.noreply.github.com> Date: Wed, 23 Sep 2026 23:28:19 -0400 Subject: [PATCH 2/2] Restore comment explaining the null trigger type check The comment distinguishing a null trigger type (processRequestStart never ran) from a non-HTTP one (ran, trigger unsupported) was dropped while the requestContext guard below it was reworked. Both branches are still there and still behave differently, so the explanation still applies. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java b/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java index e4b8c261369..8ed1eb4cb6a 100644 --- a/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java +++ b/dd-trace-core/src/main/java/datadog/trace/lambda/LambdaAppSecHandler.java @@ -135,6 +135,8 @@ public static void processRequestEnd(AgentSpan span) { return; } + // A null trigger type means processRequestStart never ran, so the invocation was not analysed + // at all, which is not the same as an unsupported trigger. if (!triggerType.isHttp()) { span.setMetric(UNSUPPORTED_EVENT_TYPE_METRIC, 1); return;