Skip to content
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

feat(android): Add custom masking options for session replay #11624

Merged
merged 27 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
649b485
feat(android): Add custom masking options for session replay
romtsn Oct 21, 2024
877f5d4
feat(android): Add custom masking options for session replay
romtsn Oct 21, 2024
5f73956
Add missing imports
romtsn Oct 21, 2024
82d92b7
Rearrange folders
romtsn Oct 21, 2024
0a7bab9
Add required version
romtsn Oct 21, 2024
fca534d
Add masking example images
romtsn Oct 21, 2024
db409c4
Update docs/platforms/android/session-replay/index.mdx
romtsn Oct 22, 2024
9126d2a
Update docs/platforms/android/session-replay/index.mdx
romtsn Oct 22, 2024
2095c52
Update docs/platforms/android/session-replay/index.mdx
romtsn Oct 22, 2024
69797d3
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 22, 2024
da7f3ea
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 22, 2024
830fb64
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 22, 2024
c5e7929
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 22, 2024
5acb078
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 22, 2024
80788c4
Fix formatting
romtsn Oct 22, 2024
887136a
Fix and add masking behavior section
romtsn Oct 22, 2024
ed730bc
Add missing images
romtsn Oct 22, 2024
73c4161
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
e5c7f5e
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
7a304db
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
0a751a9
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
57bca3a
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
10c599e
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
45bf9c8
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
3ddf363
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
0ba684d
Update docs/platforms/android/session-replay/privacy/index.mdx
romtsn Oct 23, 2024
81008d5
Add old way of disabling redaction
romtsn Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions docs/platforms/android/session-replay/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,20 @@ Sampling begins as soon as a session starts. `sessionSampleRate` is evaluated fi

## Privacy

The SDK is recording and aggressively redacting all text and images. We plan to add fine controls for redacting, but in this version, we just allow either on or off. The default is on. Please don’t turn it off if you have sensitive data in your app. Before the Beta is complete, we'll give you the controls you need.
The SDK is recording and aggressively masking all text, images and webviews. Please don’t turn it off if you have sensitive data in your app.
However, if you're working on a mobile app that's free of PII or other types of private data, you can opt out of the default text and image masking settings. To learn more about Session Replay privacy, [read our docs](/platforms/android/session-replay/privacy/).

<Note>

Jetpack Compose views are not yet redacted. This is being [tracked here](https://github.com/getsentry/sentry-java/issues/3577).
If you encounter any other data not being redacted with the default settings, please let us know through a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new?assignees=&labels=Platform%3A+Android%2CType%3A+Bug&projects=&template=bug_report_android.yml).
If you find that any other data isn't being masked with the default settings, please let us know by creating a [GitHub issue](https://github.com/getsentry/sentry-java/issues/new?assignees=&labels=Platform%3A+Android%2CType%3A+Bug&projects=&template=bug_report_android.yml).

</Note>

To disable redaction altogether (not to be used on applications with sensitive data):
To disable masking altogether (not to be used on applications with sensitive data):

```kotlin
options.experimental.sessionReplay.redactAllText = false
options.experimental.sessionReplay.redactAllImages = false
options.experimental.sessionReplay.maskAllText = false
romtsn marked this conversation as resolved.
Show resolved Hide resolved
options.experimental.sessionReplay.maskAllImages = false
```
romtsn marked this conversation as resolved.
Show resolved Hide resolved

## Error Linking
Expand All @@ -120,8 +120,12 @@ Errors that happen on the page while a replay is running will be linked to the r

## FAQ

Q: Why are parts of my replay not masked?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for adding this. We should have this on iOS, React Native and Flutter too!
cc @brustolin @krystofwoldrich @vaind

A: Text fields, input fields, images, video players and webviews are all masked by default. Local assets, such as colors or vector drawables, aren't masked because the likelihood of these assets containing PII is low. If you encounter a view/component that should be masked by default, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues).

