fix: an exception in BeforeSend or BeforeSendTransaction now drops the item instead of sending it - #5610
fix: an exception in BeforeSend or BeforeSendTransaction now drops the item instead of sending it#5610jamescrosswell wants to merge 5 commits into
Conversation
…e item Work item 3 of #5535. Both callbacks previously demystified the exception, stapled its message and stack trace onto the item as a breadcrumb, and sent the item anyway. The spec says a callback failure MUST NOT be attached to the item, and that filters drop — a callback that threw part-way may not have applied the redaction the user wrote it to apply, so the partially-scrubbed item plus the exception detail were both reaching Sentry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## fix/callback-client-reports #5610 +/- ##
===============================================================
- Coverage 74.96% 74.91% -0.05%
===============================================================
Files 515 515
Lines 18993 18968 -25
Branches 3698 3692 -6
===============================================================
- Hits 14238 14210 -28
- Misses 3874 3878 +4
+ Partials 881 880 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ric-oliv
left a comment
There was a problem hiding this comment.
Looks good! Just two notes my agent flagged:
Android native BeforeSend still fails open. Platforms/Android/Callbacks/BeforeSendCallback.cs catches the exception, logs "Before Send Error" and returns the original native event (return e), so a scrubber that throws partway still lets the event through. It only applies to apps that opt into Native.EnableBeforeSend, and it wasn't in #5535's conformance table, but it's the same gap this PR closes elsewhere. Could we align it here, or track it separately and change Closes #5535 to Part of?
Worth a line in the release note (not a blocker). On iOS, BeforeSend also runs on native crashes (Platforms/Cocoa/SentrySdk.cs:321), where @event.Exception is null. A callback that dereferences it currently still gets the crash reported, with a breadcrumb. After this PR it's silently dropped. Something like "BeforeSend also runs on iOS native crashes, where Exception is null; a throwing callback now drops the event" would help people notice. A ProcessOnBeforeSend test with a throwing callback would also pin down that path.
…ng it through Review feedback on #5606. Failing open meant a filter written to exclude entries would, once broken, start sending them. Before this PR the exception reached the app and nothing was sent, so dropping keeps that outcome while removing the crash, and matches the filter rule applied to BeforeSend in #5610. Also aligns the internal screenshot callback names with the public SetBeforeScreenshotCapture, and fixes a doc sample naming a method that no longer exists. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ah, that's actually a problem. We use serialisation to convert the Java object to a managed one, so we can run the managed beforesend callback. We don't want issues with that serialisation to prevent the event from being sent. However that catches issues in the beforesend and then still sends the original event - meaning any problems in the user's beforesend callback (maybe a callback they wrote to strip PII) would result in all events being sent unredacted... not good. It's not introduced by this PR but it is the kind of thing this PR is supposed to fix, so we should address it. |
…unscrubbed native event The JNI bridge wrapped its whole body in a catch that returns the original Java event, so a scrubber that threw part-way sent the completely unredacted native event. That catch was added for serialization safety, so the guard goes around the user callback inside BeforeSendWrapper instead: it returns null, which the Java SDK turns into a drop and accounts for in its own client report, and Execute's catch keeps failing open only for serialization. Also covers the Cocoa native-crash path, where a throwing callback now drops the event and previously had no test at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…o fix/before-send-failure-drops
…eforeSend family Sentry.Tests.SentrySdkTests already has six ProcessOnBeforeSend tests inside an #if __IOS__ block. A second SentrySdkTests class under Platforms/iOS was a confusing duplicate name in a parallel namespace. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Work item 3 ("align
BeforeSend/BeforeSendTransactionwith the matrix") of #5535, the last one. Stacked on #5607 — review that one first; this branch targetsfix/callback-client-reports.Three
BeforeSendroutes did the same thing when a user's callback threw: swallow the exception and send the item anyway.BeforeSendbefore_send/errorBeforeSendTransactionbefore_send/transaction+spanBeforeSendFor the two managed callbacks this deviated from the spec twice — a failure MUST NOT be attached to the item, and the matrix says filters drop, with the rationale spelled out: "filters drop because a callback that failed part-way may not have applied the redaction the user wrote it to apply."
That's the realistic case in .NET, where the commonest job for
BeforeSendis PII scrubbing. A callback that threw mid-scrub meant both the partially-redacted event and the exception message and stack trace we stapled to it were sent — a user's redaction failure turning into two kinds of unintended data landing in their org. Hence handling this as a bug rather than holding it for a major.Both managed paths now match
DoBeforeSendFeedback, which was already conformant.The Android bridge (added after review)
Not in #5535's conformance table, and not a regression from this PR, but the same gap and a worse outcome — so it's fixed here rather than left behind a
Closes.BeforeSendCallback.Executewraps its whole body in atryand returnseon failure — the original Java event, not the managed one the callback was mutating. So a scrubber that threw part-way didn't send a partially-redacted event; it sent the completely unredacted native one. That catch arrived in #4022 aimed at serialization safety ("native types tend to move before dotnet does"), and swallowing the user callback was collateral.The fix keeps those two concerns apart. The user callback is invoked one level in, inside
BeforeSendWrapper, so guarding it there returnsnullfor a user failure — which the Java SDK turns into a drop — and leavesExecute's catch doing only the serialization fail-open it was written for.No managed client report on that path: sentry-java already records
BEFORE_SEND/Erroritself when the callback returns null (SentryClient.java#L163), and native events are the native SDK's to account for. Recording it again on the managed side would double-count.Scope:
Native.EnableBeforeSendis opt-in and defaults tofalse. Managed events never cross the JNI bridge.SuppressSegfaultsinstalls the same wrapper without calling user code, so that route can't reach the guard.Notes for review
DoBeforeSendrecordsbefore_send/erroritself;BeforeSendTransactionreturnsnullintoCaptureTransaction's existing drop branch, which recordsbefore_sendfor the transaction and its spans. Duplicating the span accounting inside the callback helper would have been worse.e.Demystify()goes with the breadcrumb. It existed only to make the stack trace readable in that breadcrumb, and was the reason both methods carried an[UnconditionalSuppressMessage("Trimming", "IL2026")]and anAotHelper.IsTrimmedguard — all now gone.Ben.Demystifieris still used byDebugStackTraceforStackTraceMode.Enhanced, so nothing is orphaned.CaptureEvent_BeforeEventThrows_ErrorToEventBreadcrumbandCaptureTransaction_BeforeSendTransactionThrows_ErrorToEventBreadcrumbpinned the old behaviour and are deleted, along with their three Verify snapshots and the now-emptySentryClientTests.verify.cs. Replaced with ordinary assertions covering the drop, the client report, the error log, and the absence of any breadcrumb on the item.BeforeSend, andSentryEvent.Exceptionis null there, so a callback that dereferences it throws on that path only — previously reported with a breadcrumb, now dropped.ProcessOnBeforeSendhad no coverage for a throwing callback; it does now. The underlying null-Exceptionproblem is not introduced here and is tracked separately in iOS: SentryEvent.Exception is null for native crashes, so a BeforeSend callback that dereferences it throws #5620.Closes #5535
🤖 Generated with Claude Code