From 45f9939b73961167a2b4ecabf6d4dbb79a76e541 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Thu, 29 Oct 2026 18:42:26 +0500 Subject: [PATCH 1/7] test(replay): real-browser attribute/value smoke and document.open masking cases (#120) Co-Authored-By: Claude Sonnet 5.5 --- .../test/replay-masking.browser.e2e.ts | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts index 9eade8cf..81fb647f 100644 --- a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts +++ b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts @@ -623,6 +623,169 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { }); } + // --------------------------------------------------------------------------------------------------------- + // Secrets in every place a page keeps them: prefilled and typed input values (text, password, card, e-mail), + // textarea, `alt`, `title`, `placeholder`, `aria-label`, `data-*`, element text; both present BEFORE recording + // starts (the full snapshot) and ADDED / EDITED after (the mutation path). + // --------------------------------------------------------------------------------------------------------- + const ATTR_CATEGORIES = [ + 'TEXT', + 'PASSWORD', + 'CC', + 'EMAIL', + 'TEXTAREA', + 'ALT', + 'IMGTITLE', + 'TITLE', + 'DATA', + 'ARIA', + 'LINK', + 'PH', + 'DATAUSER', + 'DIVTITLE', + 'DIV', + 'TYPED', + ]; + const attrSecrets = (phase: string) => ATTR_CATEGORIES.map((c) => `ATTRSMOKE_${c}_${phase}`); + const attrLeaks = (events: string) => + attrSecrets('PRE') + .concat(attrSecrets('ADDED')) + .filter((s) => events.includes(s)); + + const insertAttrFixture = (phase: string) => + page.evaluate((p) => { + const mount = document.getElementById('mount') as HTMLElement; + mount.insertAdjacentHTML( + 'beforeend', + `
+ + + + + +ATTRSMOKE_ALT_${p} +ATTRSMOKE_LINK_${p} + +
ATTRSMOKE_DIV_${p}
+ +
`, + ); + }, phase); + + const typeAttrSecret = () => + page.evaluate(() => { + for (const el of document.querySelectorAll('input.typed')) { + el.value = 'ATTRSMOKE_TYPED_PRE'; + el.dispatchEvent(new Event('input', { bubbles: true })); + } + }); + + const runAttrSmoke = async (mode: Mode, options: Record) => { + await open(); + await insertAttrFixture('PRE'); + await start(mode, options); + await settle(); + await insertAttrFixture('ADDED'); + await typeAttrSecret(); + await settle(400); + return dump(); + }; + + for (const mode of ['raw', 'provider'] as const) { + it(`attribute/value smoke (${mode}): no secret in values, text, alt, title, placeholder, aria, data-* at defaults`, async () => { + const events = await runAttrSmoke(mode, {}); + expect(events).toContain('TOP_CONTROL'); // the harness sees the page (control) + expect(attrLeaks(events)).toEqual([]); + await page.close(); + }); + } + + it('control: the attribute smoke fixture DOES leak with masking off (every category is detectable)', async () => { + const events = await runAttrSmoke('raw', { + maskAllText: false, + maskAllInputs: false, + blockAllMedia: false, + }); + const found = attrLeaks(events); + // the always-masked sensitive inputs (password, card) must NOT appear even here: the hard floor + expect(found).not.toContain('ATTRSMOKE_PASSWORD_PRE'); + expect(found).not.toContain('ATTRSMOKE_CC_PRE'); + // the rest were recorded in the clear, so the harness can see each category + for (const c of ['TEXT', 'EMAIL', 'TEXTAREA', 'ALT', 'TITLE', 'PH', 'DIV', 'LINK', 'TYPED']) { + expect(found, `${c} should be visible when masking is off`).toContain(`ATTRSMOKE_${c}_PRE`); + } + await page.close(); + }); + + // --------------------------------------------------------------------------------------------------------- + // document.open(): rewrites a document in place (replaces a frame's content without a navigation). + // --------------------------------------------------------------------------------------------------------- + const rewriteFrame = (id: string, html: string) => + page.evaluate( + ([frameId, markup]) => { + const d = (document.getElementById(frameId as string) as HTMLIFrameElement) + .contentDocument as Document; + d.open(); + d.write(markup as string); + d.close(); + }, + [id, html] as const, + ); + + const REWRITTEN = + '

FRAME4_SECRET_REWRITTEN

CONTROL_REWRITTEN

'; + + for (const mode of ['raw', 'provider'] as const) { + it(`document.open (${mode}): a blocked iframe rewritten in place contributes nothing`, async () => { + await open(); + await mountFrames({ id: 'a', n: 1 }); + await start(mode); + await settle(); + await rewriteFrame('a', REWRITTEN); + await settle(400); + await pokeFrame('a', 'AFTEROPEN'); + await settle(); + const events = await dump(); + expect(events).toContain('TOP_CONTROL'); + expect(events).not.toContain('CONTROL_REWRITTEN'); + expect(events).not.toContain('FRAME4_SECRET'); + expect(events).not.toContain('TYPED_SECRET_REWRITTEN'); + expect(leaks(events)).toEqual([]); + await page.close(); + }); + + it(`document.open (${mode}): an unblocked iframe rewritten in place stays masked`, async () => { + await open(); + await mountFrames({ id: 'a', n: 1, attrs: 'class="bugsee-show"' }); + await start(mode); + await settle(400); + await rewriteFrame('a', REWRITTEN); + await settle(400); + await pokeFrame('a', 'AFTEROPEN'); + await settle(); + const events = await dump(); + expect(events).toContain('CONTROL_FRAME1'); // the unblocked frame was recorded before the rewrite (control) + expect(events).not.toContain('FRAME4_SECRET'); + expect(events).not.toContain('TYPED_SECRET_REWRITTEN'); + expect(leaks(events)).toEqual([]); + await page.close(); + }); + } + + it('control: document.open content DOES leak with masking off, and a blocked iframe still does not', async () => { + await open(); + await mountFrames({ id: 'a', n: 1, attrs: 'class="bugsee-show"' }, { id: 'b', n: 3 }); + await start('raw', { maskAllText: false, maskAllInputs: false }); + await settle(400); + await rewriteFrame('a', REWRITTEN.replace('FRAME4_SECRET', 'FRAME4_SECRET_UNBLOCKED')); + await rewriteFrame('b', REWRITTEN.replace('FRAME4_SECRET', 'FRAME4_SECRET_BLOCKED')); + await settle(500); + const events = await dump(); + expect(events).toContain('FRAME4_SECRET_UNBLOCKED_REWRITTEN'); // visible in the unblocked frame: the harness can fail + expect(events).not.toContain('FRAME4_SECRET_BLOCKED'); // ...but nothing from the blocked one + await page.close(); + }); + it('control: with maskAllText and maskAllInputs switched off the same page DOES leak (the harness can fail)', async () => { await open(); await mountFrames({ id: 'a', n: 3, attrs: 'class="bugsee-show"' }); From 25ac3b693e5b7772f41aee5c48af4074c4f14134 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Thu, 29 Oct 2026 18:46:36 +0500 Subject: [PATCH 2/7] test(replay): strengthen masking smoke controls per review (#120) Co-Authored-By: Claude Sonnet 5.5 --- .../test/replay-masking.browser.e2e.ts | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts index 81fb647f..e6e64b81 100644 --- a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts +++ b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts @@ -626,7 +626,7 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { // --------------------------------------------------------------------------------------------------------- // Secrets in every place a page keeps them: prefilled and typed input values (text, password, card, e-mail), // textarea, `alt`, `title`, `placeholder`, `aria-label`, `data-*`, element text; both present BEFORE recording - // starts (the full snapshot) and ADDED / EDITED after (the mutation path). + // starts (the full snapshot) and ADDED / TYPED into after (the mutation path); existing nodes get typed values and a second section is inserted. // --------------------------------------------------------------------------------------------------------- const ATTR_CATEGORIES = [ 'TEXT', @@ -672,13 +672,14 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { ); }, phase); - const typeAttrSecret = () => - page.evaluate(() => { - for (const el of document.querySelectorAll('input.typed')) { - el.value = 'ATTRSMOKE_TYPED_PRE'; - el.dispatchEvent(new Event('input', { bubbles: true })); - } - }); + const typeAttrSecret = (phase: string) => + page.evaluate((p) => { + const el = document.querySelector( + `#smoke_${p} input.typed`, + ) as HTMLInputElement; + el.value = `ATTRSMOKE_TYPED_${p}`; + el.dispatchEvent(new Event('input', { bubbles: true })); + }, phase); const runAttrSmoke = async (mode: Mode, options: Record) => { await open(); @@ -686,7 +687,8 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { await start(mode, options); await settle(); await insertAttrFixture('ADDED'); - await typeAttrSecret(); + await typeAttrSecret('PRE'); + await typeAttrSecret('ADDED'); await settle(400); return dump(); }; @@ -695,27 +697,35 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { it(`attribute/value smoke (${mode}): no secret in values, text, alt, title, placeholder, aria, data-* at defaults`, async () => { const events = await runAttrSmoke(mode, {}); expect(events).toContain('TOP_CONTROL'); // the harness sees the page (control) + expect(events).toContain('smoke_PRE'); // ...the snapshot section (ids are not masked) + expect(events).toContain('smoke_ADDED'); // ...and the node added after start reached the events expect(attrLeaks(events)).toEqual([]); await page.close(); }); } - it('control: the attribute smoke fixture DOES leak with masking off (every category is detectable)', async () => { - const events = await runAttrSmoke('raw', { - maskAllText: false, - maskAllInputs: false, - blockAllMedia: false, + for (const mode of ['raw', 'provider'] as const) { + it(`control (${mode}): the attribute smoke fixture DOES leak with masking off (every category, both phases)`, async () => { + const events = await runAttrSmoke(mode, { + maskAllText: false, + maskAllInputs: false, + blockAllMedia: false, + }); + const found = attrLeaks(events); + for (const phase of ['PRE', 'ADDED']) { + // the always-masked sensitive inputs (password, card) must NOT appear even here: the hard floor + expect(found).not.toContain(`ATTRSMOKE_PASSWORD_${phase}`); + expect(found).not.toContain(`ATTRSMOKE_CC_${phase}`); + // everything else was recorded in the clear, so the harness can see each category + for (const c of ATTR_CATEGORIES.filter((x) => x !== 'PASSWORD' && x !== 'CC')) { + expect(found, `${c} (${phase}) should be visible when masking is off`).toContain( + `ATTRSMOKE_${c}_${phase}`, + ); + } + } + await page.close(); }); - const found = attrLeaks(events); - // the always-masked sensitive inputs (password, card) must NOT appear even here: the hard floor - expect(found).not.toContain('ATTRSMOKE_PASSWORD_PRE'); - expect(found).not.toContain('ATTRSMOKE_CC_PRE'); - // the rest were recorded in the clear, so the harness can see each category - for (const c of ['TEXT', 'EMAIL', 'TEXTAREA', 'ALT', 'TITLE', 'PH', 'DIV', 'LINK', 'TYPED']) { - expect(found, `${c} should be visible when masking is off`).toContain(`ATTRSMOKE_${c}_PRE`); - } - await page.close(); - }); + } // --------------------------------------------------------------------------------------------------------- // document.open(): rewrites a document in place (replaces a frame's content without a navigation). @@ -765,6 +775,7 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { await settle(); const events = await dump(); expect(events).toContain('CONTROL_FRAME1'); // the unblocked frame was recorded before the rewrite (control) + expect(events).toContain('CONTROL_REWRITTEN'); // ...and the rewritten document is still being recorded expect(events).not.toContain('FRAME4_SECRET'); expect(events).not.toContain('TYPED_SECRET_REWRITTEN'); expect(leaks(events)).toEqual([]); @@ -782,6 +793,7 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { await settle(500); const events = await dump(); expect(events).toContain('FRAME4_SECRET_UNBLOCKED_REWRITTEN'); // visible in the unblocked frame: the harness can fail + expect(events).toContain('TYPED_SECRET_REWRITTEN'); // the typed value is detectable too expect(events).not.toContain('FRAME4_SECRET_BLOCKED'); // ...but nothing from the blocked one await page.close(); }); From 1f04654380f901327f1567113aab0feeb9270b96 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Thu, 29 Oct 2026 18:49:38 +0500 Subject: [PATCH 3/7] test(replay): document.open control covers both modes and post-rewrite edits (#120) Co-Authored-By: Claude Sonnet 5.5 --- .../test/replay-masking.browser.e2e.ts | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts index e6e64b81..c0ecf82a 100644 --- a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts +++ b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts @@ -783,20 +783,38 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { }); } - it('control: document.open content DOES leak with masking off, and a blocked iframe still does not', async () => { - await open(); - await mountFrames({ id: 'a', n: 1, attrs: 'class="bugsee-show"' }, { id: 'b', n: 3 }); - await start('raw', { maskAllText: false, maskAllInputs: false }); - await settle(400); - await rewriteFrame('a', REWRITTEN.replace('FRAME4_SECRET', 'FRAME4_SECRET_UNBLOCKED')); - await rewriteFrame('b', REWRITTEN.replace('FRAME4_SECRET', 'FRAME4_SECRET_BLOCKED')); - await settle(500); - const events = await dump(); - expect(events).toContain('FRAME4_SECRET_UNBLOCKED_REWRITTEN'); // visible in the unblocked frame: the harness can fail - expect(events).toContain('TYPED_SECRET_REWRITTEN'); // the typed value is detectable too - expect(events).not.toContain('FRAME4_SECRET_BLOCKED'); // ...but nothing from the blocked one - await page.close(); - }); + for (const mode of ['raw', 'provider'] as const) { + it(`control (${mode}): document.open content and later edits DO leak with masking off; a blocked iframe still does not`, async () => { + await open(); + await mountFrames({ id: 'a', n: 1, attrs: 'class="bugsee-show"' }, { id: 'b', n: 3 }); + await start(mode, { maskAllText: false, maskAllInputs: false }); + await settle(400); + // distinct markers per frame, so a leak can be attributed to the frame it came from + await rewriteFrame('a', REWRITTEN.replaceAll('REWRITTEN', 'REWRITTEN_UNBLOCKED')); + await rewriteFrame('b', REWRITTEN.replaceAll('REWRITTEN', 'REWRITTEN_BLOCKED')); + await settle(500); + await pokeFrame('a', 'AFTEROPEN'); + await pokeFrame('b', 'AFTEROPENB'); + await settle(500); + const events = await dump(); + // visible in the unblocked frame: the harness can fail, for the rewrite and for what changes after it + for (const marker of [ + 'FRAME4_SECRET_REWRITTEN_UNBLOCKED', + 'TYPED_SECRET_REWRITTEN_UNBLOCKED', + 'CONTROL_REWRITTEN_UNBLOCKED', + 'IFB_SECRET_EDIT_AFTEROPEN', + 'IFB_SECRET_ADDED_AFTEROPEN', + 'TYPED_SECRET_AFTEROPEN', + 'IFB_SECRET_PH_AFTEROPEN', + ]) { + expect(events, marker).toContain(marker); + } + // ...but nothing at all from the blocked one + expect(events).not.toContain('REWRITTEN_BLOCKED'); + expect(events).not.toContain('AFTEROPENB'); + await page.close(); + }); + } it('control: with maskAllText and maskAllInputs switched off the same page DOES leak (the harness can fail)', async () => { await open(); From 44cef0b9b0966d48d8006fa991995a63f51f9c9b Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Thu, 29 Oct 2026 18:53:40 +0500 Subject: [PATCH 4/7] test(replay): attribute smoke covers typed password/card and blockAllMedia-off (#120) Co-Authored-By: Claude Sonnet 5.5 --- .../test/replay-masking.browser.e2e.ts | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts index c0ecf82a..5c0bfed6 100644 --- a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts +++ b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts @@ -645,7 +645,11 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { 'DIVTITLE', 'DIV', 'TYPED', + 'PWTYPED', + 'CCTYPED', ]; + // the always-masked floor: never visible, whatever the options + const FLOOR = ['PASSWORD', 'CC', 'PWTYPED', 'CCTYPED']; const attrSecrets = (phase: string) => ATTR_CATEGORIES.map((c) => `ATTRSMOKE_${c}_${phase}`); const attrLeaks = (events: string) => attrSecrets('PRE') @@ -659,8 +663,8 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { 'beforeend', `
- - + + ATTRSMOKE_ALT_${p} @@ -679,6 +683,16 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { ) as HTMLInputElement; el.value = `ATTRSMOKE_TYPED_${p}`; el.dispatchEvent(new Event('input', { bubbles: true })); + for (const [cls, tag] of [ + ['pw', 'PWTYPED'], + ['cc', 'CCTYPED'], + ] as const) { + const f = document.querySelector( + `#smoke_${p} input.${cls}`, + ) as HTMLInputElement; + f.value = `ATTRSMOKE_${tag}_${p}`; + f.dispatchEvent(new Event('input', { bubbles: true })); + } }, phase); const runAttrSmoke = async (mode: Mode, options: Record) => { @@ -704,6 +718,17 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { }); } + for (const mode of ['raw', 'provider'] as const) { + // At defaults the alt/title are hidden because the image is BLOCKED; with media unblocked the + // attribute masking itself must hide them (the control below proves they are detectable then). + it(`attribute smoke (${mode}): with blockAllMedia off, attribute masking still hides alt/title/data-*`, async () => { + const events = await runAttrSmoke(mode, { blockAllMedia: false }); + expect(events).toContain('smoke_ADDED'); + expect(attrLeaks(events)).toEqual([]); + await page.close(); + }); + } + for (const mode of ['raw', 'provider'] as const) { it(`control (${mode}): the attribute smoke fixture DOES leak with masking off (every category, both phases)`, async () => { const events = await runAttrSmoke(mode, { @@ -714,10 +739,9 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { const found = attrLeaks(events); for (const phase of ['PRE', 'ADDED']) { // the always-masked sensitive inputs (password, card) must NOT appear even here: the hard floor - expect(found).not.toContain(`ATTRSMOKE_PASSWORD_${phase}`); - expect(found).not.toContain(`ATTRSMOKE_CC_${phase}`); + for (const c of FLOOR) expect(found).not.toContain(`ATTRSMOKE_${c}_${phase}`); // everything else was recorded in the clear, so the harness can see each category - for (const c of ATTR_CATEGORIES.filter((x) => x !== 'PASSWORD' && x !== 'CC')) { + for (const c of ATTR_CATEGORIES.filter((x) => !FLOOR.includes(x))) { expect(found, `${c} (${phase}) should be visible when masking is off`).toContain( `ATTRSMOKE_${c}_${phase}`, ); From b85e86984c01297e6a55db040016a137cb304097 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Thu, 29 Oct 2026 18:56:29 +0500 Subject: [PATCH 5/7] test(replay): anchor smoke on serialized img and password field (#120) Co-Authored-By: Claude Sonnet 5.5 --- .../instrumentation-tests/test/replay-masking.browser.e2e.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts index 5c0bfed6..1bd6e727 100644 --- a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts +++ b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts @@ -712,6 +712,10 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { const events = await runAttrSmoke(mode, {}); expect(events).toContain('TOP_CONTROL'); // the harness sees the page (control) expect(events).toContain('smoke_PRE'); // ...the snapshot section (ids are not masked) + // the password/card fields ARE in the snapshot (as masked/ignored inputs), so their absence of secrets below is a + // statement about masking, not about the recorder never seeing them. Typed events on them are dropped by the + // sensitive-input ignore rule, so the PWTYPED/CCTYPED checks guard that rule and have no failing control by design. + expect(events).toContain('"type":"password"'); expect(events).toContain('smoke_ADDED'); // ...and the node added after start reached the events expect(attrLeaks(events)).toEqual([]); await page.close(); @@ -724,6 +728,7 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { it(`attribute smoke (${mode}): with blockAllMedia off, attribute masking still hides alt/title/data-*`, async () => { const events = await runAttrSmoke(mode, { blockAllMedia: false }); expect(events).toContain('smoke_ADDED'); + expect(events).toContain('R0lGODlhAQABAAAAACw'); // the image IS serialized (not a blocked placeholder) expect(attrLeaks(events)).toEqual([]); await page.close(); }); From e5b4ef5f1951913edb84162b4b6cd25ae63aa1d3 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Thu, 29 Oct 2026 18:58:59 +0500 Subject: [PATCH 6/7] test(replay): add card-field and snapshot anchors to masking smoke (#120) Co-Authored-By: Claude Sonnet 5.5 --- .../instrumentation-tests/test/replay-masking.browser.e2e.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts index 1bd6e727..109ce266 100644 --- a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts +++ b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts @@ -712,10 +712,11 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { const events = await runAttrSmoke(mode, {}); expect(events).toContain('TOP_CONTROL'); // the harness sees the page (control) expect(events).toContain('smoke_PRE'); // ...the snapshot section (ids are not masked) - // the password/card fields ARE in the snapshot (as masked/ignored inputs), so their absence of secrets below is a + // the password field IS in the snapshot (as masked/ignored inputs), so their absence of secrets below is a // statement about masking, not about the recorder never seeing them. Typed events on them are dropped by the // sensitive-input ignore rule, so the PWTYPED/CCTYPED checks guard that rule and have no failing control by design. expect(events).toContain('"type":"password"'); + expect(events).toContain('"class":"cc"'); // ...and so is the card field expect(events).toContain('smoke_ADDED'); // ...and the node added after start reached the events expect(attrLeaks(events)).toEqual([]); await page.close(); @@ -728,6 +729,7 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { it(`attribute smoke (${mode}): with blockAllMedia off, attribute masking still hides alt/title/data-*`, async () => { const events = await runAttrSmoke(mode, { blockAllMedia: false }); expect(events).toContain('smoke_ADDED'); + expect(events).toContain('smoke_PRE'); expect(events).toContain('R0lGODlhAQABAAAAACw'); // the image IS serialized (not a blocked placeholder) expect(attrLeaks(events)).toEqual([]); await page.close(); From 2df4e8525c2b6edb1b11ac877863607b87174d04 Mon Sep 17 00:00:00 2001 From: Alexey Karimov Date: Thu, 29 Oct 2026 21:12:16 +0500 Subject: [PATCH 7/7] test(replay): non-prefix tags so the unblocked positive control cannot be satisfied by the blocked frame Co-Authored-By: Claude Sonnet 5.5 --- .../test/replay-masking.browser.e2e.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts index 109ce266..4cc08433 100644 --- a/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts +++ b/packages/instrumentation-tests/test/replay-masking.browser.e2e.ts @@ -824,8 +824,8 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { await rewriteFrame('a', REWRITTEN.replaceAll('REWRITTEN', 'REWRITTEN_UNBLOCKED')); await rewriteFrame('b', REWRITTEN.replaceAll('REWRITTEN', 'REWRITTEN_BLOCKED')); await settle(500); - await pokeFrame('a', 'AFTEROPEN'); - await pokeFrame('b', 'AFTEROPENB'); + await pokeFrame('a', 'AFTEROPEN_UNBLOCKED'); + await pokeFrame('b', 'AFTEROPEN_BLOCKED'); await settle(500); const events = await dump(); // visible in the unblocked frame: the harness can fail, for the rewrite and for what changes after it @@ -833,16 +833,16 @@ describe('replay masking — REAL Chromium + REAL rrweb bundle', () => { 'FRAME4_SECRET_REWRITTEN_UNBLOCKED', 'TYPED_SECRET_REWRITTEN_UNBLOCKED', 'CONTROL_REWRITTEN_UNBLOCKED', - 'IFB_SECRET_EDIT_AFTEROPEN', - 'IFB_SECRET_ADDED_AFTEROPEN', - 'TYPED_SECRET_AFTEROPEN', - 'IFB_SECRET_PH_AFTEROPEN', + 'IFB_SECRET_EDIT_AFTEROPEN_UNBLOCKED', + 'IFB_SECRET_ADDED_AFTEROPEN_UNBLOCKED', + 'TYPED_SECRET_AFTEROPEN_UNBLOCKED', + 'IFB_SECRET_PH_AFTEROPEN_UNBLOCKED', ]) { expect(events, marker).toContain(marker); } // ...but nothing at all from the blocked one expect(events).not.toContain('REWRITTEN_BLOCKED'); - expect(events).not.toContain('AFTEROPENB'); + expect(events).not.toContain('AFTEROPEN_BLOCKED'); await page.close(); }); }