Q: Does Session Replay work with Jetpack Compose?
A: Yes, but we're still adding support for redaction for Jetpack Compose views. Redaction will be added during the Beta program and after GA these views will be masked by default.
A: Yes, by default, text, input field, and image composables should be masked. Masking within embedded Android views (`AndroidView`)
in Compose isn't currently supported. If you encounter composables that aren't masked but should be, consider opening a [GitHub issue](https://github.com/getsentry/sentry-java/issues).

Q: What's the lowest version of Android supported?
A: Recording only happens on Android 8 (API level 26) or newer. For devices running an older version, SDK features other than recording work normally.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 154 additions & 0 deletions docs/platforms/android/session-replay/privacy/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
title: Privacy
sidebar_order: 5501
notSupported:
description: "Learn how to mask parts of your app's data in Session Replay."
---

<Alert>

Before publising an App with Session Replay enabled, make sure to test it thoroughly to ensure that no sensitive data is exposed.

If you choose to use custom masking in your Session Replays, you may accidentally expose sensitive customer data. Be sure to double-check what you choose to expose.
romtsn marked this conversation as resolved.
Show resolved Hide resolved

</Alert>

By default, our Session Replay SDK masks all text content, images, webviews and user input. This helps ensure that no sensitive data will be exposed. You can also manually choose which parts of your app's data you want to mask by using the different options listed below.

romtsn marked this conversation as resolved.
Show resolved Hide resolved
To disable the default masking behavior (not to be used on applications with sensitive data):

```kotlin
options.experimental.sessionReplay.maskAllText = false
options.experimental.sessionReplay.maskAllImages = false
```

|Session Replay Unmasked | Session Replay Masked |
|:-----------------------------:|:---------------------------------------: |
|![session replay unmasked](./img/session-replay.jpg) |![session replay masked](./img/session-replay-redacted.jpg) |


_Make sure your Sentry Android SDK version is at least 7.15.0._

## Mask by View Class

You can choose which type of view you want to mask or unmask by using the `addMaskViewClass` or `addUnmaskViewClass` options.

Let's say you have:
- A custom view that you want to mask
- A `TextView` subclass (which normally would be masked) that you don't want to mask

You can set the options like this:

```kotlin
options.experimental.sessionReplay.addMaskViewClass("com.example.MyCustomView")
options.experimental.sessionReplay.addUnmaskViewClass("com.example.MyCustomTextView")
```

<Note>

If you're using a code obfuscation tool (R8/ProGuard), adjust your proguard rules accordingly so your custom view class names don't get minified.

</Note>

### Class Hierarchy

The masking behavior applies to classes and their subclasses. This means if you add a view via `addMaskViewClass` (for example, `TextView`, which is the default behavior), its respective subclasses (`RadioButton`, `CheckBox`, `EditText`, and so on) will also be masked. For example, you can do the following:

```kotlin
options.experimental.sessionReplay.addMaskViewClass("android.widget.TextView") // mask TextView and all its subclasses
options.experimental.sessionReplay.addUnmaskViewClass("android.widget.RadioButton") // but unmask RadioButton and all its subclasses
```

## Mask by View Instance

You can also choose to mask or unmask a specific view instance by using tags like this:

```xml
<View
android:id="@+id/my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="sentry-mask|sentry-unmask"
/>
```

```kotlin
view.tag = "sentry-mask|sentry-unmask"
```

If your view already has a tag assigned, you can set the masking tag by a sentry-specific id:

```xml
<View
android:id="@+id/my_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content">

<tag android:id="@id/sentry_privacy" android:value="mask|unmask"/>
</View>
```

```kotlin
view.setTag(io.sentry.android.replay.R.id.sentry_privacy, "mask|unmask")
```

We also provide convenient extension functions for Kotlin:

```kotlin
view.sentryReplayMask()
// or
view.sentryReplayUnmask()
```

## Jetpack Compose

We only support masking specific composables in Jetpack Compose. Since composables don't have a concept of classes (they are all composable functions), masking by view class is not supported.

In the below example, we want the "Hello" message to be captured in the replay, but not the custom composable. (By default, all text composables are masked.)
romtsn marked this conversation as resolved.
Show resolved Hide resolved

```kotlin
import io.sentry.android.replay.sentryReplayMask
import io.sentry.android.replay.sentryReplayUnmask

Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
MyCustomComposable(
modifier = Modifier.fillMaxWidth().sentryReplayMask()
...
)
Text("Hello", modifier = Modifier.sentryReplayUnmask())
}
```

Currently, we don't support masking anything within embedded Android views (`AndroidView`), but you can still mask the entire view as follows:

```kotlin
import io.sentry.android.replay.sentryReplayMask

AndroidView(
modifier = Modifier.sentryReplayMask(),
factory = { context -> ... }
)
```

## Masking Behavior
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lizokm thanks for reviewing it, I added one more section down below, could you take a look please?

romtsn marked this conversation as resolved.
Show resolved Hide resolved

Here are some general rules we follow when applying masking rules:
romtsn marked this conversation as resolved.
Show resolved Hide resolved

### View Groups

- When a `ViewGroup` is marked as masked, **all its child views inherit this behavior and will be masked too**, even if some of those child views would typically be ignored. This approach prioritizes safety and ensures no sensitive information is exposed unintentionally.

- When a `ViewGroup` is marked as unmasked, **its child views do not automatically inherit its behavior**. Each child view must be explicitly marked as unmasked if you want to capture them in the replay.

romtsn marked this conversation as resolved.
Show resolved Hide resolved
### Masking prioritization
romtsn marked this conversation as resolved.
Show resolved Hide resolved

The following order determines how masking/unmasking rules are applied:

1. Check if a view is marked as `unmasked` via a tag/extension function/modifier
2. Check if a view is marked as `masked` via a tag/extension function/modifier
3. Check if a view's class is marked as unmasked via `addUnmaskViewClass`
4. Check if a view's class is marked as masked via `addMaskViewClass`
romtsn marked this conversation as resolved.
Show resolved Hide resolved
Loading