Skip to content

fix: mask maskView views and secure fields in bug report screenshots and replays - #60

Open
boehlerlukas wants to merge 1 commit into
mainfrom
fix/hc-audit-findings
Open

boehlerlukas wants to merge 1 commit into
mainfrom
fix/hc-audit-findings

Conversation

@boehlerlukas

Copy link
Copy Markdown
Contributor

What this fixes

Help-center audit finding 26 (high, privacy): Gleap.maskView did not mask bug report screenshots or app replays.

maskView (and the automatic masks for secure, one-time-code and card text fields) only applied to capture requests: GleapCaptureManager → GleapCaptureRenderer. Three other paths take their pictures through GleapScreenCaptureHelper.captureScreen, which drew the key window with no masks:

  • the bug report screenshot taken when the widget opens (GleapScreenshotManager.m:52)
  • the app replay frames, one every 5 s (GleapReplayHelper.m:112)
  • the screenshot attached to sendSilentCrashReport (GleapCore.m:676)

So a view the app had masked, and even a password field, showed in plain view in every bug report screenshot and replay.

Fix: captureScreen now blacks out the same targets after it draws the window. Both drawing paths are covered: the primary drawViewHierarchyInRect / Flutter path and the renderInContext fallback. It goes through a new GleapCaptureRenderer fillMasksInWindow:maskedViews:context:, which reuses the renderer's own target walk (maskTargetsInWindow:, with secure, OTP and card fields), its presentation-layer rects (so views mid-animation are covered) and fillMasksOfViews:. If the masks cannot be placed, the whole window is blacked out, as capture frames already do. The maskView: header doc now says where it applies.

Verified

  • swift build can't build this package on macOS (UIKit), on main too. I used xcodebuild test -scheme Gleap -destination 'platform=iOS Simulator,name=iPhone 17 Pro' (Xcode 27.1) instead: TEST SUCCEEDED, 132 tests, 0 failures (131 before plus the new one).
  • New test GleapCaptureTests.testBugReportScreenshotsAndReplayFramesAreMasked. It renders a window through GleapScreenCaptureHelper performPrimaryCaptureForWindow:withSize: and checks pixels: a Gleap.maskView view and a secure text field come out black, while an unmasked view and the background stay visible. I checked that it fails on main (both mask assertions) and passes with the fix. This is privacy (sanitising) code, so it gets a test per the workspace rule.
  • Not checked on a device or in a host app.

Notes

  • No version bump and no CHANGELOG entry (PR only). Suggested release note: "Gleap.maskView and the automatic secure, one-time-code and card field masks now also apply to bug report screenshots, app replays and silent crash report screenshots."
  • The Android counterpart is in the matching Android-SDK PR.

Not fixed (needs decision / out of scope)

  • Flutter, React Native and Capacitor have no maskView API. Their own widgets can't be found as native views anyway; they would need a wrapper API (mask a rect or a widget key). That is a product decision.
  • Finding 25 (an empty activation list in Flutter/RN falls back to the dashboard) is fixed in the Flutter and RN bridges by passing NONE, which iOS already treats as "none". No iOS change is needed.

🤖 Generated with Claude Code

…and replays

Gleap.maskView (and the automatic secure / one-time-code / card field masks)
only applied to the screenshots and recordings of capture requests. Bug report
screenshots, silent crash report screenshots and app replay frames all come from
GleapScreenCaptureHelper.captureScreen, which drew the key window unmasked.

captureScreen now blacks out the same targets through the capture renderer
(GleapCaptureRenderer fillMasksInWindow:), falling back to a black window when
the masks cannot be placed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant