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><iframeid="f" src="/child.html"></iframe><script>constf=document.getElementById('f')letdone=falsef.onload=()=>{if(done)returndone=trueconstchildFn=f.contentWindow.exposedFn// keep a reference into the child realmsetTimeout(()=>{f.src='http://127.0.0.1:8802/parent.html'},300)// navigate iframe cross-originsetTimeout(()=>{try{childFn.toString()// resolves to the child realm's patched Function.prototype.toStringconsole.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>constSDK_VERSION='10.34.0'constGLOBAL_OBJ=globalThisfunctiongetSentryCarrier(carrier){const__SENTRY__=(carrier.__SENTRY__=carrier.__SENTRY__||{})__SENTRY__.version=__SENTRY__.version||SDK_VERSIONreturn(__SENTRY__[SDK_VERSION]=__SENTRY__[SDK_VERSION]||{})}functiongetMainCarrier(){getSentryCarrier(GLOBAL_OBJ);returnGLOBAL_OBJ}functiongetClient(){getMainCarrier();returnundefined}constSETUP_CLIENTS=newWeakMap()constoriginalFunctionToString=Function.prototype.toStringFunction.prototype.toString=function(...args){constoriginalFunction=this&&this.__sentry_original__constcontext=SETUP_CLIENTS.has(getClient())&&originalFunction!==undefined ? originalFunction : thisreturnoriginalFunctionToString.apply(context,args)}window.exposedFn=functionexposedFn(){}</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{constoriginalFunction=getOriginalFunction(this);constcontext=SETUP_CLIENTS.has(getClient()asClient)&&originalFunction!==undefined ? originalFunction : this;returnoriginalFunctionToString.apply(context,args);}catch{// e.g. SecurityError reading __SENTRY__ off a WindowProxy whose browsing context// was navigated cross-origin — fall back to native behaviorreturnoriginalFunctionToString.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.