Skip to content

fix(replay): Route network detail through sentry-java's own hint - #3980

Open
hkarmoush wants to merge 4 commits into
getsentry:mainfrom
hkarmoush:fix-replay-network-detail-leak-local
Open

hkarmoush wants to merge 4 commits into
getsentry:mainfrom
hkarmoush:fix-replay-network-detail-leak-local

Conversation

@hkarmoush

@hkarmoush hkarmoush commented Aug 24, 2026 •

Copy link
Copy Markdown

📜 Description

Reworks the Session Replay network-detail leak fix per @buenaflor's review on #3900.

  • Dart side keeps captured HTTP request/response detail off the shared http breadcrumb, passing it via a Hint instead (unchanged from the earlier attempt).
  • Android side no longer uses a bespoke JNI side channel (ReplayNetworkDetailCache, keyed by a generated replay_request_id) to get that detail into a replay recording. Instead it's delivered through sentry-java's own SENTRY_REPLAY_NETWORK_DETAILS hint - the same mechanism SentryOkHttpInterceptor already uses - so DefaultReplayBreadcrumbConverter's own (already-tested) conversion logic builds the replay event, rather than a hand-rolled reimplementation.
  • addBreadcrumb now carries the network detail directly instead of a separate captureReplayNetworkDetail call, so there's one atomic native call instead of two - removing the race Bugbot flagged on the previous side-channel approach (fix(replay): Stop network detail capture leaking into non-replay events #3947).

💡 Motivation and Context

Closes the leak in #3900: captured HTTP bodies/headers (opt-in via enableReplayNetworkDetailsCapturing) were riding along on the shared http breadcrumb and ending up on unrelated events and native crash events, not just replay recordings.

Supersedes #3947, which used a bespoke JNI cache that Bugbot flagged a race in and @buenaflor asked to be reworked to route through sentry-java's existing mechanism instead: #3900 (comment)

💚 How did you test it?

  • Unit tests updated at the NativeScopeObserver / SentryNativeJava.addBreadcrumb seams (single-call contract, request/response detail merging).
  • dart analyze clean on both sentry and sentry_flutter; full test suites pass.
  • The new Android JNI binding entry (addBreadcrumbFromJsonBytes overload) is hand-authored, mirroring the existing pattern for [B descriptors in binding.dart, since regenerating it needs a full Android build toolchain not available in this environment - flagging for verification against a real jnigen run before merge.

📝 Checklist

  • I reviewed submitted code
  • I added tests to verify changes
  • No new PII added or SDK only sends newly added PII if sendDefaultPii is enabled
  • I updated the docs if needed
  • All tests passing
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec
  • No breaking changes

🔮 Next steps

Would like a maintainer (or CI with a real Android toolchain) to verify the hand-authored JNI binding entry in binding.dart against an actual jnigen run before merging.

@hkarmoush
hkarmoush force-pushed the fix-replay-network-detail-leak-local branch from 03b6649 to 0321cf3 Compare August 31, 2026 08:00

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0321cf3. Configure here.

Comment thread packages/dart/lib/src/scope.dart Outdated
Comment on lines +257 to +262
if (scopeObserver is HintAwareScopeObserver) {
await (scopeObserver as HintAwareScopeObserver)
.addBreadcrumbWithHint(addedBreadcrumb, resolvedHint);
} else {
await scopeObserver.addBreadcrumb(addedBreadcrumb);
}

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.

Suggested change
if (scopeObserver is HintAwareScopeObserver) {
await (scopeObserver as HintAwareScopeObserver)
.addBreadcrumbWithHint(addedBreadcrumb, resolvedHint);
} else {
await scopeObserver.addBreadcrumb(addedBreadcrumb);
}
if (scopeObserver case final HintAwareScopeObserver observer) {
await observer.addBreadcrumbWithHint(addedBreadcrumb, resolvedHint);
} else {
await scopeObserver.addBreadcrumb(addedBreadcrumb);
}

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.

making it a bit more nicer with newer Dart features

Comment on lines +5618 to +5629
// Hand-authored overload, mirroring addBreadcrumbFromJsonBytes above but
// for the two-byte-array descriptor shape (`([B[B)V`), since regenerating
// this file requires `scripts/generate-jni-bindings.sh`, which needs a
// full Android build toolchain not available in this environment. Re-run
// that script (or otherwise verify against a real jnigen run) before
// merging.
static final _id_addBreadcrumbFromJsonBytes$1 = _class.staticMethodId(
r'addBreadcrumbFromJsonBytes',
r'([B[B)V',
);

static final _addBreadcrumbFromJsonBytes$1 =

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.

let's not hand-roll this. please re-generate this with e.g Flutter 3.44

hkarmoush added a commit to hkarmoush/sentry-dart that referenced this pull request Sep 7, 2026
Use a pattern match instead of an `is` test followed by a redundant
`as` cast when dispatching breadcrumbs to HintAwareScopeObserver.

Review feedback from getsentry#3980.
@hkarmoush
hkarmoush force-pushed the fix-replay-network-detail-leak-local branch from 0321cf3 to 8d16feb Compare September 7, 2026 11:10
Session Replay's opt-in HTTP body/header capture attached its data to
the shared http breadcrumb, which then rode along into every
subsequent Dart-captured error/transaction event and, on Android, into
native crash events too - not just replay recordings (getsentry#3900).

Request/response detail is now delivered via a Hint instead of
breadcrumb.data, so it's never stored on the Scope. On Android it
reaches the replay breadcrumb converter through a new side channel
(ReplayNetworkDetailCache, correlated by an opaque replay_request_id)
that never touches native Scope, closing the crash-event leak without
touching the shipped Android replay rendering. iOS/FFI/Web never see
the detail at all now, closing a zero-benefit leak there too (iOS
doesn't render it in replay).

Note: the Kotlin/Android native changes, including a hand-authored
addition to the auto-generated JNI bindings in
lib/src/native/java/binding.dart, could not be compiled or tested in
this environment (no Android SDK/JDK/cmake available). They mirror
existing generated/native patterns exactly but need verification via
scripts/generate-jni-bindings.sh and a real device/emulator run before
this is considered ready to ship.
…er API

Populate ReplayNetworkDetailCache before the breadcrumb lands on the
native Scope, closing the window where a replay segment built in
between would never see the request/response detail.

ScopeObserver.addBreadcrumb keeps its original one-argument signature
so existing implementors aren't broken; a new opt-in
HintAwareScopeObserver interface carries the Hint for observers (like
NativeScopeObserver) that need it.
Per buenaflor's review on getsentry#3900: stop using a
bespoke JNI side channel (ReplayNetworkDetailCache) to get captured
HTTP request/response detail from Dart into a replay recording, since
sentry-java's DefaultReplayBreadcrumbConverter already supports this
via a SENTRY_REPLAY_NETWORK_DETAILS Hint (the same mechanism
sentry-android's OkHttp integration uses).

addBreadcrumb now carries the network detail directly instead of a
separate captureReplayNetworkDetail call, so there's one atomic native
call instead of two - removing the race Bugbot flagged on the earlier
side-channel approach.
Use a pattern match instead of an `is` test followed by a redundant
`as` cast when dispatching breadcrumbs to HintAwareScopeObserver.

Review feedback from getsentry#3980.
@hkarmoush
hkarmoush force-pushed the fix-replay-network-detail-leak-local branch from 8d16feb to e8e3046 Compare September 17, 2026 22:11

This branch has not been deployed

No deployments
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