@@toStringTag spoofing for null and undefined

Jordan Harband ljharb at gmail.com
Tue Jan 20 11:38:59 PST 2015

"X is bad" is, quite frankly, mostly irrelevant here. When X is bad in JS,
then TC39 seems to have gone with primarily one of two choices: use strict
mode to remove it, or, provide a better option Y so that developers *want*
to move away from X and start using Y. In this case, the value of type
checking doesn't matter - people do it, and so it must be considered.

There are two primary usages of Object.prototype.toString in my eyes:
 - constructs like `Object.prototype.toString.call(new String('foo')) ===
Object.prototype.toString.call('foo')` (or using that to throw an explicit
error "don't use object forms of primitives") which ensures that people
using my code won't fall into the very common footgun (thanks, Java) of
using boxed primitives. (The same issue will apply to Symbols in ES6, but
at least I can do `typeof Symbol.prototype.toString.call(value) ===
 - Testing of values from other realms (namely iframes).
`Object.prototype.toString.call(value) === '[object Array]'` is a
remarkably consistently written return value of an `Array.isArray`
polyfill, since it works no matter how somebody has monkeyed with any
global Array object.

Certainly it is trivial to construct a malicious array, for example, and
pass that around breaking all sorts of things. The goal, in my opinion, of
Object.prototype.toString checking is *not* security - it's avoiding common
developer hazards. In other words, I want my code to fail fast when the
developer unintentionally passes me the wrong thing - something that I
think we can agree happens often.

1) configurability of @@toStringTag
It appears that TC39 considers it important to not break existing JS code
with spec changes. Thus, `Object.prototype.toString.call(foo)`, for any ES5
value "foo", must always and forever return the same value that it returned
in ES5 - otherwise, existing code may follow different code paths in ES5
versus ES6, which is a hazard. This leads me to the belief that
@@toStringTag values on ES5 builtins should never be changeable.

Some have replied to this, "if you don't run first, all bets are off -
freeze it if you want it". Fair point! If you don't run first and keep a
reference to Object.prototype.toString, you're screwed anyways. However, in
ES5, if I *do* run first, I have 100% opt-in protection against somebody
breaking things I care about. Essentially all polyfilled `Array.isArray`
code on the web could break if it is possible to redefine
`Array.prototype[@@toStringTag]` (in any realm, not just the one I start
in), which is currently possible by default in ES6.

I believe that all built-in objects, in all realms, should have a
nonconfigurable @@toStringTag for this reason.

2) Since people *are* doing this type checking, if I make an object that
defines its @@toStringTag value to return "Array", I will break code that
does this. (Hence the protections in
I've filed https://bugs.ecmascript.org/show_bug.cgi?id=3506 to discuss the
missing values (namely Math, JSON, and Object, and Null, and Undefined have
been mentioned as well).

Currently, the spec handles this by including a whitelist of values, and
specifying that any value not in this list receives a "~" prefix. (The
value of the prefix itself is irrelevant, let's please not bikeshed it).

My proposal is that rather than maintain a whitelist, and have that added
complexity, that ES6 specifies that any user-defined @@toStringTag value
will *always* and unconditionally have a prefix applied.


I believe that either of these proposals by themselves will be a win - but
with both together, "nominal type checking" / branding code will continue
to work, and there will be no hazards or footguns by default.

For those who dislike this kind of code, I challenge you and the committee
to finalize and publish a better approach for answering the question "does
this behave like an array" or "does this behave like a map" that work
cross-realm (besides exhaustive duck-typing and/or feature detection),
rather than attempting to simply oppress what many consider to be a valid
approach, and a functional and existing one.

This is my first post to the list, so please be gentle :-) I will take any
feedback provided!

On Tue, Jan 20, 2015 at 11:26 AM, Mark S. Miller <erights at google.com> wrote:

> On Tue, Jan 20, 2015 at 10:05 AM, Brendan Eich <brendan at mozilla.org>
> wrote:
>> Domenic Denicola wrote:
>>> From: Brendan Eich [mailto:brendan at mozilla.org]
>>>  >  Can we get a leg up, rather than wait for a f2f? This thread seems
>>>> fine for further discussion and simplifying proposals.
>>> Well, to be clear, I'd prefer we not change anything at all. It's too
>>> late to be tweaking something that's been set in the spec for a very long
>>> time now.
>>> But it appears that we have some relitigation on this particular topic
>>> [on the agenda anyway][1]:
>> Toxic terms like "relitigation", heard it from from RealAlexRussell and
>> it sucked then too, are out of line -- we are not lawyers. Also, new TC39
>> rep Jordan Harband of Twitter raised this at the last meeting, asked Allen
>> about working on it, got a green light.
>>  >    @@toStringTag (rationales) (Jordan Harband)
>>>> >
>>>> >  - Missing unspoofable builtin values (Math, JSON, Object): spec bug
>>>> >  - Should built-in @@toStringTag values have { configurable: false }?
>>>> >  - Should Object.prototype.toString add a prefix to all non-built-in
>>>> @@toStringTag values?
>>> It seems the "protections" in the spec so far have given some the
>>> misleading impression that we want to encourage O.p.toString as an
>>> unspoofable [[Class]] test. (Not that [[Class]] even exists anymore!) But
>>> as Allen points out, that's not the idea at all. And I'm loathe to
>>> perpetuate that usage of them---or even the impression that they should be
>>> used that way. (Nominal-typing bad!
>> That "X-typing bad!" line is not helpful. (What is this, a sports/beer
>> commercial?)
>> Even structural typing fans such as Mark Miller have noted in their
>> research results the benefits of nominal types for certain use-cases.
>> Sometimes you need to know your implementation. This is the exception to
>> the rule, but it's not always and everywhere "bad!".
> Yes, but I would put it more positively. Nominal and Structural typing are
> about different things. Neither subsume the other. Nominal types are often
> misunderstood to be about the string-name of types or some equally
> non-generative notion of type, so I prefer to use the brand terminology.
> The classic Types are Not Sets <
> http://dl.acm.org/citation.cfm?doid=512927.512938>, IIRC, uses the term
> "trademarking" instead with the same meaning. If anyone has a link to the
> actual pdf, please post.
>>    Especially in ES6/ES7/etc. where proxies/value types/etc. give us the
>>> ability to perfectly emulate the characteristics of those types!)
>> Yes, we want to complete the MOP so nominal types are equivalent to
>> branded structural types, a la Modula 3, and per David Ungar's position
>> articulated many times over the years (I heard David say it to Tom Van
>> Cutsem in person at SPLASH 2011, re: Proxies not interceding fully for all
>> types). But we aren't there yet.
> I don't understand this paragraph. Are you saying that you want a proxy to
> be able to intercept and emulate the brand check, while somehow preserving
> the integrity implied by the brand check?
>> Anyway, this has little to do with getting the details of toStringTag in
>> the best shape we can for ES6. Perhaps Jordan will weigh in, but in any
>> case, I found his links and questions from the agenda helpful -- others may
>> too. Here they are:
>> 1. https://github.com/ljharb/agendas/wiki/January-TC39-@@
>> toStringTag-discussion
>> 2. https://bugs.ecmascript.org/show_bug.cgi?id=3506
>> 3. Should built-in `@@toStringTag` values have `{ configurable: false }`?
>> 4. Should `Object.prototype.toString` add a prefix to *all* non-built-in
>> `@@toStringTag` values?
>> /be
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/listinfo/es-discuss
> --
>     Cheers,
>     --MarkM
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20150120/87e2b62e/attachment-0001.html>

More information about the es-discuss mailing list