Skip to content

Commit

Permalink
feat can unmask attributes, masks input type=button/submit (only with…
Browse files Browse the repository at this point in the history
… maskAllText enabled)
  • Loading branch information
billyvg committed Jul 28, 2023
1 parent a38e75c commit 2d9a378
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<div>This should be masked by default</div>
<div data-sentry-unmask>This should be unmasked due to data attribute</div>
<input placeholder="Placeholder should be masked" />
<input data-sentry-unmask placeholder="Placeholder can be unmasked" />
<div title="Title should be masked">Title should be masked</div>
<svg style="width:200px;height:200px" viewBox="0 0 80 80"><path d=""/><area /><rect /></svg>
<svg style="width:200px;height:200px" viewBox="0 0 80 80" data-sentry-unblock><path d=""/><area /><rect /></svg>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@
"textContent": "\n ",
"id": 20
},
{
"type": 2,
"tagName": "input",
"attributes": {
"data-sentry-unmask": "",
"placeholder": "Placeholder can be unmasked"
},
"childNodes": [],
"id": 21
},
{
"type": 3,
"textContent": "\n ",
"id": 22
},
{
"type": 2,
"tagName": "div",
Expand All @@ -141,15 +156,15 @@
{
"type": 3,
"textContent": "***** ****** ** ******",
"id": 22
"id": 24
}
],
"id": 21
"id": 23
},
{
"type": 3,
"textContent": "\n ",
"id": 23
"id": 25
},
{
"type": 2,
Expand All @@ -160,12 +175,12 @@
},
"childNodes": [],
"isSVG": true,
"id": 24
"id": 26
},
{
"type": 3,
"textContent": "\n ",
"id": 25
"id": 27
},
{
"type": 2,
Expand All @@ -184,32 +199,32 @@
},
"childNodes": [],
"isSVG": true,
"id": 27
"id": 29
},
{
"type": 2,
"tagName": "area",
"attributes": {},
"childNodes": [],
"isSVG": true,
"id": 28
"id": 30
},
{
"type": 2,
"tagName": "rect",
"attributes": {},
"childNodes": [],
"isSVG": true,
"id": 29
"id": 31
}
],
"isSVG": true,
"id": 26
"id": 28
},
{
"type": 3,
"textContent": "\n ",
"id": 30
"id": 32
},
{
"type": 2,
Expand All @@ -219,12 +234,12 @@
"rr_height": "[100-150]px"
},
"childNodes": [],
"id": 31
"id": 33
},
{
"type": 3,
"textContent": "\n ",
"id": 32
"id": 34
},
{
"type": 2,
Expand All @@ -235,12 +250,12 @@
"src": "file:///none.png"
},
"childNodes": [],
"id": 33
"id": 35
},
{
"type": 3,
"textContent": "\n ",
"id": 34
"id": 36
},
{
"type": 2,
Expand All @@ -250,17 +265,17 @@
"rr_height": "[0-50]px"
},
"childNodes": [],
"id": 35
"id": 37
},
{
"type": 3,
"textContent": "\n ",
"id": 36
"id": 38
},
{
"type": 3,
"textContent": "\n\n",
"id": 37
"id": 39
}
],
"id": 8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@
<textarea id="textarea"></textarea>
<textarea id="textarea-masked" data-sentry-mask></textarea>
<textarea id="textarea-ignore" data-sentry-ignore></textarea>

<input type="submit" value="Submit form" />
<input data-sentry-unmask type="submit" value="Unmasked button" />
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { IncrementalSource } from '@sentry-internal/rrweb';

