Skip to content

fix(appsec): stop counting WAF and RASP timeouts as errors - #5828

Merged
RomainMuller merged 1 commit into
mainfrom
agent/implementer/6c853366257c
Nov 6, 2026
Merged

RomainMuller merged 1 commit into
mainfrom
agent/implementer/6c853366257c

Conversation

@RomainMuller

@RomainMuller RomainMuller commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Stops counting a WAF or RASP timeout as an error in Go AppSec telemetry.

Before, one timeout was counted two times:

  • RegisterWafRun counted it as a timeout: rasp.timeout (RASP) or waf.requests{waf_timeout:true} (WAF scope).
  • IncWafError counted it again as an error: rasp.error{waf_error:4} or waf.error{waf_error:4}. It also stored code 4 in RASPErrorCodes / WAFErrorCode. These codes set the _dd.appsec.rasp.error and _dd.appsec.waf.error span tags. The code 4 of a timeout could also hide the code of a real error in these tags.

Now, IncWafError skips only the raspError / wafError calls when errors.Is(err, waferrors.ErrTimeout). There is no early return. The scope and rule type checks stay, so a timeout with an unknown scope or an unknown RASP rule type still records an instrumentation exception. run.go and RegisterWafRun do not change.

Tests:

  • TestRunWAFTimeoutIsNotAnError (through runWAF): bare and wrapped timeouts for RASP and WAF scope; unknown rule type and unknown scope with a timeout; ErrInternal and a non-RunError are still counted.
  • TestRunWAFErrorAndTimeoutOrder: an error and a timeout in the same request, in both orders, for RASP and WAF scope.
  • TestIncWafErrorTimeout: direct test of IncWafError.
  • TestWAFErrorCodeSpanTag: a timeout sets no error tag; a timeout does not hide a real error.

Motivation

Action 3 of the DD-17 report (internal). Jira: APPSEC-71527.

Known limit, not changed here: on main, a RASP timeout with an unknown rule type is not counted in rasp.timeout, because RegisterWafRun returns before the timeout count. The instrumentation exception is still recorded.

Out of scope: the 2 ms default timeout and a separate RASP budget (APPSEC-62654), and the docs fix.

Release note

AppSec telemetry: a WAF or RASP timeout is no longer also counted as an error.

  • Before: one timeout was counted 2 times: as a timeout, and as rasp.error{waf_error:4} (RASP) or waf.error{waf_error:4} (WAF scope).
  • After: RASP timeouts increment rasp.timeout; WAF-scope timeouts mark waf.requests{waf_timeout:true}. They no longer increment rasp.error / waf.error. The span tags _dd.appsec.waf.error and _dd.appsec.rasp.error are no longer set for a timeout. _dd.appsec.waf.timeouts and _dd.appsec.rasp.timeout do not change.
  • Impact: for Go services, the volume of rasp.error and waf.error drops. The waf_error:4 series goes to zero. Monitors and dashboards that use these metrics will change when the services upgrade. To follow timeouts, use rasp.timeout (RASP) and waf.requests{waf_timeout:true} (WAF scope).

Jira

APPSEC-71527

Red → green proof

Command (same in both runs):

go test -count=1 -v -run 'TestRunWAFTimeoutIsNotAnError|TestRunWAFErrorAndTimeoutOrder|TestIncWafErrorTimeout|TestWAFErrorCodeSpanTag' \
  ./internal/appsec/emitter/waf/ ./internal/appsec/listener/waf/

The cases with a valid scope and rule type that contain a timeout fail without the fix. The unknown-scope and unknown-rule-type cases pass in both runs: they guard the instrumentation exception.

Red: new tests on main, without the fix in metrics.go
--- FAIL: TestIncWafErrorTimeout
    --- FAIL: TestIncWafErrorTimeout/waf-scope
    --- FAIL: TestIncWafErrorTimeout/rasp-sqli
    --- PASS: TestIncWafErrorTimeout/rasp-without-rule-address
    --- PASS: TestIncWafErrorTimeout/unknown-scope
--- FAIL: TestRunWAFTimeoutIsNotAnError
    --- FAIL: TestRunWAFTimeoutIsNotAnError/rasp/timeout
    --- FAIL: TestRunWAFTimeoutIsNotAnError/rasp/wrapped-timeout
    --- FAIL: TestRunWAFTimeoutIsNotAnError/waf/timeout
    --- FAIL: TestRunWAFTimeoutIsNotAnError/waf/wrapped-timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/rasp/unknown-rule-type/timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/unknown-scope/timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/rasp/internal-error
    --- PASS: TestRunWAFTimeoutIsNotAnError/waf/internal-error
    --- PASS: TestRunWAFTimeoutIsNotAnError/waf/non-run-error
--- FAIL: TestRunWAFErrorAndTimeoutOrder
    --- FAIL: TestRunWAFErrorAndTimeoutOrder/rasp/error-then-timeout
    --- FAIL: TestRunWAFErrorAndTimeoutOrder/waf/error-then-timeout
    --- FAIL: TestRunWAFErrorAndTimeoutOrder/rasp/timeout-then-error
    --- FAIL: TestRunWAFErrorAndTimeoutOrder/waf/timeout-then-error
FAIL
FAIL	github.com/DataDog/dd-trace-go/v2/internal/appsec/emitter/waf	0.625s
--- FAIL: TestWAFErrorCodeSpanTag
    --- PASS: TestWAFErrorCodeSpanTag/waf_error_emits_code_not_count
    --- PASS: TestWAFErrorCodeSpanTag/no_waf_error_no_tag
    --- PASS: TestWAFErrorCodeSpanTag/rasp_error_closest_to_zero_across_rule_types
    --- FAIL: TestWAFErrorCodeSpanTag/waf_timeout_sets_no_error_tag
    --- FAIL: TestWAFErrorCodeSpanTag/rasp_timeout_sets_no_error_tag
    --- FAIL: TestWAFErrorCodeSpanTag/waf_timeout_does_not_hide_real_error/error_then_timeout
    --- FAIL: TestWAFErrorCodeSpanTag/rasp_timeout_does_not_hide_real_error/error_then_timeout
    --- FAIL: TestWAFErrorCodeSpanTag/waf_timeout_does_not_hide_real_error/timeout_then_error
    --- FAIL: TestWAFErrorCodeSpanTag/rasp_timeout_does_not_hide_real_error/timeout_then_error
FAIL
FAIL	github.com/DataDog/dd-trace-go/v2/internal/appsec/listener/waf	0.611s
FAIL

Failure reasons: rasp.error / waf.error = 1 instead of 0; error code 4 instead of 0 (or instead of 1 when an ErrInternal was also seen); _dd.appsec.waf.error / _dd.appsec.rasp.error set to 4.

Green: same tests with the fix
--- PASS: TestIncWafErrorTimeout
    --- PASS: TestIncWafErrorTimeout/waf-scope
    --- PASS: TestIncWafErrorTimeout/rasp-sqli
    --- PASS: TestIncWafErrorTimeout/rasp-without-rule-address
    --- PASS: TestIncWafErrorTimeout/unknown-scope
--- PASS: TestRunWAFTimeoutIsNotAnError
    --- PASS: TestRunWAFTimeoutIsNotAnError/rasp/timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/rasp/wrapped-timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/waf/timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/waf/wrapped-timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/rasp/unknown-rule-type/timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/unknown-scope/timeout
    --- PASS: TestRunWAFTimeoutIsNotAnError/rasp/internal-error
    --- PASS: TestRunWAFTimeoutIsNotAnError/waf/internal-error
    --- PASS: TestRunWAFTimeoutIsNotAnError/waf/non-run-error
--- PASS: TestRunWAFErrorAndTimeoutOrder
    --- PASS: TestRunWAFErrorAndTimeoutOrder/rasp/error-then-timeout
    --- PASS: TestRunWAFErrorAndTimeoutOrder/waf/error-then-timeout
    --- PASS: TestRunWAFErrorAndTimeoutOrder/rasp/timeout-then-error
    --- PASS: TestRunWAFErrorAndTimeoutOrder/waf/timeout-then-error
ok  	github.com/DataDog/dd-trace-go/v2/internal/appsec/emitter/waf	0.741s
--- PASS: TestWAFErrorCodeSpanTag
    --- PASS: TestWAFErrorCodeSpanTag/waf_error_emits_code_not_count
    --- PASS: TestWAFErrorCodeSpanTag/no_waf_error_no_tag
    --- PASS: TestWAFErrorCodeSpanTag/rasp_error_closest_to_zero_across_rule_types
    --- PASS: TestWAFErrorCodeSpanTag/waf_timeout_sets_no_error_tag
    --- PASS: TestWAFErrorCodeSpanTag/rasp_timeout_sets_no_error_tag
    --- PASS: TestWAFErrorCodeSpanTag/waf_timeout_does_not_hide_real_error/error_then_timeout
    --- PASS: TestWAFErrorCodeSpanTag/rasp_timeout_does_not_hide_real_error/error_then_timeout
    --- PASS: TestWAFErrorCodeSpanTag/waf_timeout_does_not_hide_real_error/timeout_then_error
    --- PASS: TestWAFErrorCodeSpanTag/rasp_timeout_does_not_hide_real_error/timeout_then_error
ok  	github.com/DataDog/dd-trace-go/v2/internal/appsec/listener/waf	0.740s

