Skip to content

ref: add the DataCollection resolver and SendDefaultPii bridge - #5885

Merged
jamescrosswell merged 4 commits into
mainfrom
feat/data-collection-resolver
Nov 9, 2026
Merged

jamescrosswell merged 4 commits into
mainfrom
feat/data-collection-resolver

Conversation

@jamescrosswell

@jamescrosswell jamescrosswell commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #5426

Adds DataCollectionResolver, the single read path integrations will use for data collection policy, and the SendDefaultPii bridge behind it. Nothing reads it yet — each call site moves over in its own Phase 2 issue — so this is behaviour-neutral.

Resolution is the two-step rule from the resolution-model comment, now that #5853 made every category non-nullable: options.DataCollection is null (nobody called ConfigureDataCollection, nothing bound from configuration) → derive each category from SendDefaultPii; otherwise read the object and ignore SendDefaultPii.

Decisions this settles

Lazy, not resolve-once. SentryOptions stays mutable after Init and tests mutate it, so a snapshot taken at init would silently ignore later edits. The resolver holds the options and recomputes per query, like io.sentry.DataCollectionResolver. Cost is a null check and a field read; the two default KeyValueCollectionBehavior instances are cached statics, which is safe now that #5853 made them immutable.

The SendDefaultPii = false bridge does not extend deny lists with the GDPR terms, contrary to the issue text, which came from the JS bridge. The .NET SDK has never filtered X-Forwarded-For and friends, so adding those terms here would make the Phase 2 header migration ship a behaviour change inside a refactor. Headers and query params therefore resolve to a plain DenyList() either way, and cookies to Off when SendDefaultPii is false. This matches what sentry-java's resolver actually does (explicitOrEmptyDenyList, and off() for cookies), as opposed to what the spec prescribes for a from-scratch SDK. Turning the terms on by default for legacy users stays available as a deliberate change in #5422.

That also makes the KeyValueCollectionBehavior.DenyList doc wrong where it says those terms "restore the header filtering that SendDefaultPii applied when disabled" — there was none to restore. Reworded to present them as the terms the spec names for filtering end-user-identifying keys.

The legacy gate is per call site, not per category. The global fallbacks above are faithful to the server-side capture path, where SendDefaultPii = false skips only the Cookie header and collects every other header and the query string regardless (ScopeExtensions.cs:140, SystemWebRequestEventProcessor.cs:62). The client-side handlers are all-or-nothing on headers instead: SentryHttpFailedRequestHandler and SentryGraphQLHttpFailedRequestHandler attach no request or response headers and neither cookie set when it is off. Migrating those onto the global RequestHeaders / ResponseHeaders would start sending that data for SendDefaultPii = false apps, so each needs a sibling accessor falling back to SendDefaultPii ? DenyList : Off — the shape cookies already have — landing with its consumer, as sentry-java does with isGraphqlDocumentWithLegacyBodyGate().

Request.Url is not one of them, and UrlQueryParams needs no gated variant. HttpRequestUrl() keeps the query string — what it drops is user:pass@ and the fragment — so the query reaches Sentry whichever way SendDefaultPii is set, in Request.Url and in the unconditionally-set Request.QueryString. Both handlers pin http://localhost/test/path?query=string with PII off (SentryHttpFailedRequestHandlerTests.cs:238, SentryGraphQlHttpFailedRequestHandlerTests.cs:150), which a gated accessor resolving to Off would break. So the global accessor applies to both fields, and what the URL gate really decides — whether credentials and the fragment are sent — is no DataCollection category at all: RedactUrl is the SDK's answer to credentials in URLs but never runs on Request.Url. Left to #5429 as its own call. Noted on #5429 and #5432 with the per-field detail. Thanks @ric-oliv for both catches.

HTTP bodies resolve per direction (IncomingRequestBody, OutgoingRequestBody, …) rather than exposing HttpBodyTypes, because that is what a call site needs and because the legacy gate differs per direction:

  • OutgoingRequestBody / IncomingResponseBody fall back to SendDefaultPii, which is what gates them today (SentryGraphQLHttpFailedRequestHandler.cs:74 and :78).
  • IncomingRequestBody falls back to true. MaxRequestBodySize has always been its only gate, in both ASP.NET Core and AddAspNet — RequestBodyExtractionDispatcher switches on RequestSize and never reads SendDefaultPii — so a SendDefaultPii fallback would cost an app that set MaxRequestBodySize its request bodies once DataCollection phase 2: migrate Sentry.AspNetCore #5430 lands.
  • OutgoingResponseBody has no consumer today, so there is no behaviour to preserve; it follows SendDefaultPii as the conservative default.

GenAI content follows SendDefaultPii, and that is a deliberate change for #5434 to ship visibly. Sentry.Extensions.AI never reads SendDefaultPii, and RecordInputs / RecordOutputs default to true, so a default app records prompts and completions today. Gating them is what the spec and the other SDKs do, and SendDefaultPii = false failing to suppress the most PII-dense data the SDK collects is the anomaly — but it is user-visible, so it belongs in #5434 with a changelog entry rather than arriving inside a refactor. Noted there.

Docs

The XML docs said these options were inert. That stays true until Phase 2 migrates the first consumer, so rather than deleting the caveats as the issue note suggested, they now name the real state: ConfigureDataCollection scopes the SendDefaultPii replacement to the integrations that have moved over, and the caveat says that until they do, SendDefaultPii still decides what they collect and setting a category has no effect. The caveat goes away for real as Phase 2 lands.

No public API change, so no approval-snapshot churn.

#skip-changelog

🤖 Generated with Claude Code

Integrations will read their data collection policy from
DataCollectionResolver rather than from SendDefaultPii or DataCollection
directly. Resolution is lazy, since SentryOptions stays mutable after
Init, and falls back to SendDefaultPii while DataCollection is null.

No consumer migrated yet, so this is behaviour-neutral.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.81%. Comparing base (ccf4820) to head (aa32810).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5885      +/-   ##
==========================================
+ Coverage   76.77%   76.81%   +0.03%     
==========================================
  Files         532      533       +1     
  Lines       19826    19850      +24     
  Branches     3909     3920      +11     
==========================================
+ Hits        15222    15248      +26     
+ Misses       3718     3717       -1     
+ Partials      886      885       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jamescrosswell
jamescrosswell marked this pull request as ready for review November 6, 2026 00:03
@github-actions github-actions Bot added the risk: medium PR risk score: medium label Nov 6, 2026
Comment thread src/Sentry/SentryOptions.cs Resolved
Comment thread src/Sentry/DataCollectionOptions.cs Resolved
jamescrosswell and others added 2 commits November 9, 2026 08:33
…lver tests

Incoming request bodies have never been gated on SendDefaultPii:
RequestBodyExtractionDispatcher switches on RequestSize alone, and both
ASP.NET Core and AddAspNet call it unconditionally. That direction now
falls back to true, so #5430 cannot cost an app that set
MaxRequestBodySize its request bodies.

Tests configure one body direction at a time, and leave UserInfo at its
default where DatabaseQueryData is the subject, so that reading the wrong
category fails.

Docs say what is true today: until the integrations migrate, SendDefaultPii
still decides what they collect, and setting a category - not only
restricting one - has no effect yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test and the PR description already carry the reasoning, and src/ aims
for no comments.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@ric-oliv ric-oliv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM!

@jamescrosswell
jamescrosswell merged commit 4cd0e78 into main Nov 9, 2026
58 checks passed
@jamescrosswell
jamescrosswell deleted the feat/data-collection-resolver branch November 9, 2026 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DataCollection phase 1: resolver and SendDefaultPii bridge

2 participants