import { sentryTest } from '../../../utils/fixtures';
import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
import {
import { getFullRecordingSnapshots ,
getIncrementalRecordingSnapshots,
shouldSkipReplayTest,
waitForReplayRequest,
} from '../../../utils/replayHelpers';


function isInputMutation(
snap: IncrementalRecordingSnapshot,
): snap is IncrementalRecordingSnapshot & { data: inputData } {
Expand Down Expand Up @@ -137,7 +138,10 @@ sentryTest(
const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await reqPromise0;
const fullSnapshot = getFullRecordingSnapshots(await reqPromise0)
const stringifiedSnapshot = JSON.stringify(fullSnapshot);
expect(stringifiedSnapshot.includes('Submit form')).toBe(false);
expect(stringifiedSnapshot.includes('Unmasked button')).toBe(true);

const text = 'test';
await page.locator('#textarea').fill(text);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@

<textarea id="textarea"></textarea>
<textarea id="textarea-unmasked" data-sentry-unmask></textarea>

<input type="submit" value="Submit form" />
<input data-sentry-unmask type="submit" value="Unmasked button" />
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { inputData } from '@sentry-internal/rrweb';
import { IncrementalSource } from '@sentry-internal/rrweb';

import { sentryTest } from '../../../utils/fixtures';
import type { IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
import { getFullRecordingSnapshots, IncrementalRecordingSnapshot } from '../../../utils/replayHelpers';
import {
getIncrementalRecordingSnapshots,
shouldSkipReplayTest,
Expand Down Expand Up @@ -56,7 +56,10 @@ sentryTest(
const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await reqPromise0;
const fullSnapshot = getFullRecordingSnapshots(await reqPromise0)
const stringifiedSnapshot = JSON.stringify(fullSnapshot);
expect(stringifiedSnapshot.includes('Submit form')).toBe(false);
expect(stringifiedSnapshot.includes('Unmasked button')).toBe(true);

const text = 'test';

Expand Down
3 changes: 3 additions & 0 deletions packages/replay/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ export const SLOW_CLICK_SCROLL_TIMEOUT = 300;

/** When encountering a total segment size exceeding this size, stop the replay (as we cannot properly ingest it). */
export const REPLAY_MAX_EVENT_BUFFER_SIZE = 20_000_000; // ~20MB

/** Default attributes to be ignored when `maskAllText` is enabled */
export const DEFAULT_IGNORED_ATTRIBUTES = ['title', 'placeholder'];
46 changes: 31 additions & 15 deletions packages/replay/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,33 +95,49 @@ export class Replay implements Integration {
// eslint-disable-next-line deprecation/deprecation
ignoreClass,
}: ReplayConfiguration = {}) {
const privacyOptions = getPrivacyOptions({
mask,
unmask,
block,
unblock,
ignore,
blockClass,
blockSelector,
maskTextClass,
maskTextSelector,
ignoreClass,
});

this._recordingOptions = {
maskAllInputs,
maskAllText,
maskInputOptions: { ...(maskInputOptions || {}), password: true },
maskTextFn: maskFn,
maskInputFn: maskFn,
maskAttributeFn: (key: string, value: string): string => {
// For now, always mask these attributes
if (maskAttributes.includes(key)) {
maskAttributeFn: (key: string, value: string, el: HTMLElement): string => {
// We only mask attributes if `maskAllText` is true
if (!maskAllText) {
return value;
}

// unmaskTextSelector takes precendence
if (privacyOptions.unmaskTextSelector && el.matches(privacyOptions.unmaskTextSelector)) {
return value;
}

if (
maskAttributes.includes(key) ||
// Need to mask `value` attribute for `<input>` if it's a button-like
// type
(key === 'value' && el.tagName === 'INPUT' && ['submit', 'button'].includes(el.getAttribute('type') || ''))
) {
return value.replace(/[\S]/g, '*');
}

return value;
},

...getPrivacyOptions({
mask,
unmask,
block,
unblock,
ignore,
blockClass,
blockSelector,
maskTextClass,
maskTextSelector,
ignoreClass,
}),
...privacyOptions,

// Our defaults
slimDOMOptions: 'all',
Expand Down

0 comments on commit 2d9a378

Please sign in to comment.