Skip to content

fix(session-replay-privacy): require complete mask-subtree discovery - #19

Draft
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/fix-replay-mask-subtree-completeness
Draft

fix(session-replay-privacy): require complete mask-subtree discovery#19
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/fix-replay-mask-subtree-completeness

Conversation

@posthog

@posthog posthog Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Why

posthog-flutter#500 ("mask every element that matched a masking rule") fixed a shipped privacy leak in the mobile screenshot-masking pipeline (both iOS and Android — Flutter's Dart-side ImageMaskPainter paints masks itself, so both platforms shared the bug). ElementData.extractRects(), which collects the rectangles painted black over each screenshot, was not fully recursive: it walked a root's direct children, and grandchildren only when a child itself had more than one child — dropping the child in that branch instead of also visiting it.

Concretely: a PostHogMaskWidget wrapping several matched children lost its own mask rect. Anything inside the wrapper that didn't independently match a rule (an image with maskAllImages off, spacing, a decoration) recorded unmasked, despite the developer explicitly wrapping that subtree to be masked. Verified live on both iOS and Android in the PR: before the fix, a wrapper around Text / unmasked-widget / Text masked only the two texts, leaving the widget between them fully visible in the recording.

openspec/specs/session-replay-privacy/spec.md behavior item 6 describes how masks get painted into screenshots but never states completeness — that every matching element/subtree must be included, not just the first match a traversal happens to reach. This is a real spec gap the shipped bug fell into, not a documented-then-violated rule.

What this PR does

  • Adds a completeness clause to Behavior item 4 (mask discovery must traverse the full matched subtree; a masked wrapper's own rect must be included alongside its descendants').
  • Adds one new scenario to the Canonical session-replay-privacy behavior requirement (via openspec/changes/archive/2026-07-31-fix-replay-mask-subtree-completeness/, following this repo's propose→archive convention): a masked subtree with a non-matching child in the middle must still be fully masked.
  • openspec validate --specs --strict passes.

Uncertain / flagged for reviewer attention

  • Only posthog-flutter was checked against this defect class. Native iOS/Android/web mask-discovery implementations weren't independently re-audited for the same traversal bug — worth a follow-up pass (noted in tasks.md §4.1).
  • Two adjacent bugs the Flutter PR explicitly found and deliberately left unfixed (stale masking-config parsers after re-setup(); maskAllImages masking text when maskAllTexts is off) are intentionally not addressed here — flagged for a separate change if confirmed cross-platform (tasks.md §4.2).

Created with PostHog Code

Flutter's shipped mask-rect tree walk silently dropped matched nodes; the spec described how masks are painted but never stated that discovery must be complete.

Generated-By: PostHog Code
Task-Id: 7d7cb7ed-0d67-4718-ada0-2b2cd585f3a6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants