diff --git a/CHANGELOG.md b/CHANGELOG.md index 1185d47aaf..09aecbbf9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ - Decide whether foregrounding the app starts a new session on a monotonic clock instead of the wall clock, so that a device time change no longer starts a session that should have been resumed, or resumes one that should have ended. This affects session counts ([#6096](https://github.com/getsentry/sentry-java/pull/6096)) - `SentryUserFeedbackForm` now extends `android.app.Dialog` instead of `android.app.AlertDialog` ([#6338](https://github.com/getsentry/sentry-java/pull/6338)) - The form never used `AlertDialog` features. Code that calls `AlertDialog`-only methods on it, such as `setButton` or `setMessage`, no longer compiles. +- Screenshots attached to errors and user feedback now mask all text and images by default. Turn it off with `options.screenshot.setMaskAllText(false)` and `setMaskAllImages(false)`, or the `io.sentry.screenshot.mask-all-text` and `io.sentry.screenshot.mask-all-images` manifest options. Masking needs `sentry-android-replay`, so apps that depend on `sentry-android-core` alone get no screenshot until they add it or turn masking off ([#6437](https://github.com/getsentry/sentry-java/pull/6437)) ## 8.62.0 diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java index b6e802bbd5..003ae6cd1b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java @@ -806,13 +806,12 @@ static void applyMetadata( options.setSpotlightConnectionUrl(spotlightUrl); } - // Screenshot masking options (default to false for backwards compatibility) options .getScreenshot() - .setMaskAllText(readBool(metadata, logger, SCREENSHOT_MASK_ALL_TEXT, false)); + .setMaskAllText(readBool(metadata, logger, SCREENSHOT_MASK_ALL_TEXT, true)); options .getScreenshot() - .setMaskAllImages(readBool(metadata, logger, SCREENSHOT_MASK_ALL_IMAGES, false)); + .setMaskAllImages(readBool(metadata, logger, SCREENSHOT_MASK_ALL_IMAGES, true)); if (options.getAnrProfilingSampleRate() == null) { final double anrProfilingSampleRate = diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/SentryScreenshotOptions.java b/sentry-android-core/src/main/java/io/sentry/android/core/SentryScreenshotOptions.java index 6e1cceb760..b028f7ffcc 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/SentryScreenshotOptions.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/SentryScreenshotOptions.java @@ -6,8 +6,8 @@ * Screenshot masking options for error screenshots. Extends the base {@link SentryMaskingOptions} * with screenshot-specific defaults. * - *

By default, masking is disabled for screenshots. Enable masking by calling {@link - * #setMaskAllText(boolean)} and/or {@link #setMaskAllImages(boolean)}. + *

By default, all text and images are masked. Disable masking by calling {@link + * #setMaskAllText(boolean)} and/or {@link #setMaskAllImages(boolean)} with {@code false}. * *

Note: Screenshot masking requires the {@code sentry-android-replay} module to be present at * runtime. If masking is enabled but the replay module is not available, no screenshot is captured @@ -16,8 +16,8 @@ public final class SentryScreenshotOptions extends SentryMaskingOptions { public SentryScreenshotOptions() { - // Default to NO masking until next major version. - // maskViewClasses starts empty, so nothing is masked by default. + setMaskAllText(true); + setMaskAllImages(true); } @Override diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt index 6684f8c617..8959708975 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ManifestMetadataReaderTest.kt @@ -2869,31 +2869,31 @@ class ManifestMetadataReaderTest { @Test fun `applyMetadata reads screenshot mask-all-text to options`() { // Arrange - val bundle = bundleOf(ManifestMetadataReader.SCREENSHOT_MASK_ALL_TEXT to true) + val bundle = bundleOf(ManifestMetadataReader.SCREENSHOT_MASK_ALL_TEXT to false) val context = fixture.getContext(metaData = bundle) // Act ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) // Assert - assertTrue(fixture.options.screenshot.maskViewClasses.contains("android.widget.TextView")) + assertFalse(fixture.options.screenshot.maskViewClasses.contains("android.widget.TextView")) } @Test fun `applyMetadata reads screenshot mask-all-images to options`() { // Arrange - val bundle = bundleOf(ManifestMetadataReader.SCREENSHOT_MASK_ALL_IMAGES to true) + val bundle = bundleOf(ManifestMetadataReader.SCREENSHOT_MASK_ALL_IMAGES to false) val context = fixture.getContext(metaData = bundle) // Act ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) // Assert - assertTrue(fixture.options.screenshot.maskViewClasses.contains("android.widget.ImageView")) + assertFalse(fixture.options.screenshot.maskViewClasses.contains("android.widget.ImageView")) } @Test - fun `applyMetadata without specifying screenshot mask-all-text, stays false`() { + fun `applyMetadata without specifying screenshot mask-all-text, stays true`() { // Arrange val context = fixture.getContext() @@ -2901,11 +2901,11 @@ class ManifestMetadataReaderTest { ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) // Assert - assertFalse(fixture.options.screenshot.maskViewClasses.contains("android.widget.TextView")) + assertTrue(fixture.options.screenshot.maskViewClasses.contains("android.widget.TextView")) } @Test - fun `applyMetadata without specifying screenshot mask-all-images, stays false`() { + fun `applyMetadata without specifying screenshot mask-all-images, stays true`() { // Arrange val context = fixture.getContext() @@ -2913,7 +2913,7 @@ class ManifestMetadataReaderTest { ManifestMetadataReader.applyMetadata(context, fixture.options, fixture.buildInfoProvider) // Assert - assertFalse(fixture.options.screenshot.maskViewClasses.contains("android.widget.ImageView")) + assertTrue(fixture.options.screenshot.maskViewClasses.contains("android.widget.ImageView")) } @Test diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/ScreenshotEventProcessorTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/ScreenshotEventProcessorTest.kt index 0abb232b80..1dfb277175 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/ScreenshotEventProcessorTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/ScreenshotEventProcessorTest.kt @@ -73,7 +73,13 @@ class ScreenshotEventProcessorTest { private class Fixture { lateinit var activity: Activity val threadChecker = mock() - val options = SentryAndroidOptions().apply { dsn = "https://key@sentry.io/proj" } + val options = + SentryAndroidOptions().apply { + dsn = "https://key@sentry.io/proj" + // Tests opt in to masking, so they also run without sentry-android-replay. + screenshot.setMaskAllText(false) + screenshot.setMaskAllImages(false) + } val mainProcessor = MainEventProcessor(options) init { diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/SentryScreenshotOptionsTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/SentryScreenshotOptionsTest.kt index ad6d29b561..ed7562d532 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/SentryScreenshotOptionsTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/SentryScreenshotOptionsTest.kt @@ -9,9 +9,12 @@ import kotlin.test.assertTrue class SentryScreenshotOptionsTest { @Test - fun `maskViewClasses is empty by default`() { + fun `masks all text and images by default`() { val options = SentryScreenshotOptions() - assertTrue(options.maskViewClasses.isEmpty()) + assertTrue(options.maskViewClasses.contains(SentryMaskingOptions.TEXT_VIEW_CLASS_NAME)) + assertTrue(options.maskViewClasses.contains(SentryMaskingOptions.IMAGE_VIEW_CLASS_NAME)) + assertTrue(options.maskViewClasses.contains(SentryMaskingOptions.WEB_VIEW_CLASS_NAME)) + assertEquals(8, options.maskViewClasses.size) } @Test @@ -23,6 +26,8 @@ class SentryScreenshotOptionsTest { @Test fun `setMaskAllText true only adds TextView`() { val options = SentryScreenshotOptions() + options.setMaskAllText(false) + options.setMaskAllImages(false) options.setMaskAllText(true) assertTrue(options.maskViewClasses.contains(SentryMaskingOptions.TEXT_VIEW_CLASS_NAME)) @@ -35,6 +40,7 @@ class SentryScreenshotOptionsTest { @Test fun `setMaskAllImages true adds ImageView and sensitive view classes`() { val options = SentryScreenshotOptions() + options.setMaskAllImages(false) options.setMaskAllImages(true) assertTrue(options.maskViewClasses.contains(SentryMaskingOptions.IMAGE_VIEW_CLASS_NAME)) @@ -68,7 +74,7 @@ class SentryScreenshotOptionsTest { options.setMaskAllImages(true) // CopyOnWriteArraySet should prevent duplicates - assertEquals(7, options.maskViewClasses.size) + assertEquals(8, options.maskViewClasses.size) } @Test @@ -108,6 +114,6 @@ class SentryScreenshotOptionsTest { options.setMaskAllImages(false) - assertTrue(options.maskViewClasses.isEmpty()) + assertEquals(setOf(SentryMaskingOptions.TEXT_VIEW_CLASS_NAME), options.maskViewClasses) } } diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ScreenshotUtilsMaskedScreenshotTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ScreenshotUtilsMaskedScreenshotTest.kt index 9bc6f9f85b..9f6e220c2d 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ScreenshotUtilsMaskedScreenshotTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/internal/util/ScreenshotUtilsMaskedScreenshotTest.kt @@ -36,6 +36,9 @@ class ScreenshotUtilsMaskedScreenshotTest { SentryAndroidOptions().apply { dsn = "https://key@sentry.io/proj" threadChecker = this@Fixture.threadChecker + // Tests opt in to masking, so they also run without sentry-android-replay. + screenshot.setMaskAllText(false) + screenshot.setMaskAllImages(false) } val buildInfoProvider = BuildInfoProvider(options.logger)