-
-
Notifications
You must be signed in to change notification settings - Fork 465
feat(screenshot): Add screenshot masking using view hierarchy #5077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛
Internal Changes 🔧Deps
Other
Other
🤖 This preview updates automatically when you update the PR. |
|
Adds masking support to error screenshots by reusing the Session Replay masking logic. This allows sensitive content (text, images) to be masked before attaching screenshots to error events. - Add SentryMaskingOptions base class for shared masking configuration - Add SentryScreenshotOptions for screenshot-specific masking settings - Create MaskRenderer utility for shared mask rendering (used by both replay and screenshots) - Add manifest metadata support for screenshot masking options - Add snapshot tests with Dropbox Differ library for visual regression - Update CLAUDE.md with dependency management guidelines Masking requires the sentry-android-replay module to be present at runtime. Without it, screenshots are captured without masking. Refs: #3286 Co-Authored-By: Claude Opus 4.5 <[email protected]>
sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java
Show resolved
Hide resolved
8afa77c to
17beece
Compare
sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java
Show resolved
Hide resolved
sentry-android-core/src/main/java/io/sentry/android/core/SentryScreenshotOptions.java
Show resolved
Hide resolved
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 27d7cf8 | 397.90 ms | 498.65 ms | 100.75 ms |
| e59e22a | 329.74 ms | 383.31 ms | 53.57 ms |
| ae7fed0 | 293.84 ms | 380.22 ms | 86.38 ms |
| b3d8889 | 371.84 ms | 447.49 ms | 75.65 ms |
| 5f14e5d | 325.76 ms | 368.32 ms | 42.56 ms |
| fcec2f2 | 311.35 ms | 384.94 ms | 73.59 ms |
| d15471f | 307.28 ms | 381.85 ms | 74.57 ms |
| fc5ccaf | 279.11 ms | 353.34 ms | 74.23 ms |
| 8687935 | 294.00 ms | 304.02 ms | 10.02 ms |
| ee747ae | 400.46 ms | 423.61 ms | 23.15 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 27d7cf8 | 1.58 MiB | 2.12 MiB | 549.42 KiB |
| e59e22a | 1.58 MiB | 2.20 MiB | 635.34 KiB |
| ae7fed0 | 1.58 MiB | 2.12 MiB | 551.77 KiB |
| b3d8889 | 1.58 MiB | 2.10 MiB | 535.07 KiB |
| 5f14e5d | 1.58 MiB | 2.19 MiB | 620.00 KiB |
| fcec2f2 | 1.58 MiB | 2.12 MiB | 551.51 KiB |
| d15471f | 1.58 MiB | 2.13 MiB | 559.54 KiB |
| fc5ccaf | 1.58 MiB | 2.13 MiB | 557.54 KiB |
| 8687935 | 1.58 MiB | 2.19 MiB | 619.17 KiB |
| ee747ae | 1.58 MiB | 2.10 MiB | 530.95 KiB |
sentry-android-core/src/main/java/io/sentry/android/core/ScreenshotEventProcessor.java
Show resolved
Hide resolved
…s configured The isMaskingEnabled() method was logging a warning before checking if masking was actually configured. This caused users who never set up screenshot masking to see spurious warnings on every event. Co-Authored-By: Claude <[email protected]>
…false) is called setMaskAllImages(true) was adding WebView, VideoView, and ExoPlayer classes to maskViewClasses, but setMaskAllImages(false) only removed ImageView. This caused asymmetric toggle behavior where disabling image masking didn't restore the original state. Co-Authored-By: Claude <[email protected]>
…mory leak When an exception occurred in applyMasking after creating a mutable copy of the bitmap, the catch block returned the original screenshot without recycling the copy. This caused bitmap memory to accumulate until GC runs, potentially causing OOM issues on frequent errors. Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| if (createdCopy && !mutableBitmap.isRecycled()) { | ||
| mutableBitmap.recycle(); | ||
| } | ||
| return screenshot; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unmasked screenshot sent when masking operation fails
Low Severity
When masking is explicitly enabled but fails (due to an exception during renderMasks or if bitmap.copy() returns null), the original unmasked screenshot is returned and attached to the error event. Users who configured masking expect sensitive content (text, images) to be protected, but this failure mode silently sends unmasked screenshots to Sentry. The safer behavior would be to skip attaching the screenshot entirely if masking was requested but couldn't be applied.
Additional Locations (1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@markushi wdyt? makes sense?
| public void setMaskAllImages(final boolean maskAllImages) { | ||
| super.setMaskAllImages(maskAllImages); | ||
| if (maskAllImages) { | ||
| addSensitiveViewClasses(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this is questionable if you enable masking images, we will also mask other media. But I didn't want to introduce new flag, so made it dependant on this one. We can document this, or introduce a new flag. Thoughts @markushi ?
I think adding those classes by default is probably a no-go for screenshots, since it's a single frame screenshot, not a sequence like in replay, where potentially a lot more could be leaked


📜 Description
Adds masking support to error screenshots by reusing the Session Replay masking logic. This allows sensitive content (text, images) to be masked before attaching screenshots to error events.
Masking requires the
sentry-android-replaymodule to be present at runtime. Without it, screenshots are captured without masking.some example events:
Also verified that pixelCopy strategy still works fine after the change, replay here:
https://sentry-sdks.sentry.io/explore/replays/cb4a531e5851484cb547c93e31a9c9f3/
💡 Motivation and Context
Closes #3286
💚 How did you test it?
Manually + automated
📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Docs and maybe wizard