Repository navigation
fix(android): mask session replay frames on FLAG_SECURE windows - #685
Conversation
There was a problem hiding this comment.
PostHog Review · Standard · reviewed 1d834ac: 1 Must 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.
| private fun getSessionReplayState(): Map<String, Any?> = | ||
| mapOf( | ||
| private fun getSessionReplayState(): Map<String, Any?> { | ||
| captureStartedOnSecureWindow = isWindowSecure() |
There was a problem hiding this comment.
P1 · Track secure state across the actual screenshot capture
When FLAG_SECURE is set after this read but before renderObject.toImage(), then cleared during image processing, both checks return false and takeFrameMasked() sends the secure screenshot unmasked. captureScreenshot() awaits endOfFrame after this read, leaving the capture outside the sampled state; track secure transitions throughout capture or discard affected frames.
posthog-flutter Compliance ReportDate: 2026-10-29 19:27:58 UTC ✅ All Tests Passed!45/45 tests passed Capture Tests✅ 29/29 tests passed View Details
Feature_Flags Tests✅ 16/16 tests passed View Details
|
dustinbyrne
left a comment
There was a problem hiding this comment.
Reviewed the Android masking path and frame-state propagation. No actionable findings.
AI-assisted review.
💡 Motivation and Context
Apps set
FLAG_SECUREon screens that must never be screenshotted (login, payment, PIN). Flutter Android session replay ignored it and recorded those screens anyway. Flutter renders replay frames itself, so the fix in posthog-android doesn't cover these frames.Now, while the activity window has
FLAG_SECURE, the frame is still sent but painted fully black at the same size. That's the same colour Flutter replay uses for masks, and it's the same decision as the posthog-android fix: mask rather than skip, so the replay shows that something private was on screen.getSessionReplayStatereportssecure, andsendFullSnapshotpasses it back assecureAtCapture. So a frame captured on a secure screen stays masked even if the flag clears (the screen is popped) before it goes out, and the plugin keeps no state between calls.PixelCopy) are composited into the same frame, so they're covered too.💚 How did you test it?
getSessionReplayStatereports the window'ssecureflag;secureis true is sent withsecureAtCapture: true. It fails if the argument is dropped../gradlew :posthog_flutter:testDebugUnitTest, ktlint,dart format,dart analyze,flutter test: all pass.📝 Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
mainfix.secureAtCapturevalue, so there's no state to carry over.inJustDecodeBounds) instead of decoding the secure pixels.FLAG_SECUREclears while the screen looks exactly the same, the replay stays black until the screen changes.🤖 Generated with Claude Code