PostHog / PostHog/posthog-flutter
Session replay: mask part of a text node (e.g. only the digits) instead of the whole node
Nobody has claimed this yet.
- 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 = falseand wrap every sensitive value inPostHogMaskWidget: every amount, account number, card number and reference in the app has to be found and wrapped, in every screen, forever. A single missedTextleaks a balance. And it can't be done at all for strings that mix label and value in one node ('Account $accountNumber',Text.richamounts 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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