Skip to content

FunctionToString overflows on cyclic Proxy prototype chains #25963

Open

Description

@sentry-junior

The patched Function.prototype.toString from functionToStringIntegration throws RangeError: Maximum call stack size exceeded for a function whose prototype chain loops back through a Proxy. Native toString returns normally for the same function. Anti-tamper and fingerprinting scripts use this kind of probe to detect patched natives.

Reported by Sergio Lombana from a customer on @sentry/react@10.75.3 with Castle bot detection loaded. Their event has a short stack: the Sentry toString wrapper is the top frame and the frames below it are Castle's.

Root cause (high confidence for the repro, unverified for the customer's exact trigger)

The wrapper calls getOriginalFunction(this), which reads this.__sentry_original__ (packages/core/src/utils/object.ts). For a function without that own property, the read walks the prototype chain. If the chain loops back through a Proxy, the walk never ends and overflows inside the wrapper frame. Native toString never reads properties from this, so it doesn't overflow.

This still happens on develop after #25921 / #25922. Those PRs fixed the apply re-entry and huge-argument-list overflows, not this property lookup.

Reproduction

const orig = Function.prototype.toString;
const callOrig = Function.prototype.call.bind(orig);

function f() {}
Object.setPrototypeOf(f, Object.create(new Proxy(f, {})));

Function.prototype.toString.call(f); // native: 'function f() {}'

// same shape as the current wrapper on develop
Function.prototype.toString = function () {
  const o = this.__sentry_original__;
  return callOrig(o ?? this);
};

Function.prototype.toString.call(f); // RangeError: Maximum call stack size exceeded

Unknowns

  • Someone else reproduced this artificial case but couldn't trigger the error with the same Castle and SDK versions. So it's not confirmed that this is the probe Castle runs in the customer's environment.

Workaround

  • Filter the RangeError via ignoreErrors / beforeSend. Removing functionToStringIntegration can make Sentry-wrapped functions look tampered to anti-bot scripts, so it isn't recommended here.

Related: #21965, #22515, #25921, #25922

via Sergio.

--

View Session [Sentry]

Activity

  1. linear-code commented on Oct 30, 2026

    @linear-code
  2. thelastbackspace commented on Nov 4, 2026

    @thelastbackspace

    I ran into this too and have a small fix ready: the toString wrapper reads __sentry_original__ with a normal property lookup, which walks the looping Proxy prototype chain. Checking it's an own property first (which is how markFunctionWrapped sets it) and moving the read inside the existing try/catch fixes it, with a regression test using your repro. Happy to open a PR if that works for you.

  3. adityareddy-dev commented on Nov 4, 2026

    @adityareddy-dev
    Contributor

    I'd like to take this on. I'd guard the original-function lookup in the toString wrapper and fall back to the bound native call when that lookup throws. That would avoid adding a live .call lookup or changing how inherited wrappers are resolved. Is that approach welcome?

  4. self-assigned this
    on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions