From 814aa1d9f14f04841a43711447799ce7dd5530df Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 15 Jul 2026 09:29:05 +0200 Subject: [PATCH 1/3] fix(core): Prevent functionToStringIntegration from throwing on cross-origin realms The patched `Function.prototype.toString` reads the Sentry carrier off `getClient()` on every invocation. When `this` (or the global object) is a `WindowProxy` whose browsing context was navigated cross-origin, that read throws a `SecurityError`. Since the patch lives on `Function.prototype`, any third-party `.toString()` call hits it, turning harmless introspection into uncatchable error noise. Wrap the patch body in a try/catch and fall back to the native `toString` so it never throws where the native impl would not. Fixes #21965 Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + .../core/src/integrations/functiontostring.ts | 15 ++++++--- .../lib/integrations/functiontostring.test.ts | 33 ++++++++++++++++++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1eea95f4872..e22df84f8a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +- fix(core): Prevent `functionToStringIntegration` from throwing on cross-origin `WindowProxy` realms Work in this release was contributed by @dobladov. Thank you for your contribution! diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index f8768037db6b..a4b2d10569bc 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -22,10 +22,17 @@ const _functionToStringIntegration = (() => { // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal) try { Function.prototype.toString = function (this: WrappedFunction, ...args: unknown[]): string { - const originalFunction = getOriginalFunction(this); - const context = - SETUP_CLIENTS.has(getClient() as Client) && originalFunction !== undefined ? originalFunction : this; - return originalFunctionToString.apply(context, args); + try { + const originalFunction = getOriginalFunction(this); + const context = + SETUP_CLIENTS.has(getClient() as Client) && originalFunction !== undefined ? originalFunction : this; + return originalFunctionToString.apply(context, args); + } catch { + // Reading the Sentry carrier off `getClient()` can throw a `SecurityError` when `this` (or the global + // object) is a `WindowProxy` whose browsing context was navigated cross-origin. The native + // `toString` never throws here, so fall back to it to avoid turning harmless introspection into noise. + return originalFunctionToString.apply(this, args); + } }; } catch { // ignore errors here, just don't patch this diff --git a/packages/core/test/lib/integrations/functiontostring.test.ts b/packages/core/test/lib/integrations/functiontostring.test.ts index 7c40de8b06f2..1e992cbd93cb 100644 --- a/packages/core/test/lib/integrations/functiontostring.test.ts +++ b/packages/core/test/lib/integrations/functiontostring.test.ts @@ -1,16 +1,27 @@ -import { afterAll, beforeEach, describe, expect, it } from 'vitest'; +import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import * as currentScopes from '../../../src/currentScopes'; import { fill, getClient, getCurrentScope, setCurrentClient } from '../../../src'; import { functionToStringIntegration } from '../../../src/integrations/functiontostring'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; +vi.mock('../../../src/currentScopes', async importOriginal => { + const actual = await importOriginal(); + return { ...actual, getClient: vi.fn(actual.getClient) }; +}); + describe('FunctionToString', () => { beforeEach(() => { const testClient = new TestClient(getDefaultTestClientOptions({})); setCurrentClient(testClient); }); + afterEach(() => { + vi.mocked(currentScopes.getClient).mockClear(); + }); + afterAll(() => { getCurrentScope().setClient(undefined); + vi.restoreAllMocks(); }); it('it works as expected', () => { @@ -55,4 +66,24 @@ describe('FunctionToString', () => { expect(foo.bar.toString()).not.toBe(originalFunction); }); + + it('falls back to native toString and does not throw when the carrier read throws', () => { + const foo = { + bar(wat: boolean): boolean { + return wat; + }, + }; + const nativeString = foo.bar.toString(); + + const fts = functionToStringIntegration(); + getClient()?.addIntegration(fts); + + // Simulate a `SecurityError` thrown while reading the Sentry carrier off a cross-origin `WindowProxy`. + vi.mocked(currentScopes.getClient).mockImplementation(() => { + throw new Error('SecurityError: Blocked a frame from accessing a cross-origin frame.'); + }); + + expect(() => foo.bar.toString()).not.toThrow(); + expect(foo.bar.toString()).toBe(nativeString); + }); }); From 5e3f3a39e96d279de72a9f6e25a812d4de24cf0e Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 15 Jul 2026 09:51:05 +0200 Subject: [PATCH 2/3] ref --- CHANGELOG.md | 1 - .../core/src/integrations/functiontostring.ts | 34 +++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e22df84f8a69..d1eea95f4872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ ## Unreleased - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott -- fix(core): Prevent `functionToStringIntegration` from throwing on cross-origin `WindowProxy` realms Work in this release was contributed by @dobladov. Thank you for your contribution! diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index a4b2d10569bc..c13d68040d00 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -2,10 +2,9 @@ import type { Client } from '../client'; import { getClient } from '../currentScopes'; import { defineIntegration } from '../integration'; import type { IntegrationFn } from '../types/integration'; -import type { WrappedFunction } from '../types/wrappedfunction'; import { getOriginalFunction } from '../utils/object'; -let originalFunctionToString: () => void; +let originalFunctionToString: () => string; const INTEGRATION_NAME = 'FunctionToString' as const; @@ -21,19 +20,24 @@ const _functionToStringIntegration = (() => { // intrinsics (like Function.prototype) might be immutable in some environments // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal) try { - Function.prototype.toString = function (this: WrappedFunction, ...args: unknown[]): string { - try { - const originalFunction = getOriginalFunction(this); - const context = - SETUP_CLIENTS.has(getClient() as Client) && originalFunction !== undefined ? originalFunction : this; - return originalFunctionToString.apply(context, args); - } catch { - // Reading the Sentry carrier off `getClient()` can throw a `SecurityError` when `this` (or the global - // object) is a `WindowProxy` whose browsing context was navigated cross-origin. The native - // `toString` never throws here, so fall back to it to avoid turning harmless introspection into noise. - return originalFunctionToString.apply(this, args); - } - }; + Function.prototype.toString = new Proxy(originalFunctionToString, { + apply(target, thisArg, args) { + const originalFunction = getOriginalFunction(thisArg); + let context = thisArg; + + try { + if (SETUP_CLIENTS.has(getClient()!) && originalFunction) { + context = originalFunction; + } + } catch { + // Reading the Sentry carrier off `getClient()` can throw a `SecurityError` when `this` (or the global + // object) is a `WindowProxy` whose browsing context was navigated cross-origin. The native + // `toString` never throws here, so fall back to it to avoid turning harmless introspection into noise. + } + + return Reflect.apply(target, context, args); + }, + }); } catch { // ignore errors here, just don't patch this } From 2f8b46e038a308b502df382df3b7c3ebfbc04c53 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 15 Jul 2026 09:52:32 +0200 Subject: [PATCH 3/3] small ref --- packages/core/src/integrations/functiontostring.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index c13d68040d00..86844843664f 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -4,8 +4,6 @@ import { defineIntegration } from '../integration'; import type { IntegrationFn } from '../types/integration'; import { getOriginalFunction } from '../utils/object'; -let originalFunctionToString: () => string; - const INTEGRATION_NAME = 'FunctionToString' as const; const SETUP_CLIENTS = new WeakMap(); @@ -15,7 +13,7 @@ const _functionToStringIntegration = (() => { name: INTEGRATION_NAME, setupOnce() { // eslint-disable-next-line @typescript-eslint/unbound-method - originalFunctionToString = Function.prototype.toString; + const originalFunctionToString = Function.prototype.toString; // intrinsics (like Function.prototype) might be immutable in some environments // e.g. Node with --frozen-intrinsics, XS (an embedded JavaScript engine) or SES (a JavaScript proposal)