Skip to content

Commit 933cb97

Browse files
authored
Add appsec.waf.input_truncated metric
This PR adds support for a new telemetry metric: appsec.waf.input_truncated. This is a count metric that tracks the number of times a WAF input was truncated, which may happen multiple times per request. The metric includes a truncation_reason tag, represented as a bitfield, with the following values: 1: string too long 2: list or map too large 4: object too deep Additional Notes For every call to WAF, if truncation occurred during serialization, we should emit the metric. This will increment the count for each run where truncation was detected, and each metric will include the bitfield indicating the types of truncation that occurred. This metric should also be triggered when ObjectInstrospector truncates the object send to the WAF. This corner case affects parsed request body and grpc. This should be fixed after #8748
1 parent a1ad28f commit 933cb97

File tree

3 files changed

+63
-0
lines changed

3 files changed

+63
-0
lines changed

dd-java-agent/appsec/src/main/java/com/datadog/appsec/ddwaf/WAFModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,8 @@ public void onDataAvailable(
465465

466466
if (stringTooLong > 0 || listMapTooLarge > 0 || objectTooDeep > 0) {
467467
reqCtx.setWafTruncated();
468+
WafMetricCollector.get()
469+
.wafInputTruncated(stringTooLong > 0, listMapTooLarge > 0, objectTooDeep > 0);
468470
}
469471
}
470472
}

internal-api/src/main/java/datadog/trace/api/telemetry/WafMetricCollector.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
public class WafMetricCollector implements MetricCollector<WafMetricCollector.WafMetric> {
1515

16+
private static final int MASK_STRING_TOO_LONG = 1; // 0b001
17+
private static final int MASK_LIST_MAP_TOO_LARGE = 1 << 1; // 0b010
18+
private static final int MASK_OBJECT_TOO_DEEP = 1 << 2; // 0b100
19+
1620
public static WafMetricCollector INSTANCE = new WafMetricCollector();
1721

1822
public static WafMetricCollector get() {
@@ -34,6 +38,9 @@ private WafMetricCollector() {
3438
private static final int WAF_REQUEST_COMBINATIONS = 128; // 2^7
3539
private final AtomicLongArray wafRequestCounter = new AtomicLongArray(WAF_REQUEST_COMBINATIONS);
3640

41+
private static final AtomicLongArray wafInputTruncatedCounter =
42+
new AtomicLongArray(1 << 3); // 3 flags → 2^3 = 8 possible bit combinations
43+
3744
private static final AtomicLongArray raspRuleEvalCounter =
3845
new AtomicLongArray(RuleType.getNumValues());
3946
private static final AtomicLongArray raspRuleSkippedCounter =
@@ -104,6 +111,12 @@ public void wafRequest(
104111
wafRequestCounter.incrementAndGet(index);
105112
}
106113

114+
public void wafInputTruncated(
115+
final boolean stringTooLong, final boolean listMapTooLarge, final boolean objectTooDeep) {
116+
int index = computeWafInputTruncatedIndex(stringTooLong, listMapTooLarge, objectTooDeep);
117+
wafInputTruncatedCounter.incrementAndGet(index);
118+
}
119+
107120
static int computeWafRequestIndex(
108121
boolean ruleTriggered,
109122
boolean requestBlocked,
@@ -123,6 +136,15 @@ static int computeWafRequestIndex(
123136
return index;
124137
}
125138

139+
static int computeWafInputTruncatedIndex(
140+
boolean stringTooLong, boolean listMapTooLarge, boolean objectTooDeep) {
141+
int index = 0;
142+
if (stringTooLong) index |= MASK_STRING_TOO_LONG;
143+
if (listMapTooLarge) index |= MASK_LIST_MAP_TOO_LARGE;
144+
if (objectTooDeep) index |= MASK_OBJECT_TOO_DEEP;
145+
return index;
146+
}
147+
126148
public void raspRuleEval(final RuleType ruleType) {
127149
raspRuleEvalCounter.incrementAndGet(ruleType.ordinal());
128150
}
@@ -216,6 +238,16 @@ public void prepareMetrics() {
216238
}
217239
}
218240

241+
// WAF input truncated
242+
for (int i = 0; i < (1 << 3); i++) {
243+
long counter = wafInputTruncatedCounter.getAndSet(i, 0);
244+
if (counter > 0) {
245+
if (!rawMetricsQueue.offer(new WafInputTruncated(counter, i))) {
246+
return;
247+
}
248+
}
249+
}
250+
219251
// RASP rule eval per rule type
220252
for (RuleType ruleType : RuleType.values()) {
221253
long counter = raspRuleEvalCounter.getAndSet(ruleType.ordinal(), 0);
@@ -516,6 +548,12 @@ public WafError(final long counter, final String wafVersion, final Integer ddwaf
516548
}
517549
}
518550

551+
public static class WafInputTruncated extends WafMetric {
552+
public WafInputTruncated(final long counter, final int bitfield) {
553+
super("waf.input_truncated", counter, "truncation_reason:" + bitfield);
554+
}
555+
}
556+
519557
/**
520558
* Mirror of the {@code WafErrorCode} enum defined in the {@code libddwaf-java} module.
521559
*

internal-api/src/test/groovy/datadog/trace/api/telemetry/WafMetricCollectorTest.groovy

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,29 @@ class WafMetricCollectorTest extends DDSpecification {
462462
[triggered, blocked, wafError, wafTimeout, blockFailure, rateLimited, inputTruncated] << allBooleanCombinations(7)
463463
}
464464

465+
void 'test waf input truncated metrics'() {
466+
given:
467+
def collector = WafMetricCollector.get()
468+
def bitField = WafMetricCollector.computeWafInputTruncatedIndex(stringTooLong, listMapTooLarge, objectTooDeep)
469+
470+
when:
471+
collector.wafInputTruncated(stringTooLong, listMapTooLarge, objectTooDeep)
472+
473+
then:
474+
collector.prepareMetrics()
475+
def metrics = collector.drain()
476+
def inputTruncatedMetrics = metrics.findAll { it.metricName == 'waf.input_truncated' }
477+
478+
final metric = inputTruncatedMetrics[0]
479+
metric.type == 'count'
480+
metric.metricName == 'waf.input_truncated'
481+
metric.namespace == 'appsec'
482+
metric.tags == ["truncation_reason:${bitField}"]
483+
484+
where:
485+
[stringTooLong, listMapTooLarge, objectTooDeep] << allBooleanCombinations(3)
486+
}
487+
465488
/**
466489
* Helper method to generate all combinations of n boolean values.
467490
*/

0 commit comments

Comments
 (0)