Block Javascript on specific sites


(Andras Horvath) #1

H Everyone, could you kindly help me in the following?

I’d like to write an extension that blocks Javascript from running on specific sites. I was thinking I can use the onBeforeRequest event for this to catch the requestBody before the page loads and then remove every script tag from it. I cannot get it working. I have the following code so far:

manifest.json:
{
“description”: “just some test”,
“manifest_version”: 2,
“name”: “testttt”,
“version”: “1.0”,
“icons”: {
“48”: “icon.svg”
},

"applications": {
	"gecko": {
		"id": "testttt@mozilla.org",
		"strict_min_version": "45.0"
	}
},

"permissions": [
	"webRequest", "webRequestBlocking"
],

"background": {
  "scripts": ["background.js"]
 },

"content_scripts": [
	{
		"matches": ["*://mydomain.com/*"],
		"js": ["test.js"]
	}
]

}

test.js:
document.body.style.border = “20px solid orange”;

background.js:
browser.webRequest.onBeforeRequest.addListener(
function(requestDetails){
console.log(“hello”);
alert(22);
},
{urls: [“https://mydomain.com/*”]},
[“requestBody”, “blocking”]
);

test.js does work, however background.js does not. There is no log of “hello” in the log panel that I open up with key Ctrl+Shift+J. There is no popup box from alert either.

Could you tell me what’s wrong and how I could achive to block scripts on specific sites writing my own WebExtension?

I’m on FF 57 beta3 but I have the same reault on 55.0.3 too.

I’m testing my webext using the about:debugging#addons page and load it there using the reload button for changes.

Thanks.


(Baptiste Thémine) #2

Hello ! A background script cannot open alert dialog box because, as its name says, this script is executed in background, not within a web page. Then, if you want to see “hello” in the console, you must load a page with the url you specified in the filter (https://mydomain.com/) else the onBeforeRequest event is not triggered.
Moreover, you must provide the permission “*://mydomain.com/*” in your manifest.json and for each domain you need to receive webRequest events (Read Permissions on MDN). I suggest you to use “<all_urls>” and then do some actions on each url (don’t forget “<all_urls>” permission too).


(Andras Horvath) #3

There is this extension example:

https://gist.github.com/Ajnasz/fb7777184200504d4e47ad892583984a

How can I block Javascript on a domain from background.js and load the page without any script?

Thanks.


(Baptiste Thémine) #4

You can simply block script loading by adding the filter types: ["script"] and return {cancel: true} in the callback if url matches. Here is an example :


browser.webRequest.onBeforeRequest.addListener(
   function(details){
      var url = details.originUrl || details.url;
      return {cancel: (url.search("your url regex here")>=0)};
   },
   {urls: ["<all_urls>"], types: ["script"]},
   [“blocking”]
);

You have also some examples about blocking image loading on https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest/onBeforeRequest.


(Andras Horvath) #5

Hi, unfortunately it doesn’t work. I’m testing the script you provided on a simple page like this:

http://paste.ubuntu.com/25659651/

I’m expecting the popup message box from alert NOT to show up, but it does. No matter how I specify or don’t specify the regex to match my domain. With all_url or with a specific one. However if I replace the “script” with “image” then it does work as expected.

So I assume that it has something to do with the “script” parameter. Thanks.

Baptistou Baptiste Thémine
October 2
You can simply block script loading by adding the filter types: ["script"] and return false in the callback if url matches. Here is an example :

> 
> browser.webRequest.onBeforeRequest.addListener( function(details){ var url = details.originUrl || details.url; return (url.search("your url regex here")>=0); }, {urls: ["<all_urls>"], types: ["script"]}, [“blocking”]
> );

(Baptiste Thémine) #6

Ah ok I understand now. The fact is that onBeforeRequest is triggered when a script is loaded within a page with <script type="text/javascript" src="script.js"></script>. But what you are trying to do is to remove also inline scripts, so you need to include a content script at document start and then remove every script tags <script></script> and script attributes onload="alert(42)", onclick="alert(42)", onhover="alert(42)" etc.


(Andras Horvath) #7

I’d gladly program it if I could make it work in the first place for a very simple example, but I cannot do that either. Why doesn’t the type of “script” work where “image” does?


(Baptiste Thémine) #8

webRequest API concerns any web requests such as HTTP, FTP… It works with <img src="image.png"> because of the src attribute i.e. the page triggers a GET HTTP request to get image. It works also with <script type="text/javascript" src="script.js"> because the page triggers a GET HTTP request to get javascript file. Inline scripts are not included in this.


(Baptiste Thémine) #9

You could write a simple content script like below in order to remove inline scripts :


//Removes script tags
document.querySelectorAll("script").forEach(function(element){
    element.textContent = "";
    //OR
    element.parentNode.removeChild(element);
});

//Removes some script attributes
document.querySelectorAll("[onload], [onclick], [onhover]").forEach(function(element){
    element.removeAttribute("onload");
    element.removeAttribute("onclick");
    element.removeAttribute("onhover");
    //...
});

(Andras Horvath) #10

Actually it doesn’t work for me with “”, the script runs and the alert box appears. This is my problem. I understand your concern about the onload etc other ways of embedding scripts, but I can’t even block the script tags.

About your other offer, do you think the content script could be run before the page’s scrips get initiated?

Thank you for your help.


(Andras Horvath) #11

I tested your content script but unfortunately the page’s script runs first. Do you have any idea how I could make the content script be the first to run? Thx.

Baptistou Baptiste Thémine
October 2
You could write a simple content script like below in order to remove inline scripts :

> 
> //Removes script tags
> document.querySelectorAll("script").forEach(function(element){ element.parentNode.removeChild(element);
> }); //Removes some script attributes
> document.querySelectorAll("[onload], [onclick], [onhover]").forEach(function(element){ element.removeAttribute("onload"); element.removeAttribute("onclick"); element.removeAttribute("onhover"); //...
> });

(Niklas Gollenstede) #12

I havn’t read all your comments above, but the easiest and most robust way to do what you described in the first post is to add a CSP header with the directive script-src 'none' to the headers. That will prevent all scripts from running on that page.


(Baptiste Thémine) #13

Yeah, I haven’t thought about CSP :wink: Instead of using a content script, you could use browser.webRequest.onBeforeSendHeaders and add the Content Security Policy to avoid every scripts.


browser.webRequest.onBeforeSendHeaders.addListener(
   function(details){
      if(details.url.search("your url regex here")>=0)
         //Here you return the modified HTTP headers with CSP
   },
   {urls: ["<all_urls>"]},
   [“blocking”, "requestHeaders"]
);

See examples on https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/webRequest/onBeforeSendHeaders and https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy.


(Andras Horvath) #14

As I see, CSP is for something else. If I’m correct, It is to put limit to how the WebExtension can operate and not how the loaded page.

Baptistou Baptiste Thémine
October 2
Yeah, I haven’t thought about CSP :wink: Instead of using a content script, you could use browser.webRequest.onBeforeSendHeaders and add the Content Security Policy to avoid every scripts.

> 
> browser.webRequest.onBeforeSendHeaders.addListener( function(details){ if(details.url.search("your url regex here")>=0) //Here you return the modified HTTP headers with CSP }, {urls: ["<all_urls>"]}, [“blocking”, "requestHeaders"]
> );

(Niklas Gollenstede) #15

A CSPs script-src 'none' is exactly what you want. It instructs the browser to execute scripts from no source whatsoever.

You are correct though that CSPs are meant to be set by website developers / admins, not browser add-ons. But it is still possible and will have the desired effect!

What you have to do in your case is add a header { name: 'Content-Security-Policy', value: "script-src 'none'", } to the return responseHeaders (don’t remove / change any CSP headers).


(Andras Horvath) #16

Based on your directions guys, I’ve finally made it work. Instead of “onBeforeSendHeaders” I had to use “onHeadersReceived” and instead of “requestHeaders” I had to use “responseHeaders”.

Thank you so much for your efforts in helping Baptiste and finally Niklas. The working code is below:

http://paste.ubuntu.com/25662841/

browser.webRequest.onHeadersReceived.addListener(
function(details){
//console.log(details);
// include the original response header too merging the two arrays here
var rh = details.responseHeaders.concat([{name: “Content-Security-Policy”, value: “script-src ‘none’”}]);
return {responseHeaders: rh};
},
{urls: ["://local.frontfoo.com/"]},
[“blocking”, “responseHeaders”]
);


(Niklas Gollenstede) #17

You are welcome!

But please include the original response headers in your new headers. There can be important security stuff in there (and the "script-src 'none'" works either way):

browser.webRequest.onHeadersReceived.addListener(details => {
    // console.log("before web request", details);
    return { responseHeaders: [ ...details.responseHeaders, {name: 'Content-Security-Policy', value: "script-src 'none'", }, ], };
}, {urls: ["*://[mydomain.com/*](http://mydomain.com/*)"]}, ["blocking", "responseHeaders"]);

(Andras Horvath) #18

Right, thanks. So for clarification, I include the full code here again:

http://paste.ubuntu.com/25662977/

// https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
browser.webRequest.onHeadersReceived.addListener(
	function(details){
		//console.log(details);
		// include the original response header too merging the two arrays here
		var rh = details.responseHeaders.concat([{name: "Content-Security-Policy", value: "script-src 'none'"}]);
		return {responseHeaders: rh};
	},
	{urls: ["*://mydomain.com/*"]},
	["blocking", "responseHeaders"]
);

(Niklas Gollenstede) #19

Yepp. That looks good.


(Baptiste Thémine) #20

You’re welcome ! Good job :+1: