Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nuxt (the affected code is in @sentry/core, so all browser SDKs are affected)
SDK Version
10.34.0 — the affected code is unchanged on current master (packages/core/src/integrations/functiontostring.ts)
Framework Version
Nuxt 4 (not framework-specific)
Description
The functionToStringIntegration (enabled by default) patches Function.prototype.toString. The patched function calls getClient() → getCurrentScope() → getMainCarrier() → getSentryCarrier(GLOBAL_OBJ) on every .toString() invocation, which performs a property read of GLOBAL_OBJ.__SENTRY__.
GLOBAL_OBJ is globalThis, which in a Window realm is the WindowProxy of the realm's browsing context. If that browsing context is later navigated to a cross-origin document while code from the old realm can still be invoked (e.g. a still-alive parent window holding references into a same-origin child iframe whose src changed to a cross-origin URL), the __SENTRY__ read on the WindowProxy throws:
SecurityError: Failed to read a named property '__SENTRY__' from 'Window':
Blocked a frame with origin "https://app.example.com" from accessing a cross-origin frame.
(current Chrome wording: Blocked a restricted frame with origin "…" from accessing another frame.)
Because the patch is installed on Function.prototype, the throw is triggered by any third-party script calling .toString(). In our production case the chain was: Microsoft Clarity's MutationObserver serialization → Tawk.to's core-js Function.prototype.toString wrapper → Facebook Pixel's (fbevents.js) core-js inspectSource → Sentry's patched toString → SecurityError, surfacing as onunhandledrejection noise (24 events within ~1 second from a single page transition on a page embedding a third-party game iframe with sandbox="... allow-top-navigation").
Production stack (origins redacted, resolved via source maps):
/0.8.66/clarity.js (async/await machinery, MutationObserver serialization)
/_s/v4/app/.../twk-chunk-vendors.js (ShadowRoot.toString — core-js toString wrapper)
/en_US/fbevents.js (core-js inspectSource)
@sentry/core/build/esm/integrations/functiontostring.js:24 (Function.prototype.toString)
@sentry/core/build/esm/currentScopes.js:101 (getClient)
@sentry/core/build/esm/currentScopes.js:10 (getCurrentScope)
@sentry/core/build/esm/carrier.js:18 (getMainCarrier → getSentryCarrier(GLOBAL_OBJ) → __SENTRY__ read throws)
Note: the native Function.prototype.toString never throws for these calls — the throw is introduced solely by the integration's internal carrier access, so the SDK converts harmless third-party introspection into uncatchable (for the app) SecurityError noise, which Sentry itself then reports.
Steps to Reproduce
Self-contained repro (Chrome). Serve the two files below from http://127.0.0.1:8801 and also expose the same directory on http://127.0.0.1:8802 (different port = cross-origin but same-site, any static server works):
parent.html:
<!doctype html>
<iframe id="f" src="/child.html"></iframe>
<script>
const f = document.getElementById('f')
let done = false
f.onload = () => {
if (done) return
done = true
const childFn = f.contentWindow.exposedFn // keep a reference into the child realm
setTimeout(() => { f.src = 'http://127.0.0.1:8802/parent.html' }, 300) // navigate iframe cross-origin
setTimeout(() => {
try {
childFn.toString() // resolves to the child realm's patched Function.prototype.toString
console.log('ok')
} catch (e) {
console.log('THREW', e.name, e.message)
}
}, 1500)
}
</script>
child.html (stands in for any same-origin frame that initialized a Sentry browser SDK — inlined replica of functiontostring.ts + carrier.ts so the repro needs no bundler; using the real SDK behaves identically):
<!doctype html>
<script>
const SDK_VERSION = '10.34.0'
const GLOBAL_OBJ = globalThis
function getSentryCarrier(carrier) {
const __SENTRY__ = (carrier.__SENTRY__ = carrier.__SENTRY__ || {})
__SENTRY__.version = __SENTRY__.version || SDK_VERSION
return (__SENTRY__[SDK_VERSION] = __SENTRY__[SDK_VERSION] || {})
}
function getMainCarrier() { getSentryCarrier(GLOBAL_OBJ); return GLOBAL_OBJ }
function getClient() { getMainCarrier(); return undefined }
const SETUP_CLIENTS = new WeakMap()
const originalFunctionToString = Function.prototype.toString
Function.prototype.toString = function (...args) {
const originalFunction = this && this.__sentry_original__
const context = SETUP_CLIENTS.has(getClient()) && originalFunction !== undefined ? originalFunction : this
return originalFunctionToString.apply(context, args)
}
window.exposedFn = function exposedFn() {}
</script>
Observed output:
THREW SecurityError Failed to read a named property '__SENTRY__' from 'Window': Blocked a restricted frame with origin "http://127.0.0.1:8801" from accessing another frame.
The same read also throws in any other "realm outlives its document's origin" situation (Android Chrome same-process cross-origin navigations of the top frame show the same signature).
Expected Result
The patched Function.prototype.toString should never throw where the native implementation would not. Suggested fix — wrap the patch body, mirroring the defensive style already used around the patch installation:
Function.prototype.toString = function (this: WrappedFunction, ...args: any[]): string {
try {
const originalFunction = getOriginalFunction(this);
const context =
SETUP_CLIENTS.has(getClient() as Client) && originalFunction !== undefined ? originalFunction : this;
return originalFunctionToString.apply(context, args);
} catch {
// e.g. SecurityError reading __SENTRY__ off a WindowProxy whose browsing context
// was navigated cross-origin — fall back to native behavior
return originalFunctionToString.apply(this, args);
}
};
We've verified (against @sentry/core 10.34.0) that this keeps the unwrap behavior intact for live realms and degrades to exact native semantics otherwise. Happy to open a PR if this direction is acceptable.
Actual Result
SecurityError: Failed to read a named property '__SENTRY__' from 'Window': Blocked a frame with origin "…" from accessing a cross-origin frame. thrown out of Function.prototype.toString, reported as unhandled-rejection error events.
Is there an existing issue for this?
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/nuxt (the affected code is in @sentry/core, so all browser SDKs are affected)
SDK Version
10.34.0 — the affected code is unchanged on current master (
packages/core/src/integrations/functiontostring.ts)Framework Version
Nuxt 4 (not framework-specific)
Description
The
functionToStringIntegration(enabled by default) patchesFunction.prototype.toString. The patched function callsgetClient()→getCurrentScope()→getMainCarrier()→getSentryCarrier(GLOBAL_OBJ)on every.toString()invocation, which performs a property read ofGLOBAL_OBJ.__SENTRY__.GLOBAL_OBJisglobalThis, which in aWindowrealm is the WindowProxy of the realm's browsing context. If that browsing context is later navigated to a cross-origin document while code from the old realm can still be invoked (e.g. a still-alive parent window holding references into a same-origin child iframe whosesrcchanged to a cross-origin URL), the__SENTRY__read on the WindowProxy throws:(current Chrome wording:
Blocked a restricted frame with origin "…" from accessing another frame.)Because the patch is installed on
Function.prototype, the throw is triggered by any third-party script calling.toString(). In our production case the chain was: Microsoft Clarity's MutationObserver serialization → Tawk.to's core-jsFunction.prototype.toStringwrapper → Facebook Pixel's (fbevents.js) core-jsinspectSource→ Sentry's patchedtoString→SecurityError, surfacing asonunhandledrejectionnoise (24 events within ~1 second from a single page transition on a page embedding a third-party game iframe withsandbox="... allow-top-navigation").Production stack (origins redacted, resolved via source maps):
Note: the native
Function.prototype.toStringnever throws for these calls — the throw is introduced solely by the integration's internal carrier access, so the SDK converts harmless third-party introspection into uncatchable (for the app) SecurityError noise, which Sentry itself then reports.Steps to Reproduce
Self-contained repro (Chrome). Serve the two files below from
http://127.0.0.1:8801and also expose the same directory onhttp://127.0.0.1:8802(different port = cross-origin but same-site, any static server works):parent.html:child.html(stands in for any same-origin frame that initialized a Sentry browser SDK — inlined replica offunctiontostring.ts+carrier.tsso the repro needs no bundler; using the real SDK behaves identically):Observed output:
The same read also throws in any other "realm outlives its document's origin" situation (Android Chrome same-process cross-origin navigations of the top frame show the same signature).
Expected Result
The patched
Function.prototype.toStringshould never throw where the native implementation would not. Suggested fix — wrap the patch body, mirroring the defensive style already used around the patch installation:We've verified (against @sentry/core 10.34.0) that this keeps the unwrap behavior intact for live realms and degrades to exact native semantics otherwise. Happy to open a PR if this direction is acceptable.
Actual Result
SecurityError: Failed to read a named property '__SENTRY__' from 'Window': Blocked a frame with origin "…" from accessing a cross-origin frame.thrown out ofFunction.prototype.toString, reported as unhandled-rejection error events.