Saturday, 4 August 2018

Chrome Extension - Stop Stray CORS requests

If I type in a web address such as the British Newspaper theguardian.com then I might naively expect all resources to be delivered from that domain name. But the modern web page has all sorts of cross network calls to web analytics and constant delivery networks (CDNs). Web analytics are a fact of modern life; they help firms reach their customers with better targeted adverts. No more scatter-gun adverts, we can now have pertinent products pitched to us individually. This helps allocative efficiency which is a good thing.

If web analytics were restricted to economic transactions then I'm confident there would be no problem. Sadly, some web analytics have been put to political use which is naughty. How does one opt out of a naughty analytics provider?

Use Hosts file to Block WebAnalytics

In the past I have altered my computer's hosts file at C:\Windows\System32\drivers\etc\hosts so a name resolves to loopback interface 127.0.0.1 meaning data destined for an address never leaves your computer. But this is like a sledgehammer to crack a nut.

Chrome Extension CORS filter

The precise technical term for cross network calls is Cross-origin resource sharing (CORS). CORS requires an exchange and interaction between (browser) client and (web) server.

On the server side, by default web servers disallow CORS and programmers have to actively change their code to permit CORS requests and actually even on this blog you'll find an example enabling CORS.

But the loopholes opened on the server side can be closed on the client side.

On the client side, we can write a Chrome Extension to disable CORS requests. I have given Chrome Extension examples before on this blog. This post's extension is slightly different in that it runs as a background script instead of a context script.

So in our example we are going block requests to Facebook domains because at the time of writing they are 'on the naughty step', being criticized by a British Parliamentary Oversight committee. U.S. Congressional oversight committees' reports are currently pending. But the code could be tweaked to apply to all manner of naughtiness.

manifest.json

Here is the manifest.json file. Create a directory, I called mine N:\CORS Chrome Extension\ and copy this there. This is a standard manifest file, it asks for permissions to block web requests.

  {
    "name": "Cross Origin Filter",
    "version": "0.0.6",
    "description": "Helps you stop stray CORS requests.",
    "permissions": [
      "webRequest",
      "webRequestBlocking",
      "*://*/*"
    ],
    "background": {
      "scripts": [
        "bgp.js"
      ],
      "persistent": true
    },
    "manifest_version": 2
  }

bgp.js

Below is the background page script, I called mine bgp.js (it must match entry in manifest.json) and saved this again in folder N:\CORS Chrome Extension\

The code adds a listener to the event onBeforeSendHeaders but whilst other events are available we need to scan through the request headers looking for the Referer so we can establish if the request is cross domain.

The code parses URLs using the URL object ;we only need the hostnames e.g. www.theguardian.com, www.facebook.com so we throw away the parameter string. Once we have the hostnames we can compare them against a list of domain names to block. There are two list matching sections, one compares exactly and the other compares the tail of the domain name.

If a domain matches one we want to block then we create a blockingResponse object and set its cancel property to true. This cancels the webrequest. We print to the console when we've blocked a domain.

chrome.webRequest.onBeforeSendHeaders.addListener(function (details) {
  
  var myVars = {};
  myVars.urlsPresent = false;

  try {
    myVars.requestURL = (new URL(details.url)).hostname;
    for (var i = 0, l = details.requestHeaders.length; i < l; ++i) {
      if (details.requestHeaders[i].name == 'Referer') {
        referer = details.requestHeaders[i].value;
        myVars.refererURL = (new URL(referer)).hostname;
        myVars.crossOrigin = (myVars.refererURL !== myVars.requestURL);
        myVars.urlsPresent = true;
        break;
      }
    }
  }
  catch (err) {
    console.log("Error whilst determining URLs, err.message: " + err.message);
  }

  if (myVars.urlsPresent === true) {
    try {
      myVars.block = false;

      if (myVars.crossOrigin === true) {

        {
          var aBlockCrossOriginEndsWithList = [".fbcdn.net"];
          for (var i = 0, l = aBlockCrossOriginEndsWithList.length; i < l; ++i) {
            if (myVars.requestURL.endsWith( aBlockCrossOriginEndsWithList[i])) {
              //debugger;
              myVars.block = true;
              break;
            }
          }
        }

        {
          var aBlockCrossOriginList = ["connect.facebook.net", "www.facebook.com"];
          for (var i = 0, l = aBlockCrossOriginList.length; i < l; ++i) {
            if (aBlockCrossOriginList[i] == myVars.requestURL) {
              myVars.block = true;
            }
          }
        }


      }
    }
    catch (err) {
      console.log("Error whilst determining blocking, err.message: " + err.message);
    }
  }

  if (myVars.block === true) {
    try {
      console.log("CORS Filter v.0.0.6, blocking " + myVars.requestURL + " from " + myVars.refererURL);
      //debugger;
      blockingResponse = {};
      blockingResponse.cancel = true
      return blockingResponse;
    }
    catch (err) {
      console.log("Error whilst returning blocking response, err.message: " + err.message);
    }
  }

}, { urls: ["*://*/*"] }, ['requestHeaders', 'blocking']);

Here is the console output showing how a web page from the Guardian is having cross domain calls to Facebook blocked.