Skip to content

debugger: scrub API Security schema tags from exception replay span approvals - #7543

Merged
dromanol merged 1 commit into
mainfrom
dani/scrub-api-security-schemas-exception-replay
Aug 20, 2026
Merged

dromanol merged 1 commit into
mainfrom
dani/scrub-api-security-schemas-exception-replay

Conversation

@dromanol

Copy link
Copy Markdown
Contributor

Motivation

Test_Debugger_Exception_Replay::test_exception_replay_rockpaperscissors compares the aspnet_core.request spans against a stored approval, and the .NET tracer is about to start emitting API Security schemas (_dd.appsec.s.*) on error responses (dd-trace-dotnet#9082 makes the response phase reach the WAF for server-generated 500s).

Those tags cannot be pinned in an approval: API Security samples schemas once per (route, method, status) per DD_API_SECURITY_SAMPLE_DELAY (30s by default), which is the same order of magnitude as this test's own retry interval (_timeout_next = 30). Which of the three rock/paper/scissors requests carries the schemas is therefore a race — in the failing run it was the rock span, and it could just as easily be another one or none.

Changes

Drop every _dd.appsec.s.* key in _validate_spans, the same treatment the _dd.appsec.fp.* fingerprints already get. No approval file currently contains those keys, so no expected data changes.

Workflow

  1. ⚠️ Create your PR as draft ⚠️
  2. Work on you PR until the CI passes
  3. Mark it as ready for review
    • Test logic is modified? -> Get a review from RFC owner.
    • Framework is modified, or non obvious usage of it -> get a review from R&P team

🚀 Once your PR is reviewed and the CI green, you can merge it!

🛟 #apm-shared-testing 🛟

Reviewer checklist

  • Anything but tests/ or manifests/ is modified ? I have the approval from R&P team
  • A docker base image is modified?
    • the relevant build-XXX-image label is present
  • A scenario is added, removed or renamed?

…pprovals

API Security samples schemas per (route, method, status) within a time window
(30s by default), the same order as the test's own retry interval, so whether a
given request carries `_dd.appsec.s.*` is not deterministic. Remove those keys
like the `_dd.appsec.fp.*` fingerprints already are.
@github-actions

Copy link
Copy Markdown
Contributor

CODEOWNERS have been resolved as:

tests/debugger/test_debugger_exception_replay.py                        @DataDog/debugger @DataDog/system-tests-core

@datadog-datadog-prod-us1-2 datadog-datadog-prod-us1-2 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.

Datadog Autotest: PASS

More details

The prefix filter removes nondeterministic API Security schema tags from both supported span formats before approval comparison, while it keeps unrelated validation unchanged.

Was this helpful? React 👍 or 👎

Open Bits AI session

🤖 Datadog Autotest · Commit a605ce2 · What is Autotest? · @DataDog review to ask questions · Any feedback? Reach out in #autotest

@dromanol
dromanol enabled auto-merge (squash) August 20, 2026 09:05

@dudikeleti dudikeleti 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.

Thanks

@dromanol
dromanol merged commit c688030 into main Aug 20, 2026
422 checks passed
@dromanol
dromanol deleted the dani/scrub-api-security-schemas-exception-replay branch August 20, 2026 09:31
dromanol added a commit to DataDog/dd-trace-dotnet that referenced this pull request Sep 9, 2026
…the request addresses (#9082)

## Summary of changes

- `server.response.status` reaches the WAF with the real status code: it
is never sent during the request-phase scans, and it is sent once the
real response status is known. On ASP.NET Core requests that reach the
end of the pipeline without an endpoint, both `Scan(lastTime: true)` and
the later response scan can carry it — the WAF doesn't report the same
match twice.
- On ASP.NET Core the request address set is collected once per request
instead of on every run. ASP.NET Framework keeps refreshing it on each
`BeginRequest`, including the second pipeline a `Server.TransferRequest`
creates, which is deliberate.
- Responses the WAF never saw get a report-only scan at the end of the
pipeline: HTTP.sys, which never fires the instrumented response start
hook, and server-generated error responses on Kestrel.

## Reason for change

The status came from the request-phase address set, where
`HttpContext.Response.StatusCode` is still the default `200`, so status
rules were fed a fabricated value — and on HTTP.sys that was the only
status the WAF ever saw. Resending every request address on the later
runs also re-evaluated the whole request rule set for no new
information.

The same gap existed on Kestrel for responses the server writes itself.
On an unhandled exception the 500 is produced by `ProduceEnd()`, after
`application.ProcessRequestAsync()` has returned, so the scope set by
the diagnostic observer inside that call is no longer visible in the
`AsyncLocal`: `FireOnStarting` finds no active span and skips the WAF
entirely. Those requests reached the WAF without a response status or
response headers at all.

## Implementation details

Two one-shot flags on `AppSecRequestContext`, both driving the Core
path. Persistent addresses live for the whole WAF context, so later runs
re-evaluate rules, schemas and fingerprints against the stored values.

`server.request.cookies` on ASP.NET Framework is the one address the
end-of-request run re-reads. ASP.NET puts `ASP.NET_SessionId` in
`Request.Cookies` when the session id is read, which
`SessionStateModule` normally does at `AcquireRequestState`, well before
`EndRequest`; the explicit `SessionID` read in `GetEndRequestArgsForWaf`
is the guard for hosts where the cookie isn't visible yet, and it has to
happen before the cookies are snapshotted. Without the re-read the
cookie halves of `_dd.appsec.fp.session` go empty.

## Test coverage

New unit tests against the real WAF: status rule matching per phase,
schemas surviving a run without resupplied addresses, session
fingerprint vs late cookies, server detection. Coordinator-level tests
for the one-shot address collection, the omission of the status during
the request phase and the real status at the end of the request, on both
Core and Framework. Green locally: Security unit tests net8.0/net48,
Security integration AspNetCore net8.0 + AspNetMvc5 net48, and
system-tests `APPSEC_BLOCKING`, `APPSEC_API_SECURITY`,
`APPSEC_WAF_TELEMETRY`, `APPSEC_RASP`, `APPSEC_AUTO_EVENTS_EXTENDED`,
`DEFAULT`.

Because error responses now reach the last WAF run, API Security samples
them too and error spans can carry `_dd.appsec.s.*` schemas. That breaks
the `DEBUGGER_EXCEPTION_REPLAY` span approvals, where the schemas land
on a different request every run (the sampler's 30s window against the
test's 30s retry interval): DataDog/system-tests#7543 scrubs them like
the `_dd.appsec.fp.*` fingerprints already are.

## Other details

Replaces #8856 (rewritten from scratch, not rebased). Left out on
purpose: registering `Response.OnStarting(...)` from
`BlockingMiddleware` would work on every server and let HTTP.sys block,
but it means removing a calltarget and redoing the blocking and ordering
story.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants