From da4274355054dad151fcad1a31839ebdcfe83181 Mon Sep 17 00:00:00 2001 From: Francesco Gringl-Novy Date: Fri, 24 Apr 2026 11:12:41 +0200 Subject: [PATCH] fix(replay): Ensure `maskAttributes` works with `maskAllText=false` --- .../replay-internal/src/util/maskAttribute.ts | 22 ++++++------ .../test/unit/util/maskAttribute.test.ts | 34 +++++++++++++++++-- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/packages/replay-internal/src/util/maskAttribute.ts b/packages/replay-internal/src/util/maskAttribute.ts index 12daaeb97dff..feb3898fc6ae 100644 --- a/packages/replay-internal/src/util/maskAttribute.ts +++ b/packages/replay-internal/src/util/maskAttribute.ts @@ -11,6 +11,8 @@ interface MaskAttributeParams { /** * Masks an attribute if necessary, otherwise return attribute value as-is. + * Keys listed in `maskAttributes` are masked even when `maskAllText` is false; + * masking `value` on submit/button inputs without listing `value` still requires `maskAllText`. */ export function maskAttribute({ el, @@ -20,22 +22,20 @@ export function maskAttribute({ privacyOptions, value, }: MaskAttributeParams): string { - // We only mask attributes if `maskAllText` is true - if (!maskAllText) { - return value; - } - // unmaskTextSelector takes precedence if (privacyOptions.unmaskTextSelector && el.matches(privacyOptions.unmaskTextSelector)) { return value; } - if ( - maskAttributes.includes(key) || - // Need to mask `value` attribute for `` if it's a button-like - // type - (key === 'value' && el.tagName === 'INPUT' && ['submit', 'button'].includes(el.getAttribute('type') || '')) - ) { + const masksNamedAttribute = maskAttributes.includes(key); + // When `maskAllText` is enabled, also mask `value` on button-like inputs even if `value` is not listed. + const masksSubmitButtonValue = + maskAllText && + key === 'value' && + el.tagName === 'INPUT' && + ['submit', 'button'].includes(el.getAttribute('type') || ''); + + if (masksNamedAttribute || masksSubmitButtonValue) { return value.replace(/[\S]/g, '*'); } diff --git a/packages/replay-internal/test/unit/util/maskAttribute.test.ts b/packages/replay-internal/test/unit/util/maskAttribute.test.ts index 4819e5411e11..0446f67b04a3 100644 --- a/packages/replay-internal/test/unit/util/maskAttribute.test.ts +++ b/packages/replay-internal/test/unit/util/maskAttribute.test.ts @@ -33,11 +33,15 @@ describe('maskAttribute', () => { test.each([ ['masks if `maskAllText` is true', defaultArgs, '***'], [ - 'does not mask if `maskAllText` is false, despite `maskTextSelector` ', - { ...defaultArgs, maskAllText: false, maskTextSelector: 'classy' }, + 'masks when key is in `maskAttributes` even if `maskAllText` is false', + { ...defaultArgs, maskAllText: false }, + '***', + ], + [ + 'does not mask when key is not in `maskAttributes` and `maskAllText` is false', + { ...defaultArgs, maskAllText: false, key: 'id', maskAttributes: ['title'] }, 'foo', ], - ['does not mask if `maskAllText` is false', { ...defaultArgs, maskAllText: false }, 'foo'], [ 'does not mask if `unmaskTextSelector` matches', { ...defaultArgs, privacyOptions: { ...privacyOptions, unmaskTextSelector: '.classy' } }, @@ -53,6 +57,30 @@ describe('maskAttribute', () => { { ...defaultArgs, el: inputButton, value: 'input value' }, '***** *****', ], + [ + 'does not mask submit `value` when `maskAllText` is false unless `value` is in `maskAttributes`', + { + ...defaultArgs, + el: inputSubmit, + key: 'value', + maskAttributes: ['title'], + maskAllText: false, + value: 'input value', + }, + 'input value', + ], + [ + 'masks submit `value` when `maskAllText` is false if `value` is in `maskAttributes`', + { + ...defaultArgs, + el: inputSubmit, + key: 'value', + maskAttributes: ['value'], + maskAllText: false, + value: 'input value', + }, + '***** *****', + ], ])('%s', (_: string, input, output) => { expect(maskAttribute(input)).toEqual(output); });