If you have a website, can you somehow find out if visitors are modifying your site with javascript userscripts?
In short: EEEEEEK! Don't do it! Rather, decide what needs to be guarded, and guard that. Avoid polling (periodical checking) at all costs. Especially, avoid periodical heavy checks of anything.
Not every change is possible to track. Most changes are just extremely hard to track, since there are so many things that could change.
Changes to the DOM (new nodes, removed nodes, changed attributes) can be detected. The other answer suggests checking innerHTML
periodically, but it's better to use mutation observers (supported by Firefox, Chrome) or the older mutation events (DOMSubtreeModified
et al.) (support varies by event) instead.
Changes to standard methods cannot be reliably detected, except by comparing every single method and property manually (eeeek). This includes the need to reference tons of objects including, say, Array.prototype.splice
(and Array
and Array.prototype
as well, of course), and run a heavy script periodically. However, this is not what a userscript typically does.
The state of an input is a property, not an attribute. This means that the document HTML won't change. If the state is changed by a script, the change
event won't fire either. Again, the only solution is to poll every single input manually (eeek).
There is no reliable way to detect if an event handler has been attached. For starters, you would need to guard the onX
attributes (paragraph #2), detect any call to addEventListener
(ek) (without tripping the paragraph #2 check), detect any calls to the respective methods by your library (jQuery.bind
and several others).
One thing that plays in your favor, and possibly the only one: user scripts run on page load (never sooner), so you have plenty of time to prepare your defenses. not even that plays in your favor (thanks Brock Adams for noting and the link)
You can detect a standard method has been called by replacing it with your own (ek). There are many methods that you would need to instrument this way (eek), some by the browser, some by your framework. The fact that IE (and even firefox can be instructed to, thanks @Brock) won't let you touch the prototypes of the DOM classes adds another "e" or two to the "eek". The fact that some methods can only be obtained via a method call (return value, callback arguments) adds another "e" or two, for a total of "eeeek". The idea of crawling across the entirety of window
will be foiled by security exceptions and uncatchable security exceptions. That is, unless you don't use iFrames and you are not within an iFrame.
Even if you detect every method call, DOM can be changed by writing to innerHTML
. Firefox and Chrome support Mutation Observers, so you can use these.
Even if you detect every method call to a pre-existing method and listen to mutations, most properties are reflected by neither, so you need to watch all properties of every object as well. Pray someone does not add a non-enumerable property with a key you would never guess. Incidentally, this will catch DOM mutations as well. In ES6, it will be possible to observe an object's property set. I'm not sure if you can attach a setter to an existing object property in ES5 (while adhering to ES3 syntax). Polling every property is eeeek.
Of course, you should allow your own scripts to do some changes. The work flow would be to set a flag (not accessible from the global scope!) "I'm legit", do your job, and clear the flag - remember to flank all your callbacks as well. The method observers will then check the flag is set. The property watchdogs will have a harder time detecting if a change is valid, but they could be notified from the script of every legit change (manually; again make sure the userscripts cannot see that notification stream). Eeek.
There's an entirely different problem that I didn't realise at first: Userscripts run at page load, but they can create an iFrame as well. It's not entirely inconcievable (but still unlikely now) that a userscript would: 1) detect your script blocker, 2) nuke the page from the orbit (you can't prevent document.body.innerHTML =
, at least not without heavily tampering with document.body
), 3) insert a single iframe with the original URL (prevent double loads server-side?) and 4) have a plenty of time to act on that empty iframe before your protection is even loaded.
Also, see the duplicate found by Brock Adams, which shows several other checks that I didn't think of that should be done.