Is there an existing issue for this?
I searched the existing issues and did not find one covering this specific unguarded property access.
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/browser
SDK Version
10.73.0 (also reproduced on 10.72.0 — the relevant code is byte-for-byte identical between the two)
Framework Version
React 19, Vite. The SDK runs inside an embeddable widget that is loaded into third-party pages we do not control.
Link to Sentry event
Not linkable — the events are in a private project. Full sanitized stack traces are below.
Reproduction Example/SDK Setup
Sentry.init({
dsn: '…',
integrations: [
Sentry.replayIntegration({ maskAllText: false, blockAllMedia: false }),
],
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 1.0,
});
Steps to Reproduce
- Initialise the browser SDK with
replayIntegration on a page that embeds one or more cross-origin iframes.
- Let a replay session start and let those iframes load.
- When rrweb's
IframeManager fires onIframeLoad for such an iframe, ShadowDomManager.observeAttachShadow() runs and reads iframeWindow.Element.
I have not been able to isolate the exact origin state of the iframe at the moment it throws — I suspect a frame whose document is still readable when the load handler runs but whose Window is already cross-origin restricted. I would rather report the unguarded access than guess at the precise trigger; happy to gather more if useful.
Expected Result
Replay skips iframes it cannot access and continues recording. Instrumentation should not raise errors into the host page's window.onerror / onunhandledrejection.
Actual Result
Two unhandled errors, both originating inside @sentry/replay, captured via mechanism: auto.browser.global_handlers.*.
Variant 1 — TypeError
TypeError: Cannot read properties of undefined (reading 'prototype')
@sentry/replay/build/npm/esm/index.js:649:7 (HTMLIFrameElement.<anonymous>) listener();
@sentry/replay/build/npm/esm/index.js:1286:13 (<anonymous>) onIframeLoad(
@sentry/replay/build/npm/esm/index.js:2118:35 (onIframeLoad) this.shadowDomManager.observeAttachShadow(iframe);
@sentry/replay/build/npm/esm/index.js:3905:10 (observeAttachShadow) this.patchAttachShadow(
@sentry/replay/build/npm/esm/index.js:3917:17 (patchAttachShadow) element.prototype,
Variant 2 — SecurityError (DOMException.code: 18)
SecurityError: Failed to read a named property 'Element' from 'Window':
Blocked a frame with origin "https://<host-page-origin>" from accessing a cross-origin frame.
@sentry/replay/build/npm/esm/index.js:1286:13 (HTMLIFrameElement.<anonymous>) onIframeLoad(
@sentry/replay/build/npm/esm/index.js:2118:35 (onIframeLoad) this.shadowDomManager.observeAttachShadow(iframe);
Analysis
The guard in ShadowDomManager.observeAttachShadow covers two accesses but not the one that actually fails:
observeAttachShadow(iframeElement) {
const iframeDoc = getIFrameContentDocument(iframeElement);
const iframeWindow = getIFrameContentWindow(iframeElement);
if (!iframeDoc || !iframeWindow) return;
this.patchAttachShadow(
iframeWindow.Element, // <-- unguarded cross-origin read
iframeDoc
);
}
Both helpers are defensive:
function getIFrameContentDocument(iframe) { try { return iframe.contentDocument; } catch {} }
function getIFrameContentWindow(iframe) { try { return iframe.contentWindow; } catch {} }
which is what makes the guard look sufficient. But iframe.contentWindow does not throw for a cross-origin frame — the browser returns a restricted Window proxy, so iframeWindow is truthy and the early return does not fire. The subsequent bare iframeWindow.Element read then either throws SecurityError (variant 2) or evaluates to undefined, so element.prototype inside patchAttachShadow throws TypeError (variant 1).
In other words: the two accesses that are wrapped are not the ones that fail, and the one that fails is not wrapped.
Suggested Fix
Guard the property read the same way the others are, and bail when the constructor is not reachable:
observeAttachShadow(iframeElement) {
const iframeDoc = getIFrameContentDocument(iframeElement);
const iframeWindow = getIFrameContentWindow(iframeElement);
if (!iframeDoc || !iframeWindow) return;
let iframeElementConstructor;
try {
iframeElementConstructor = iframeWindow.Element;
} catch {
return; // cross-origin frame — nothing to observe
}
if (!iframeElementConstructor?.prototype) return;
this.patchAttachShadow(iframeElementConstructor, iframeDoc);
}
Why this matters beyond the noise
For an SDK embedded in pages the SDK user does not control, an instrumentation error that escapes to window.onerror is attributed to the host page and is visible to whoever owns it. Replay is a passive recorder, so it should fail closed on frames it cannot reach rather than raise into the page.
Happy to open a PR with the above if that is welcome.
Is there an existing issue for this?
I searched the existing issues and did not find one covering this specific unguarded property access.
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/browserSDK Version
10.73.0 (also reproduced on 10.72.0 — the relevant code is byte-for-byte identical between the two)
Framework Version
React 19, Vite. The SDK runs inside an embeddable widget that is loaded into third-party pages we do not control.
Link to Sentry event
Not linkable — the events are in a private project. Full sanitized stack traces are below.
Reproduction Example/SDK Setup
Steps to Reproduce
replayIntegrationon a page that embeds one or more cross-origin iframes.IframeManagerfiresonIframeLoadfor such an iframe,ShadowDomManager.observeAttachShadow()runs and readsiframeWindow.Element.I have not been able to isolate the exact origin state of the iframe at the moment it throws — I suspect a frame whose document is still readable when the load handler runs but whose
Windowis already cross-origin restricted. I would rather report the unguarded access than guess at the precise trigger; happy to gather more if useful.Expected Result
Replay skips iframes it cannot access and continues recording. Instrumentation should not raise errors into the host page's
window.onerror/onunhandledrejection.Actual Result
Two unhandled errors, both originating inside
@sentry/replay, captured viamechanism: auto.browser.global_handlers.*.Variant 1 —
TypeErrorVariant 2 —
SecurityError(DOMException.code: 18)Analysis
The guard in
ShadowDomManager.observeAttachShadowcovers two accesses but not the one that actually fails:Both helpers are defensive:
which is what makes the guard look sufficient. But
iframe.contentWindowdoes not throw for a cross-origin frame — the browser returns a restrictedWindowproxy, soiframeWindowis truthy and the early return does not fire. The subsequent bareiframeWindow.Elementread then either throwsSecurityError(variant 2) or evaluates toundefined, soelement.prototypeinsidepatchAttachShadowthrowsTypeError(variant 1).In other words: the two accesses that are wrapped are not the ones that fail, and the one that fails is not wrapped.
Suggested Fix
Guard the property read the same way the others are, and bail when the constructor is not reachable:
Why this matters beyond the noise
For an SDK embedded in pages the SDK user does not control, an instrumentation error that escapes to
window.onerroris attributed to the host page and is visible to whoever owns it. Replay is a passive recorder, so it should fail closed on frames it cannot reach rather than raise into the page.Happy to open a PR with the above if that is welcome.