Block Third-Party Scripts with a Few Lines of Javascript

How to prevent third-party scripts from collecting data, sending analytics, and more without user consent.

Disclaimer: This article is about blocking scripts that are known to be safe, like analytics — meaning scripts from third party services that you include on your own. To block malicious unwanted third-party scripts or prevent Cross Site Scripting attacks you should always consider using other solutions, like adding a Content-Security-Policy header!
Asking for user consent — without tracking ahead of time.

The context

Here at Snips we take privacy very seriously. Now that the GDPR is in effect, we have decided that our owned websites should never collect analytics on visiting users — unless they opt-in voluntarily.

Unfortunately, blocking third-party analytics can be a bit tricky. The problem is that these services are built to start immediately, and that they rely on small minified code snippets and script tags. You can try to modify the minified code yourself, but it gets a bit messy and hard to maintain, especially if you use several libraries.

So in order to handle this better — automatically — we created a small open source library called Yett (interesting meaning) that takes care of blocking the execution of analytics scripts. The library also allows you to later unblock these scripts, once a user has opted in.

To block scripts automatically might seem trivial — but it’s actually not that easy to block inline script tags!

So while digging into the subject, we found some technical subtleties that we thought would be nice to share with the community 😉.

The goal

Here are the requirements:

  • The code must be loaded and executed synchronously, before any other script;
  • At this point you have no clue how the final html will look, since the document has not yet been fully loaded;
  • The blocked scripts must not be altered in any way (the goal is to prevent them from executing without touching their own code);
  • We should be able to (eventually) unblock the scripts programmatically later on.

Below we list a few techniques that will actually work.

Groundwork — Using a MutationObserver to observe script elements insertion

MutationObserver comes in handy in this case, since you can register the observer on the document element ahead of time and be notified whenever DOM nodes get inserted, especially script nodes.

MutationObserver

This alone won’t be sufficient of course, but it will be the first step towards our goal.

Preventing a <script> tag from executing

Changing the script type attribute

Changing the type attribute to something other than application/javascript before the script payload is retrieved actually allows us to completely stop the script from executing. This does the magic in Chrome, Safari, IE and Edge .

In the following snippets we arbitrarily pickedjavascript/blocked, but it could be any type you want.

Note that the script will still be downloaded.

Changing the type blocks execution

Unfortunately, this won’t work for Firefox.

Using the beforescriptexecute listener

The beforescriptexecute event is marked as deprecated, but is still supported by Firefox as of version 4.

Unfortunately, even if this is considered bad practice we don’t have much choice other than to call it.

The good news is that it works perfectly 😄.

Extra Firefox event — beforescriptexecute

Bonus - mark script tags with a custom type manually

Adding this extra attribute also allows the rest of the code to unblock these scripts automatically later on, and most importantly, prevents the script from downloading on Chrome and Firefox.

Preventing the execution of dynamically inserted script tags

As you certainly know, scripts can also be created and appended to the DOM programmatically. The MutationObserver will catch these, but changing the type or adding the events will not work in these cases.

Fortunately, there are solutions.

Monkey patching document.createElement

If we cannot prevent the execution after these scripts have been inserted, we can still return a slightly modified script instance whenever the script elements are created.

Adding ‘src’ and ‘type’ getters & setters on HTMLScriptElement

The last step is to ensure that the type is set to javascript/blocked when the third-party code changes the source of the HTMLScriptElement to something that we want to block.

Re-enabling these scripts afterwards

Of course, after blocking these scripts you may want to run them later on — for instance, when a user has voluntarily consented to opt-in.

The process is not detailed here, but the general idea is the following:

  • Backup every script node the MutationObserver catches having type javascript/blocked.
  • Then, expose a function that puts back those nodes into the <head> when called with the application/javascript type restored.
  • Realistically, you would display a banner on your website with a button that the user can click to opt-in. Just call the function on click to un-block all the scripts.

For more details on the implementation, you can check out the Yett repository.

Wrapping it up

As you can see, it is absolutely feasible from a developer standpoint to prevent analytics from gathering data that should stay private — even if you don’t have direct access to the third-party code.

If you want to follow in our steps and truly respect the privacy of users browsing your websites, we made Yett, an open source library hosted on Github using all the code above in a convenient way. Collaboration and feedback are more than welcome!

I humbly hope that some of the tricks described here were useful to you, and thanks for reading this article! 👏

Thanks to those who reviewed this article, especially Jonas Ohlsson Aden for providing me with the idea and the opportunity to realize it!

If you liked this article and want to support Snips, please share it!

Follow us on Twitter 👉 snips.

If you want to work on AI + Privacy, check out out jobs page.