diff --git a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts index 1a6effdceb058..803b0fb3e6703 100644 --- a/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts +++ b/packages/playwright-core/src/server/trace/recorder/snapshotterInjected.ts @@ -371,8 +371,13 @@ export function frameSnapshotStreamer(snapshotStreamer: string, removeNoScript: } if (removeNoScript && nodeName === 'NOSCRIPT') return; - if (nodeName === 'META' && (node as HTMLMetaElement).httpEquiv.toLowerCase() === 'content-security-policy') - return; + if (nodeName === 'META') { + const httpEquiv = (node as HTMLMetaElement).httpEquiv.toLowerCase(); + // Drop META directives that can navigate, set cookies, or otherwise + // affect the trace viewer when the recorded snapshot is rendered. + if (httpEquiv === 'content-security-policy' || httpEquiv === 'refresh' || httpEquiv === 'set-cookie') + return; + } // Skip iframes which are inside document's head as they are not visible. // See https://github.com/microsoft/playwright/issues/12005. if ((nodeName === 'IFRAME' || nodeName === 'FRAME') && headNesting) diff --git a/packages/playwright-core/src/utils/isomorphic/trace/snapshotRenderer.ts b/packages/playwright-core/src/utils/isomorphic/trace/snapshotRenderer.ts index 0b0085a00366c..62ad3aaad636d 100644 --- a/packages/playwright-core/src/utils/isomorphic/trace/snapshotRenderer.ts +++ b/packages/playwright-core/src/utils/isomorphic/trace/snapshotRenderer.ts @@ -109,8 +109,13 @@ export class SnapshotRenderer { const isFrame = nodeName === 'IFRAME' || nodeName === 'FRAME'; const isAnchor = nodeName === 'A'; const isImg = nodeName === 'IMG'; + const isMeta = nodeName === 'META'; const isImgWithCurrentSrc = isImg && attrs.some(a => a[0] === kCurrentSrcAttribute); const isSourceInsidePictureWithCurrentSrc = nodeName === 'SOURCE' && parentTag === 'PICTURE' && parentAttrs?.some(a => a[0] === kCurrentSrcAttribute); + // For META, only allow a small whitelist of http-equiv directives so a malicious snapshot + // cannot navigate the snapshot iframe via e.g. or otherwise + // affect the trace viewer. + const hasUnsafeHttpEquiv = isMeta && attrs.some(a => a[0].toLowerCase() === 'http-equiv' && !kAllowedMetaHttpEquivs.has(a[1].trim().toLowerCase())); for (const [attr, value] of attrs) { let attrName = attr; if (isFrame && attr.toLowerCase() === 'src') { @@ -127,6 +132,10 @@ export class SnapshotRenderer { // we will be using the currentSrc instead. attrName = '_' + attrName; } + if (hasUnsafeHttpEquiv && (attr.toLowerCase() === 'http-equiv' || attr.toLowerCase() === 'content')) { + // Neutralize the META directive by renaming the attribute so the browser ignores it. + attrName = '_' + attr; + } let attrValue = value; if (!isAnchor && (attr.toLowerCase() === 'href' || attr.toLowerCase() === 'src' || attr === kCurrentSrcAttribute)) attrValue = rewriteURLForCustomProtocol(value); @@ -220,6 +229,10 @@ export class SnapshotRenderer { const autoClosing = new Set(['AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR']); +// Whitelist of META http-equiv directives that are safe to render in the trace viewer. +// Notably excludes 'refresh' (auto-navigation), 'set-cookie' and 'content-security-policy'. +const kAllowedMetaHttpEquivs = new Set(['content-type', 'content-language', 'default-style', 'x-ua-compatible']); + function snapshotNodes(snapshot: FrameSnapshot): NodeSnapshot[] { if (!(snapshot as any)._nodes) { const nodes: NodeSnapshot[] = []; diff --git a/packages/trace-viewer/snapshot.html b/packages/trace-viewer/snapshot.html index 60a5d82cf5194..afd2fcd5acb08 100644 --- a/packages/trace-viewer/snapshot.html +++ b/packages/trace-viewer/snapshot.html @@ -16,7 +16,7 @@
- +