Repository navigation
fix(replay): mask FLAG_SECURE windows and discard frames with unplaceable masked views - #910
Open
turnipdabeets wants to merge 8 commits into
Open
turnipdabeets wants to merge 8 commits into
turnipdabeets wants to merge 8 commits into
Conversation
…d nested masked views
This was referenced Oct 29, 2026
Also splits the legacy discard log so a transient-state discard says why, documents the FLAG_SECURE behaviour on the screenshot option, and points the duplicated mask rules at each other.
Contributor
There was a problem hiding this comment.
PostHog Review · Standard · reviewed e23f2a8: 1 Should fix.
See this review and its findings in PostHog Review. Comment @posthog review --deep for a Deep review. A Deep review takes longer and looks deeper than a Standard review.
| } | ||
|
|
||
| // Main thread only, like the window callbacks that add and remove these interceptors. | ||
| private val touchInterceptors = WeakHashMap<Window, TouchEventInterceptor>() |
Contributor
There was a problem hiding this comment.
P2 · Synchronize the interceptor cache across lifecycle threads
When background PostHog.close() overlaps window callbacks, uninstall() calls clearViewListeners() off-main and races getOrPut/remove on this unsynchronized WeakHashMap. Lost cache entries prevent teardown from finding attached interceptors. Follow the mutable-collection rule in AGENTS.md:28: confine accesses to main or guard them with one lock.
This was referenced Oct 29, 2026
4 of 6 tasks
…through the window
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
💡 Motivation and Context
Privacy gaps in session replay's screenshot mode (opt-in on 3.x):
FLAG_SECUREwindows were recorded. Apps setFLAG_SECUREto keep a screen out of screenshots, but replay copied it anyway. These windows are now recorded as one fully masked (black) frame, and their pixels are never recorded or uploaded. The flag is checked when capture starts and again after the copy, so a window that turns secure mid-copy is masked too.FLAG_SECUREwindows were recorded. Touch coordinates (e.g. on a PIN pad) were still uploaded over the black frame. They're now dropped in screenshot mode. Flutter's Android touches come from this native interceptor, so posthog-flutter gets this through the native SDK; its frames are handled in fix(android): mask session replay frames on FLAG_SECURE windows posthog-flutter#685.Before this, the SDK's autocapture taps and interaction snapshots already skipped
FLAG_SECUREwindows, but replay did not. Wireframe mode (the 3.x default) still records secure windows after this PR; that's fixed in the stacked #912.Intentional behaviour changes (patch):
FLAG_SECUREnow show as a solid black frame in recordings, without taps.The transient-state fix is the 3.x port of 37df5a2 from #908, and this change reaches the 4.0 branches when
mainis merged into them.Follow-up: #913 replaces the duplicated mask rules (
findMaskableWidgets/hasMaskableContent) with one shared classifier.Deferred (not in this PR):
💚 How did you test it?
a secure window is recorded fully masked without copying its pixels(all 16 scale/colour/verification settings): captures a white frame, setsFLAG_SECURE, and checks the next frame is all black with no new PixelCopy, so stale pixels in the reused bitmap can't leak.a window that turns secure during the copy is recorded fully masked(16 settings).a touch on a secure window is not recorded in screenshot mode: touches are recorded before the flag is set and dropped after, and the app still receives them.legacy capture discards a frame with a masked view in transient state: plain text inside a nested layout.legacy mask walk is not poisoned by an animating view with nothing to mask(control)../gradlew spotlessCheck apiCheck :posthog-android:testDebugUnitTestpasses.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
FLAG_SECUREwindow rather than skip the frame, so recordings show that something private was there instead of freezing on the previous screen. Dropping taps and re-checking the flag after the copy follow from that decision.🤖 Generated with Claude Code