Let AI write emails and messages for you 🔥

Block API requests via chrome extension and demystifying permission warnings - Manifest v2 and v3

Gourav Goyal

Gourav Goyal

May 14, 2022

You can conditionally block specific API requests using a chrome extension. I'll be using new chrome extension API declarativeNetRequest instead of deprecated webRequest.

declarativeNetRequest vs webRequest

There are 2 APIs one can use to block/unblock/redirect/modify header of API requests:

declarativeNetRequest

  • available for Chrome (both Manifest v2 and v3)
  • not available for Firefox Manifest v2 but will be for Manifest v3 (track progress)

webRequest

  • available for Chrome Manifest v2 but deprecated in Manifest v3
  • available for Firefox Manifest v2 and will be supported in Manifest v3 as well (reference)

more detailed comparison:  https://developer.chrome.com/docs/extensions/reference/declarativeNetRequest/#comparison-with-the-webrequest-api

As you can see, declarativeNetRequest seems to be more future-proof when developing browser extensions for both browsers (Chrome and Firefox)

Let's jump right into the implementation!

Dynamically block/unblock URLs

It means you can conditionally block/unblock any API request at anytime

in manifest.json file, add declarativeNetRequest permission:

{
  "permissions": [
    "*://example.com/*", // mention domain(s) where declarativeNetRequest should run
    "declarativeNetRequest"
  ]
}

Add below to background script for Manifest v2 extension, or to service worker for Manifest v3 extension:

Below snippet blocks API requests that start with adservice.google.com/adsid/google/ui on domain: example.com

const adblockRuleID = 2; // give any id to indetify the rule but must be greater than 1
chrome.declarativeNetRequest.updateDynamicRules(
  {
    addRules: [
      {
        action: {
          type: "block",
        },
        condition: {
          urlFilter: "adservice.google.com/adsid/google/ui", // block URLs that starts with this
          domains: ["example.com"], // on this domain
        },
        id: adblockRuleID,
        priority: 1,
      },
    ],
    removeRuleIds: [adblockRuleID], // this removes old rule if any
  },
  () => {
    console.log("block rule added");
  }
);

To unblock a rule:

chrome.declarativeNetRequest.updateDynamicRules(
  {
    removeRuleIds: [adblockRuleID], //invalid and non-existing rules will be ignored
  },
  () => {
    console.log("rule removed");
  }
);

Few notes:

  • you can at most create 5000 such rules (reference). An average adblocker extension needs 70,000+ rules to block most ads effectively. A default installation of the uBlock Origin extension comes with 100,000 rules. So yes, this will handicap adblocker extensions.
  • The rules with IDs listed in removeRuleIds are first removed, and then the rules given in addRules are added.
  • These rules are persisted across browser sessions and extension updates.
  • You can perform other actions as well like modifying api header, redirecting URL, etc. see docs.
  • pattern matching is also supported for urlFilter. see docs.
  • To block WebSockets, mention url with wss:// protocol ex: urlFilter: "wss://augloop.office.com"

Statically block URLs

It means all the APIs and their rules must be defined beforehand in a rules.json file, and that file must be referenced in manifest.json.

manifest.json:

{
  "name": "declarativeNetRequest extension",
  "version": "1",
  "declarative_net_request": {
    "rule_resources": [
      {
        "id": "ruleset_1",
        "enabled": true,
        "path": "rules.json"
      }
    ]
  },
  "permissions": [
    "*://*.google.com/*",
    "*://*.abcd.com/*",
    "*://*.example.com/*",
    "https://*.xyz.com/*",
    "*://*.headers.com/*",
    "declarativeNetRequest"
  ],
  "manifest_version": 2
}

rules.json:

[
  {
    "id": 1,
    "priority": 1,
    "action": { "type": "block" },
    "condition": { "urlFilter": "google.com", "resourceTypes": ["main_frame"] }
  },
  {
    "id": 2,
    "priority": 1,
    "action": { "type": "allow" },
    "condition": {
      "urlFilter": "google.com/123",
      "resourceTypes": ["main_frame"]
    }
  },
  {
    "id": 3,
    "priority": 2,
    "action": { "type": "block" },
    "condition": {
      "urlFilter": "google.com/12345",
      "resourceTypes": ["main_frame"]
    }
  },
  {
    "id": 4,
    "priority": 1,
    "action": {
      "type": "redirect",
      "redirect": { "url": "https://example.com" }
    },
    "condition": { "urlFilter": "google.com", "resourceTypes": ["main_frame"] }
  },
  {
    "id": 5,
    "priority": 1,
    "action": { "type": "redirect", "redirect": { "extensionPath": "/a.jpg" } },
    "condition": { "urlFilter": "abcd.com", "resourceTypes": ["main_frame"] }
  },
  {
    "id": 6,
    "priority": 1,
    "action": {
      "type": "redirect",
      "redirect": {
        "transform": { "scheme": "https", "host": "new.example.com" }
      }
    },
    "condition": {
      "urlFilter": "||example.com",
      "resourceTypes": ["main_frame"]
    }
  },
  {
    "id": 7,
    "priority": 1,
    "action": {
      "type": "redirect",
      "redirect": {
        "regexSubstitution": "https://\\1.xyz.com/"
      }
    },
    "condition": {
      "regexFilter": "^https://www\\.(abc|def)\\.xyz\\.com/",
      "resourceTypes": ["main_frame"]
    }
  }
]

Few notes:

  • You can at max create 30,000 such static rules (reference).
  • There's also a global quota of static rules shared between all extensions and can be used by extensions on a first-come, first-served basis. Global quota is not yet documented, but it's rumored to be around ~150,000 rules.

Permission warnings

Adding declarativeNetRequest in Manifest file will show a warning to users when installing the extension: "Block content on any page"

popup warning due to declarativeNetRequest permission
popup warning due to declarativeNetRequest permission

Not all permissions show warnings to users. See list of permissions that show warning.

You can test which warnings are shown when developing the extension by following this guide: 

Check for popup warning due to new permissions on updated version

Note: adding a less powerful permission will not show a warning popup if the previous version already has a more powerful permission.

Reference: https://developer.chrome.com/docs/extensions/mv2/permission_warnings/#view_warnings

  • Go to chrome://extensions -> pack previous version -> it'll pack extension (.crx file) and create 1 key (.pem file)
  • drop crx file to chrome://extensions in order to install it
  • Now pack latest version and mention *.pem file
  • drop latest crx file to check for new permissions popup

declarativeNetRequestWithHostAccess

declarativeNetRequestWithHostAccess is a similar alternative to declarativeNetRequest (means you just need to replace it in manifest and rest of the code remains same) and it does not show any popup warnings!

what's the catch you ask?

  • It's available on Chrome v96 and above versions.
  • Inconsistent support for MV2, docs say declarativeNetRequestWithHostAccess permission always requires host permissions but host_permissions key is not available in manifest v2.
That's all, folks!

Gourav Goyal

Gourav Goyal