Skip to content

Native SDK session replay may capture unmasked screenshots bypassing Flutter's PostHogMaskWidge #356

@KadaEndre

Description

@KadaEndre

Version

5.23.0

Steps to Reproduce

When using PostHogMaskWidget to selectively mask sensitive fields (e.g., password inputs) with maskAllTexts = false, the masked content may still appear unmasked in PostHog session recordings, even if I wrap these fields in PosthogMaskWidget

  • Flutter: 3.41.6

Expected Result

If I wrap something in PosthogMaskWidget, I eexpect it to be masked on sessionReplay, currently this doean't happen. Example:
PostHogMaskWidget( child: FormBuilderTextField( name: widget.name, initialValue: widget.initialValue, autofocus: widget.autofocus, obscureText: _isObscured, textInputAction: widget.textInputAction, keyboardType: widget.keyboardType, autofillHints: widget.autofillHints, maxLines: widget.maxLines, textCapitalization: widget.isSecureField ? TextCapitalization.none : widget.textCapitalization, decoration: InputDecoration( hintText: widget.hintText, errorText: widget.errorText, errorStyle: const TextStyle(fontSize: 0), suffix: widget.isSecureField ? GestureDetector( onTap: _toggleVisibility, child: Text( _isObscured ? LocaleKeys.show.tr() : LocaleKeys.hide.tr(), ), ) : null, ), onChanged: widget.onChanged, onSubmitted: widget.onSubmitted, validator: _validateField, ), ),

Actual Result

The Flutter plugin configures the native PostHog SDK (both iOS and Android) with sessionReplay = true, which enables the native SDK's own session replay mechanism alongside Flutter's screenshot-based
replay.

Android (PosthogFlutterPlugin.kt):
posthogConfig.getIfNotNull("sessionReplay") {
sessionReplay = it // enables native SDK session replay
}

iOS (PosthogFlutterPlugin.swift):
if let sessionReplay = posthogConfig["sessionReplay"] as? Bool {
config.sessionReplay = sessionReplay // enables native SDK session replay
}

This results in two parallel recording streams:

  1. Flutter-side (PostHogWidget / ScreenshotCapturer): Captures screenshots from the RepaintBoundary, applies masks from PostHogMaskWidget and TextField(obscureText: true), then sends masked image bytes
    to native via sendFullSnapshot
  2. Native-side (posthog-ios / posthog-android): Captures the raw FlutterView / FlutterSurfaceView content directly — without any masking

Both produce $snapshot events sent to PostHog, meaning unmasked frames from the native SDK can appear in recordings.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Session ReplaybugSomething isn't workingquestionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions