I remember use of this being discouraged multiple decades ago, and am astonished to find that it’s still around.
I wonder if anyone who runs a search engine has ever done a census of sites that still actually depend on ancient things like this, and quantified the effect of just dropping support – I have to imagine that a huge percentage of things that used to use this back in the day have just silently linkrotted their way off the internet since then.
The crome web platform security team is tracking this as an anti-pattern they would like to deprecate.
See “DOM clobbered variable accessed” in the “Bad Markup” section at https://deprecate.it/ - access is currently at 10% of page views, though it’s unclear and hard to measure if missing access would actually break anything.
It might not return what you think. According to the spec, when there are multiple instances of the same named element in the DOM — say, two instances of div class=“cool” — the browser should return an HTMLCollection with an array of the instances. Firefox, however, only returns the first instance. Then again, the spec says we ought to use one instance of an id in an element’s tree anyway
What if you create an iframe and then have the iframe send back the clean function to test against? I guess malicious code could trap iframe creation, but it would be pretty complicated. Any better techniques from the battle against spam ad clicks?
Even if you could send natives across that boundary, they’re not the same object. Each frame has its own globals, so monkey-patching String in one doesn’t mess with the others.
I assumed they were saying “get the Function.prototype.toString function from the other frame”, as that’s the only thing that makes sense because of what you just said :)
Or just run the code you don’t want patched in an iframe with no 3rd-party scripts and have a stub in the parent using something like Comlink to expose it.
Well, there’s some truly bizarre stuff out there like https://partytown.builder.io that runs a service worker that intercepts certain pseudo-URLs and then uses non-async XHR calls to send non-async third party scripts off asynchronously into a service worker. But yeah, probably it has some limit somewhere.
You’re right, I didn’t explain it well enough. To add some more context:
Cross-origin, you’ll have the serialization issue.
On same-origin, you can can pass references around, but comparing them (with a referential equality check) would still return a falsy value because they’re pointing at two different functions — but feel free to correct me if I’m missing something.
I can’t run it now, but what I mean is: in your example, in the “gotFetch” callback, a “f === window.fetch” statement would return “false”, right?
On firefox at least, (f === window.fetch) === false. Though you could use this trick to get access to an unaltered version of a function, which is pretty neat.
I was gonna say that you could still not fully trust that function because third-parties could still override the iframing API, and that sometimes there might be limitations in using iframes (e.g. CSP)…
But you know what? When the goal is “using” a clean native function and not just checking if it’s monkey patched, in most cases this solution does seem more practical than the one I mentioned in the article. I’ll update it, thank you all for the discussion.
Before I forget, I just noticed that another limitation of this approach is that you won’t be able to use it for APIs that interact with the document (such as document.createElement).
I’d recorded a short video for my oh so niche-y niche side project Omnia Atari that runs a Python script to download all the Atari 8 bit binaries from the Internet Archive and makes them accessible to folks running Fujinet wifi adapters on their 8 bit.
I wasn’t sure how to prepare it to be added to my project page. This is perfect!
I remember use of this being discouraged multiple decades ago, and am astonished to find that it’s still around.
I wonder if anyone who runs a search engine has ever done a census of sites that still actually depend on ancient things like this, and quantified the effect of just dropping support – I have to imagine that a huge percentage of things that used to use this back in the day have just silently linkrotted their way off the internet since then.
The crome web platform security team is tracking this as an anti-pattern they would like to deprecate.
See “DOM clobbered variable accessed” in the “Bad Markup” section at https://deprecate.it/ - access is currently at 10% of page views, though it’s unclear and hard to measure if missing access would actually break anything.
TIL about https://deprecate.it/, thanks for sharing.
It’s actually worse. A named element can overwrite (clobber) a native property.
E.g., two radio buttons with name
childNodes
will overwrite theform.childNodes
and an iteration will only see the radio buttons. No other childnodes.This attack is called DOM Clobbering.
You’re right. I wanted to also write a DOM clobbering specific follow-up and forgot to mention it in the article.
Someone is confused between class and id…
A minor detail :D
Ouch, yeah, haha. Let me see if I can fix that.
What if you create an iframe and then have the iframe send back the clean function to test against? I guess malicious code could trap iframe creation, but it would be pretty complicated. Any better techniques from the battle against spam ad clicks?
Even if you could send natives across that boundary, they’re not the same object. Each frame has its own globals, so monkey-patching String in one doesn’t mess with the others.
I assumed they were saying “get the Function.prototype.toString function from the other frame”, as that’s the only thing that makes sense because of what you just said :)
Or just run the code you don’t want patched in an iframe with no 3rd-party scripts and have a stub in the parent using something like Comlink to expose it.
True. But that wouldn’t work for APIs that interact with the document (such as
document.createElement
).Well, there’s some truly bizarre stuff out there like https://partytown.builder.io that runs a service worker that intercepts certain pseudo-URLs and then uses non-async XHR calls to send non-async third party scripts off asynchronously into a service worker. But yeah, probably it has some limit somewhere.
Yup, but even with Partytown or with atomics you would have to access the main thread’s DOM API. I get your point though.
If you don’t want elements to be tampered with, they need to be in an iframe anyway.
You wouldn’t be able to compare the function in the in the iframe with the one outside of it because you’d need to serialise it to pass it around.
You can pass functions between frames in the same origin. This snippet creates a new iframe which passes its
window.fetch
to the parent:You’re right, I didn’t explain it well enough. To add some more context:
I can’t run it now, but what I mean is: in your example, in the “gotFetch” callback, a “f === window.fetch” statement would return “false”, right?
On firefox at least,
(f === window.fetch) === false
. Though you could use this trick to get access to an unaltered version of a function, which is pretty neat.I was gonna say that you could still not fully trust that function because third-parties could still override the iframing API, and that sometimes there might be limitations in using iframes (e.g. CSP)… But you know what? When the goal is “using” a clean native function and not just checking if it’s monkey patched, in most cases this solution does seem more practical than the one I mentioned in the article. I’ll update it, thank you all for the discussion.
Before I forget, I just noticed that another limitation of this approach is that you won’t be able to use it for APIs that interact with the document (such as
document.createElement
).Yeah, I thought that after your comment.
Thanks for this. Really great advice!
I’d recorded a short video for my oh so niche-y niche side project Omnia Atari that runs a Python script to download all the Atari 8 bit binaries from the Internet Archive and makes them accessible to folks running Fujinet wifi adapters on their 8 bit.
I wasn’t sure how to prepare it to be added to my project page. This is perfect!
Glad it helps! Let me know how it goes :)