From 945bcc47d80c4c86f23deaad6ee64c2a8c11bc25 Mon Sep 17 00:00:00 2001 From: Clara Poncet Date: Mon, 14 Sep 2026 11:45:11 +0200 Subject: [PATCH] Consult AppSec trace rate limiter only when the WAF requests trace retention Co-Authored-By: Claude Opus 5 --- .../com/datadog/appsec/ddwaf/WAFModule.java | 19 ++++++++----------- .../ddwaf/WAFModuleSpecification.groovy | 19 +++++++++++++++++-- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java index 4c07c1e5a7a..16affc8ac59 100644 --- a/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java +++ b/dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java @@ -437,10 +437,10 @@ public void onDataAvailable( WafMetricCollector.get().raspRuleMatch(gwCtx.raspRuleType, flow.isBlocking()); } Collection events = buildEvents(resultWithData, securityResponseId); - boolean isThrottled = reqCtx.isThrottled(rateLimiter); - - if (!isThrottled) { - if (resultWithData.keep) { + // The limiter gates force-keeping the trace, not WAF execution or event reporting. + if (resultWithData.keep) { + boolean isThrottled = reqCtx.isThrottled(rateLimiter); + if (!isThrottled) { reqCtx.setManuallyKept(true); AgentSpan activeSpan = AgentTracer.get().activeSpan(); if (activeSpan != null) { @@ -456,13 +456,10 @@ public void onDataAvailable( .setTag(Tags.PROPAGATED_TRACE_SOURCE, ProductTraceSource.ASM); } } else { - // If active span is not available then we need to set manual keep in GatewayBridge - log.debug("There is no active span available"); - } - } else { - log.debug("Rate limited WAF events"); - if (!gwCtx.isRasp) { - reqCtx.setWafRateLimited(); + log.debug("Rate limited AppSec trace"); + if (!gwCtx.isRasp) { + reqCtx.setWafRateLimited(); + } } } diff --git a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy index 1c3bb0b32ff..8e988e37b3b 100644 --- a/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy +++ b/dd-java-agent/appsec/src/test/groovy/com/datadog/appsec/ddwaf/WAFModuleSpecification.groovy @@ -1917,7 +1917,6 @@ class WAFModuleSpecification extends DDSpecification { 1 * ctx.isWafContextClosed() >> false 1 * ctx.closeWafContext() 1 * ctx.reportDerivatives(['_dd.appsec.trace.agent':'RulesCompat/v1', '_dd.appsec.trace.integer': 123456789]) - 1 * ctx.isThrottled(null) // libddwaf 2.0.1: ResultWithData.events now reflects the real "events" array, so attributes-only matches don't call reportEvents(). 0 * ctx.reportEvents(_) 0 * ctx._(*_) @@ -1961,6 +1960,23 @@ class WAFModuleSpecification extends DDSpecification { 1 * ctx.setManuallyKept(true) 0 * ctx._(*_) !flow3.blocking + + when: 'test rate-limited rules_compat rule with attributes, keep and event' + def flow4 = new ChangeableFlow() + dataListener.onDataAvailable(flow4, ctx, bundle3, gwCtx) + ctx.closeWafContext() + + then: + 1 * ctx.getOrCreateWafContext(_, true, false) >> { wafContext = new WafContext(it[0]) } + 2 * ctx.getWafMetrics() >> metrics + 1 * ctx.isWafContextClosed() >> false + 1 * ctx.closeWafContext() + 1 * ctx.reportDerivatives(['_dd.appsec.trace.agent':'RulesCompat/v3', '_dd.appsec.trace.integer': 555666777]) + 1 * ctx.reportEvents(_ as Collection) + 1 * ctx.isThrottled(null) >> true + 1 * ctx.setWafRateLimited() + 0 * ctx._(*_) + !flow4.blocking } void 'test trace tagging rule with attributes, no keep and event (dynamic value extraction)'() { @@ -2061,7 +2077,6 @@ class WAFModuleSpecification extends DDSpecification { // Should report derivatives with dynamic value extraction - the user-agent value should be extracted 1 * ctx.reportDerivatives(['_dd.appsec.trace.agent':'TraceTagging/v4', '_dd.appsec.trace.integer': 1729]) 1 * ctx.reportEvents(_ as Collection) - 1 * ctx.isThrottled(null) 0 * ctx._(*_) !flow.blocking // Should not block since keep: false }