Reviewer's Checklist

  • Changed code has unit tests for its functionality at or near 100% coverage.
  • System-Tests covering this feature have been added and enabled with the va.b.c-dev version tag. N/A: the change is a local classification in IncWafError. The new unit tests check the classification, the submitted metric values and the span tag values. The code that sends telemetry and span tags does not change. No existing system test reads rasp.error, waf.error, _dd.appsec.waf.error or _dd.appsec.rasp.error; those tests do not check the corrected metrics. A cross-tracer system test is a possible follow-up in DataDog/system-tests.
  • There is a benchmark for any new code, or changes to existing code. N/A: no new work on the hot path.
  • If this interacts with the agent in a new way, a system test has been added. N/A: no new agent interaction.
  • New code is free of linting errors. golangci-lint run ./internal/appsec/... reports 0 issues.
  • New code doesn't break existing tests. go test ./internal/appsec/... passes, with and without DD_APPSEC_ENABLED=true.
  • Add an appropriate team label so this PR gets put in the right place for the release notes. (appsec)
  • All generated files are up to date. You can check this by running make generate locally. (No generated file is affected.)
  • Non-trivial go.mod changes, e.g. adding new modules, are reviewed by @DataDog/dd-trace-go-guild. Make sure all nested modules are up to date by running make fix-modules locally. (No go.mod change.)

One WAF or RASP timeout was counted two times. `RegisterWafRun` counted
it as a timeout (`rasp.timeout`, `waf.requests{waf_timeout:true}`). Then
`IncWafError` also counted it as an error (`rasp.error{waf_error:4}`,
`waf.error{waf_error:4}`) and stored code 4 in `RASPErrorCodes` and
`WAFErrorCode`. These codes feed the `_dd.appsec.rasp.error` and
`_dd.appsec.waf.error` span tags. A timeout code could also hide a real
error code in these tags.

In `IncWafError`, skip only the `raspError` and `wafError` calls when
the error matches `waferrors.ErrTimeout`. Keep the scope and rule type
checks, so a timeout with an unknown scope or an unknown RASP rule type
still records an instrumentation exception. `run.go` and
`RegisterWafRun` do not change.

Add tests for: bare and wrapped timeouts through `runWAF`; real errors
that are still counted; an error and a timeout in the same request, in
both orders; the unknown scope and unknown rule type cases; and the
span tags.

APPSEC-71527
@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Pipelines  Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
• Patch Coverage: 100.00%
• Overall Coverage: 73.58% (-0.19%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 97d62fc | Docs | View more details | Give us feedback!

@pr-commenter

pr-commenter Bot commented Nov 5, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-11-05 16:10:56

Comparing candidate commit 97d62fc in PR branch agent/implementer/6c853366257c with baseline commit 1bdbf93 in branch main.

📊 Benchmarking dashboard

Found 0 performance improvements and 2 performance regressions! Performance is the same for 413 metrics, 1 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:BenchmarkHttpServeTraceQueryObfuscation/really_long_1

  • 🟥 execution_time [+0.684µs; +1.322µs] or [+2.818%; +5.445%]

scenario:BenchmarkHttpServeTraceQueryObfuscation/really_long_2

  • 🟥 execution_time [+579.142ns; +1236.058ns] or [+2.405%; +5.133%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:BenchmarkContextWithSpan/detach-no-snapshot

  • unstable execution_time [-1.338ns; +1.612ns] or [-4.545%; +5.476%]

@dd-octo-sts dd-octo-sts Bot mentioned this pull request Nov 5, 2026
@RomainMuller
RomainMuller marked this pull request as ready for review November 6, 2026 09:22
@RomainMuller
RomainMuller requested a review from a team as a code owner November 6, 2026 09:22
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-06T09:25:38.316944Z 97d62fc Draft marked ready
🔒 Security Review ✅ Completed 2026-11-06T09:26:05.219640Z 97d62fc Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97d62fc3c1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/appsec/emitter/waf/metrics.go Resolved

@datadog-datadog-prod-us1-2 datadog-datadog-prod-us1-2 Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: PASS

More details

Bare and wrapped timeouts leave error counters and error-code span tags unset, while genuine errors remain recorded when a timeout occurs in the same request. Scope and rule-type validation remain intact.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Bits Code Review · Commit 97d62fc

@RomainMuller
RomainMuller merged commit 132954b into main Nov 6, 2026
245 of 249 checks passed
@RomainMuller
RomainMuller deleted the agent/implementer/6c853366257c branch November 6, 2026 10:08
RomainMuller added a commit that referenced this pull request Nov 6, 2026
Resolve the conflicts with the timeout rework of #5828:

- keep main's IncWafError, and the A2 code of this branch
- keep main's TestIncWafErrorTimeout and the shared test helpers, and
  drop the tests of this branch that duplicate them
- update the tests of #5828 that assert the old positive error codes
RomainMuller added a commit that referenced this pull request Nov 6, 2026
Replace the section with the A2 content only: timeouts are handled by
#5828, so the section no longer describes them.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants