Skip to content

fix(trace-viewer): reject unsafe snapshot tag names - #42712

Merged
Devin Rousso (dcrousso) merged 4 commits into
microsoft:mainfrom
SebTardif:fix-trace-snapshot-tag-names
Sep 17, 2026
Merged

Devin Rousso (dcrousso) merged 4 commits into
microsoft:mainfrom
SebTardif:fix-trace-snapshot-tag-names

Conversation

@SebTardif

@SebTardif Sebastien Tardif (SebTardif) commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Drop snapshot tag names that would emit extra attributes (space, =, <, >, /).
  • Keep Unicode custom elements such as math-α.

Problem

SnapshotRenderer concatenates name into HTML. Filters for on*, iframe src, and meta http-equiv only walk the attrs object. A name such as iframe src=https://evil.example becomes real markup those filters never see.

script-src 'nonce-…' already blocks inline handlers such as onerror=alert(1). This change is the attribute-smuggle case, not script XSS.

Public path: npx playwright show-trace on a crafted zip.

Change

Reject empty names and names matching /[\s=<>\/]/. Ordinary tags and Unicode custom elements still render.

Validation

  • snapshot renderer drops unsafe tag names that smuggle attributes
  • snapshot renderer still renders ordinary custom elements includes math-α and my-élément

Related

Crafted traces could put markup in the element name so the renderer
emitted attributes such as onerror. Drop names that are not a single
HTML token.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
Allow WHATWG-valid names such as my-widget_v2 in snapshot HTML.
Attribute-smuggling payloads still fail the tag-name check.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Comment thread packages/isomorphic/trace/snapshotRenderer.ts Outdated
Comment thread tests/library/snapshot-renderer.spec.ts
Reject only names that smuggle attributes (whitespace, =, <, >, /).

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
return;
// Reject names that would emit extra attributes (space, =, <, >, /).
// Allow Unicode custom elements such as math-α.
if (!name || /[\s=<>\/]/.test(name))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we need the !name? everything else seems to assume it's non-empty so maybe that's a guarantee at this point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Rousso (@dcrousso)

do we need the !name? everything else seems to assume it's non-empty so maybe that's a guarantee at this point?

No. Capture never writes an empty nodeName, and '' only emits <>. Dropped the !name check in 169e8e4eb. The smuggle regex stays.


test('snapshot renderer drops unsafe tag names that smuggle attributes', () => {
const renderer = new SnapshotRenderer(new LRUCache(1_000_000), [], [makeSnapshot({
html: ['HTML', {}, ['BODY', {}, ['img src=x onerror=alert(1)', {}], 'safe']],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you share more info about how this could actually happen? i thought we already did a lot of sanitization that would prevent this so im concerned we're adding additional protection for something that cant actually happen

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Rousso (@dcrousso)

can you share more info about how this could actually happen? i thought we already did a lot of sanitization that would prevent this

A recorded page cannot produce this. Capture uses live node.nodeName, and document.createElement('iframe src=...') throws.

A zip opened with show-trace can. Load is JSON.parse then addFrameSnapshot with no name check (traceModernizer.ts).

The existing filters only match exact names (toUpperCase() === 'IFRAME'|'META'|'IMG'). They never see iframe src=https://evil.example, so the iframe src rewrite does not run. Unfixed render of ['iframe src=https://evil.example', {}] is <iframe src=https://evil.example>.

Same crafted-zip model as the SCRIPT / on* / srcdoc / object / embed / doctype guards already in this file.

!name is not needed. Empty name only emits <>. Dropped that check in 169e8e4eb.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Capture never produces an empty nodeName. Keep the smuggle regex.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
@github-actions

Copy link
Copy Markdown
Contributor

Test results for "tests 1"

2 failed
❌ [installation tests] › playwright-cdn.spec.ts:43 › playwright cdn failover should work (https://cdn.playwright.dev/dbazure/download/playwright) @package-installations-macos-latest
❌ [installation tests] › playwright-cdn.spec.ts:43 › playwright cdn failover should work (https://playwright.download.prss.microsoft.com/dbazure/download/playwright) @package-installations-macos-latest

7 flaky ⚠️ [chromium-library] › library/browsercontext-page-event.spec.ts:173 › should work with Ctrl-clicking `@frozen-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:690 › screencast › should capture full viewport `@frozen-time-library-chromium-linux`
⚠️ [chromium-library] › library/video.spec.ts:690 › screencast › should capture full viewport `@chromium-ubuntu-22.04-node24`
⚠️ [chromium-library] › library/video.spec.ts:762 › screencast › should work with video+trace `@chromium-ubuntu-22.04-node24`
⚠️ [firefox-library] › library/browsercontext-cookies-third-party.spec.ts:257 › third party 'Partitioned;' cookies `@firefox-ubuntu-22.04-node20`
⚠️ [firefox-library] › library/browsercontext-cookies-third-party.spec.ts:470 › top level 'Partitioned;' cookie and same origin iframe `@firefox-ubuntu-22.04-node20`
⚠️ [playwright-test] › ui-mode-trace.spec.ts:715 › should indicate current test status `@windows-latest-node22`

51987 passed, 1247 skipped


Merge workflow run.

@github-actions

Copy link
Copy Markdown
Contributor

Test results for "MCP"

8590 passed, 1446 skipped


Merge workflow run.

@dcrousso Devin Rousso (dcrousso) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

thanks for the fix!

@dcrousso
Devin Rousso (dcrousso) merged commit 3cd1025 into microsoft:main Sep 17, 2026
44 of 45 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants