Skip to content

Five web builtins return undefined for a prototype-method value-read, and URL.prototype.toString silently resolves to the wrong function #10808

Description

@proggeramlug

Reading a prototype method as a value returns undefined for five web builtins, and returns the wrong function for a sixth. Confirmed empirically, not inferred.

These are the remaining members of the deliberate #10555 group in crates/perry-runtime/src/object/global_this/proto_methods.rs, whose arms install only a Symbol.toStringTag descriptor. The seventh member, URLSearchParams, is fixed by #10759 / PR #10807; this issue covers the other six, audited while fixing it.

Results

builtin value-read of a prototype method verdict
AbortController typeof undefined same defect
AbortSignal typeof undefined same defect
CustomEvent typeof undefined same defect
Event typeof undefined same defect
EventTarget typeof undefined same defect
URL — .prototype.toJSON typeof undefined same defect
URL — .prototype.toString resolves to a callable — the wrong one worse; see below

Why URL.prototype.toString is the more dangerous case

The other failures are loud: you read a method, get undefined, and the next .call() throws. URL.prototype.toString instead resolves to the inherited generic Object.prototype.toString, which is callable and returns "[object URL]" rather than the URL string.

So it does not throw. It silently returns a plausible-looking wrong value. Any code that reads toString as a value — a serializer, a logger, a template helper, anything doing String(x) through an indirection — gets "[object URL]" where Node gives the href. That is a correctness bug that no crash will surface.

Mechanism

The #10555 group's arms were written on the premise that these builtins' methods are reached by type-directed static dispatch or handle tables, so reified prototype closures were unnecessary — each arm installs only the toStringTag descriptor for reflection. That premise holds for x.method() and has no answer for a method read as a value: the read finds nothing installed and falls through to undefined, or in URL's case to an inherited generic.

This is a gap in a deliberate design rather than a set of forgotten entries, which matters for the fix: the question is what a statically-dispatched builtin should do when one of its methods is read as a value, not why six entries were missed.

Note on scope

Filed as an audit result, deliberately not fixed alongside #10759 — that PR fixes one member and this issue records the other six with evidence. Whoever takes this should decide whether the right remedy is six more arms or a general answer for the group, and URL.prototype.toString should be handled first: it is the only one that fails silently.

Metadata

Metadata

Assignees

No one assigned

    Labels

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions