PostHog / PostHog/posthog-flutter

Session replay: mask part of a text node (e.g. only the digits) instead of the whole node

Open
#580 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement Session Replay
Dominant language
Dart
Stars
97
Forks
85
Avg merge
1d 8h
Merged PRs (30d)
31

Description

Problem Statement

Session replay text masking in Flutter is all-or-nothing per text node. maskAllTexts masks every Text/RichText/input, and PostHogMaskWidget / PostHogUnmaskWidget move whole subtrees between masked and clear. There is no way to say "mask the digits in this string and leave the words".

For a fintech app that is the case that matters. A balance card renders Total balance ₦2,450,000.00, a transaction row renders Ikeja Electric · -₦12,500.00, a note field holds Rent for flat 402. What we want in the replay is the label readable and the number hidden. Today the choices are:

  • maskAllTexts = true (the default): the whole screen is black boxes. You can see layout and taps, but not which screen the user was on, which button they pressed, or what a row said. Product and support get almost nothing out of the replay.
  • maskAllTexts = false and wrap every sensitive value in PostHogMaskWidget: every amount, account number, card number and reference in the app has to be found and wrapped, in every screen, forever. A single missed Text leaks a balance. And it can't be done at all for strings that mix label and value in one node ('Account $accountNumber', Text.rich amounts with a currency span, interpolated copy from a backend).

The same applies to text inputs: a non-sensitive TextField holding Room 402 is either fully masked or fully visible.

Related but not the same: #568 / #569 (defaults and unmask gaps), #574 (keypads / sensitive inputs). Those are about which nodes get masked. This is about masking part of a node.

Solution Brainstorm

Let the app decide per text node which character ranges get masked, and have the SDK turn those ranges into rects using the glyph boxes the render objects already expose (RenderParagraph.getBoxesForSelection, RenderEditable.getBoxesForSelection). The masking pipeline already produces a list of rects per element, so partial rects slot into ImageMaskPainter unchanged.

Sketch of an API on PostHogSessionReplayConfig:

config.sessionReplayConfig.textMaskPolicy = PostHogTextMaskPolicies.digits();

where a policy is PostHogTextMask Function(String text) returning one of all(), none(), only(ranges), except(ranges), with presets for the common cases (digits(), redact(RegExp), reveal(RegExp)). Precedence stays as it is today: PostHogUnmaskWidget and PostHogMaskWidget win over the policy, sensitive inputs (obscured / password / OTP / email / phone hints) stay fully masked, and the policy only decides for the remaining Text, RichText and non-sensitive inputs. If a policy throws or returns a range outside the text, mask the whole node rather than leak.

I have this working on a branch with tests and an example screen and will open a PR.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Begin at PostHogSessionReplayConfig.textMaskPolicy and the existing masking pipeline, including ImageMaskPainter, then inspect RenderParagraph.getBoxesForSelection and RenderEditable.getBoxesForSelection. Done means policies can mask selected character ranges in text and non-sensitive inputs while preserving current widget precedence and safely masking invalid or failing policies.

Written by the indexing model from the issue text.

Assessment

Tech stack
dart, flutter
Domain
mobile, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.