From 19900ae2c236dd1c30c27f4cff04d072432e9256 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Thu, 25 Jun 2026 17:26:54 +0200 Subject: [PATCH 1/5] feat: add request_excluded tag to appsec.waf.requests metric Adds the `request_excluded` tag to the `appsec.waf.requests` telemetry metric as required by the ASM Tags RFC. The tag emits string values `full`/`partial` (per RFC) with `none` as the baseline until libddwaf exposes exclusion filter data in its result. - WafMetricCollector: WAF_REQUEST_COMBINATIONS 128->256 (2^8), new requestExcluded boolean in bitmask (bit 7), tag emits "full"/"none" - AppSecRequestContext: wafRequestExcluded field + getter/setter - GatewayBridge: passes ctx.isWafRequestExcluded() as 8th arg - WafMetricCollectorTest: 256 combinations, wafInit added to given block --- .../appsec/gateway/AppSecRequestContext.java | 9 ++++++++ .../datadog/appsec/gateway/GatewayBridge.java | 3 ++- .../api/telemetry/WafMetricCollector.java | 22 +++++++++++++------ .../telemetry/WafMetricCollectorTest.groovy | 9 +++++--- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java index 860f3119ae4..d9cb4b3e448 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java @@ -168,6 +168,7 @@ public class AppSecRequestContext implements DataBundle, Closeable { private volatile boolean wafTruncated; private volatile boolean wafRequestBlockFailure; private volatile boolean wafRateLimited; + private volatile boolean wafRequestExcluded; private volatile int wafTimeouts; private volatile int raspTimeouts; @@ -287,6 +288,14 @@ public boolean isWafRateLimited() { return wafRateLimited; } + public void setWafRequestExcluded() { + wafRequestExcluded = true; + } + + public boolean isWafRequestExcluded() { + return wafRequestExcluded; + } + public void increaseWafTimeouts() { WAF_TIMEOUTS_UPDATER.incrementAndGet(this); } diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java index 22b1b09d1db..a7b04c017e2 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/GatewayBridge.java @@ -1057,7 +1057,8 @@ private NoopFlow onRequestEnded(RequestContext ctx_, IGSpanInfo spanInfo) { ctx.getWafTimeouts() > 0, // wafTimeout, ctx.isWafRequestBlockFailure(), // blockFailure, ctx.isWafRateLimited(), // rateLimited, - ctx.isWafTruncated() // inputTruncated + ctx.isWafTruncated(), // inputTruncated + ctx.isWafRequestExcluded() // requestExcluded ); } diff --git a/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java b/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java index 96797ebae6f..d4792a5bd69 100644 --- a/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java +++ b/internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java @@ -33,7 +33,7 @@ private WafMetricCollector() { private static final BlockingQueue rawMetricsQueue = new ArrayBlockingQueue<>(RAW_QUEUE_SIZE); - private static final int WAF_REQUEST_COMBINATIONS = 128; // 2^7 + private static final int WAF_REQUEST_COMBINATIONS = 256; // 2^8 private final AtomicLongArray wafRequestCounter = new AtomicLongArray(WAF_REQUEST_COMBINATIONS); private static final AtomicLongArray wafInputTruncatedCounter = @@ -99,7 +99,8 @@ public void wafRequest( final boolean wafTimeout, final boolean blockFailure, final boolean rateLimited, - final boolean inputTruncated) { + final boolean inputTruncated, + final boolean requestExcluded) { int index = computeWafRequestIndex( ruleTriggered, @@ -108,7 +109,8 @@ public void wafRequest( wafTimeout, blockFailure, rateLimited, - inputTruncated); + inputTruncated, + requestExcluded); wafRequestCounter.incrementAndGet(index); } @@ -125,7 +127,8 @@ static int computeWafRequestIndex( boolean wafTimeout, boolean blockFailure, boolean rateLimited, - boolean inputTruncated) { + boolean inputTruncated, + boolean requestExcluded) { int index = 0; if (ruleTriggered) index |= 1; if (requestBlocked) index |= 1 << 1; @@ -134,6 +137,7 @@ static int computeWafRequestIndex( if (blockFailure) index |= 1 << 4; if (rateLimited) index |= 1 << 5; if (inputTruncated) index |= 1 << 6; + if (requestExcluded) index |= 1 << 7; return index; } @@ -233,6 +237,7 @@ public void prepareMetrics() { boolean blockFailure = (i & (1 << 4)) != 0; boolean rateLimited = (i & (1 << 5)) != 0; boolean inputTruncated = (i & (1 << 6)) != 0; + boolean requestExcluded = (i & (1 << 7)) != 0; if (!rawMetricsQueue.offer( new WafRequestsRawMetric( @@ -245,7 +250,8 @@ public void prepareMetrics() { wafTimeout, blockFailure, rateLimited, - inputTruncated))) { + inputTruncated, + requestExcluded))) { return; } } @@ -497,7 +503,8 @@ public WafRequestsRawMetric( final boolean wafTimeout, final boolean blockFailure, final boolean rateLimited, - final boolean inputTruncated) { + final boolean inputTruncated, + final boolean requestExcluded) { super( "waf.requests", counter, @@ -509,7 +516,8 @@ public WafRequestsRawMetric( "waf_timeout:" + wafTimeout, "block_failure:" + blockFailure, "rate_limited:" + rateLimited, - "input_truncated:" + inputTruncated); + "input_truncated:" + inputTruncated, + "request_excluded:" + (requestExcluded ? "full" : "none")); } } diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy index 5f5663d1e3b..02b223eed12 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy @@ -432,6 +432,7 @@ class WafMetricCollectorTest extends DDSpecification { void 'test waf request metrics'() { given: def collector = WafMetricCollector.get() + collector.wafInit('waf_ver1', 'rules.1', true) when: collector.wafRequest( @@ -441,7 +442,8 @@ class WafMetricCollectorTest extends DDSpecification { wafTimeout, blockFailure, rateLimited, - inputTruncated + inputTruncated, + requestExcluded ) then: @@ -462,11 +464,12 @@ class WafMetricCollectorTest extends DDSpecification { "waf_timeout:${wafTimeout}", "block_failure:${blockFailure}", "rate_limited:${rateLimited}", - "input_truncated:${inputTruncated}" + "input_truncated:${inputTruncated}", + "request_excluded:${requestExcluded ? 'full' : 'none'}" ] where: - [triggered, blocked, wafError, wafTimeout, blockFailure, rateLimited, inputTruncated] << allBooleanCombinations(7) + [triggered, blocked, wafError, wafTimeout, blockFailure, rateLimited, inputTruncated, requestExcluded] << allBooleanCombinations(8) } void 'test waf input truncated metrics'() { From c53548c352e165843f8f379a625f13a9c97b49d6 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Thu, 25 Jun 2026 17:39:54 +0200 Subject: [PATCH 2/5] review: pre-PR checks - Add metric.value == 1 assertion in waf request metrics test - Add requestMetrics.size() == 1 guard assertion - Add placeholder comment on setWafRequestExcluded() (libddwaf blocker) - Apply spotless formatting --- .../appsec/gateway/AppSecRequestContext.java | 1 + .../api/telemetry/WafMetricCollectorTest.groovy | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java index d9cb4b3e448..905024180fc 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/gateway/AppSecRequestContext.java @@ -288,6 +288,7 @@ public boolean isWafRateLimited() { return wafRateLimited; } + // placeholder: libddwaf does not yet expose exclusion filter results public void setWafRequestExcluded() { wafRequestExcluded = true; } diff --git a/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy b/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy index 02b223eed12..a05887d6668 100644 --- a/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy @@ -451,10 +451,12 @@ class WafMetricCollectorTest extends DDSpecification { def metrics = collector.drain() def requestMetrics = metrics.findAll { it.metricName == 'waf.requests' } + requestMetrics.size() == 1 final metric = requestMetrics[0] metric.type == 'count' metric.metricName == 'waf.requests' metric.namespace == 'appsec' + metric.value == 1 metric.tags == [ "waf_version:waf_ver1", "event_rules_version:rules.1", @@ -469,7 +471,16 @@ class WafMetricCollectorTest extends DDSpecification { ] where: - [triggered, blocked, wafError, wafTimeout, blockFailure, rateLimited, inputTruncated, requestExcluded] << allBooleanCombinations(8) + [ + triggered, + blocked, + wafError, + wafTimeout, + blockFailure, + rateLimited, + inputTruncated, + requestExcluded + ] << allBooleanCombinations(8) } void 'test waf input truncated metrics'() { From 8aadb973119a587840c164bbae899e45cc91ba8b Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Thu, 25 Jun 2026 17:46:02 +0200 Subject: [PATCH 3/5] review: fix GatewayBridgeSpecification mock arity for wafRequest Update wafRequest mock expectation from 7 to 8 wildcard arguments to match the new requestExcluded parameter added in APPSEC-62739. --- .../datadog/appsec/gateway/GatewayBridgeSpecification.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy index 36a8ebc861d..7f519c3d76e 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/gateway/GatewayBridgeSpecification.groovy @@ -210,7 +210,7 @@ class GatewayBridgeSpecification extends DDSpecification { 1 * mockAppSecCtx.isWafRequestBlockFailure() 1 * mockAppSecCtx.isWafRateLimited() 1 * mockAppSecCtx.isWafTruncated() - 1 * wafMetricCollector.wafRequest(_, _, _, _, _, _, _) // call waf request metric + 1 * wafMetricCollector.wafRequest(_, _, _, _, _, _, _, _) // call waf request metric flow.result == null flow.action == Flow.Action.Noop.INSTANCE } From b130f0d0c1b779b6f66099bc5fe6ec9cfa31c95c Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Thu, 25 Jun 2026 18:00:22 +0200 Subject: [PATCH 4/5] fix: update WafMetricPeriodicActionSpecification for 8-arg wafRequest Add missing requestExcluded (false) 8th argument to all wafRequest calls and add request_excluded:none to all expected tag lists. Found by Codex review of PR #11744. --- ...afMetricPeriodicActionSpecification.groovy | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy b/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy index b14a10e431f..9486fc432aa 100644 --- a/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy +++ b/telemetry/src/test/groovy/datadog/telemetry/metric/WafMetricPeriodicActionSpecification.groovy @@ -43,16 +43,16 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { void 'push waf request metrics and push into the telemetry'() { when: WafMetricCollector.get().wafInit('0.0.0', 'rules_ver_1', true) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false) - WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false) - WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true) + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true, false) WafMetricCollector.get().prepareMetrics() periodicAction.doIteration(telemetryService) @@ -75,6 +75,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -91,6 +92,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -107,6 +109,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -123,6 +126,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -139,6 +143,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -155,6 +160,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:true', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -171,6 +177,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:true', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -187,20 +194,21 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:true', + 'request_excluded:none', ] } ) 0 * _._ when: 'waf.updates happens' WafMetricCollector.get().wafUpdates('rules_ver_2', true) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false) - WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false) - WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false) - WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true) + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(true, false, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, true, false, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, true, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, true, false, false, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, false, true, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, true, false, false, false) + WafMetricCollector.get().wafRequest(false, false, false, false, false, false, true, false) WafMetricCollector.get().prepareMetrics() periodicAction.doIteration(telemetryService) @@ -223,6 +231,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -239,6 +248,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -255,6 +265,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -271,6 +282,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -287,6 +299,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -303,6 +316,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:true', 'rate_limited:false', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -319,6 +333,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:true', 'input_truncated:false', + 'request_excluded:none', ] } ) 1 * telemetryService.addMetric( { Metric metric -> @@ -335,6 +350,7 @@ class WafMetricPeriodicActionSpecification extends DDSpecification { 'block_failure:false', 'rate_limited:false', 'input_truncated:true', + 'request_excluded:none', ] } ) 0 * _._ From 9b295705e8027d5a12c275172c7239f0918307fd Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Fri, 26 Jun 2026 08:34:26 +0200 Subject: [PATCH 5/5] ci: retrigger pipeline