Can I ask a browser to not run <script>s within an element?

JavascriptHtmlScript Tag

Javascript Problem Overview


Is it possible to tell browsers to not run JavaScript from specific parts of an HTML document?

Like:

<div script="false"> ...

It could be useful as an additional security feature. All the scripts I want are loaded in a specific part of the document. There should be no scripts in other parts of the document and if there are they should not be run.

Javascript Solutions


Solution 1 - Javascript

YES, you can :-) The answer is: Content Security Policy (CSP).

Most modern browsers support this flag, which tells the browser only to load JavaScript code from a trusted external file and disallow all internal JavaScript code! The only downside is, you can not use any inline JavaScript in your whole page (not only for a single <div>). Although there could be a workaround by dynamically including the div from an external file with a different security policy, but I'm not sure about that.

But if you can change your site to load all JavaScript from external JavaScript files then you can disable inline JavaScript altogether with this header!

Here is a nice tutorial with example: HTML5Rocks Tutorial

If you can configure the server to send this HTTP-Header flag the world will be a better place!

Solution 2 - Javascript

You can block JavaScript loaded by <script>, using beforescriptexecute event:

<script>
  // Run this as early as possible, it isn't retroactive
  window.addEventListener('beforescriptexecute', function(e) {
    var el = e.target;
    while(el = el.parentElement)
      if(el.hasAttribute('data-no-js'))
        return e.preventDefault(); // Block script
  }, true);
</script>

<script>console.log('Allowed. Console is expected to show this');</script>
<div data-no-js>
  <script>console.log('Blocked. Console is expected to NOT show this');</script>
</div>

Note that beforescriptexecute was defined in HTML 5.0 but has been removed in HTML 5.1. Firefox is the only major browser that implemented it.

In case you are inserting an untrusted bunch of HTML in your page, be aware blocking scripts inside that element won't provide more security, because the untrusted HTML can close the sandboxed element, and thus the script will be placed outside and run.

And this won't block things like <img onerror="javascript:alert('foo')" src="//" />.

Solution 3 - Javascript

Interesting question, I don't think it's possible. But even if it is, it sounds like it would be a hack.

If the contents of that div are untrusted, then you need to escape the data on the server side before it is sent in the HTTP response and rendered in the browser.

If you only want to remove <script> tags and allow other html tags, then just strip them out of the content and leave the rest.

Look into XSS prevention.

https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet

Solution 4 - Javascript

JavaScript is executed "inline", i.e. in the order in which it appears in the DOM (if that wasn't the case, you could never be sure that some variable defined in a different script was visible when you used it for the first time).

So that means in theory you could have a script at the beginning of the page (i.e. first <script> element) which looks through the DOM and removed all <script> elements and event handlers inside of your <div>.

But the reality is more complex: DOM and script loading happens asynchronously. This means that the browser only guarantees that a script can see the part of the DOM which is before it (i.e. the header so far in our example). There are no guarantees for anything beyond (this is related to document.write()). So you might see the next script tag or maybe, you don't.

You could latch to the onload event of the document - which would make sure you got the whole DOM - but at that time, malicious code could have already executed. Things get worse when other scripts manipulate the DOM, adding scripts there. So you would have to check for every change of the DOM, too.

So @cowls solution (filtering on the server) is the only solution which can be made to work in all situations.

Solution 5 - Javascript

If you're looking to display JavaScript code in your browser:

Using JavaScript and HTML, you'll have to use HTML entities to display the JavaScript code and avoiding this code to be executed. Here you can find the list of HTML entities:

If you're using a server-side scripting language (PHP, ASP.NET, etc.), most probably, there's a function which would escape a string and convert the special characters into HTML entities. In PHP, you would use "htmlspecialchars()" or "htmlentities()". The latter covers all the HTML characters.

If you're looking to display your JavaScript code in a nice way, then try one of the code highlighters:

Solution 6 - Javascript

I've got a theory:

  • Wrap the specific part of the document inside a noscript tag.
  • Use DOM functions to discard all script tags inside the noscript tag then unwrap its contents.

Proof of concept example:

body { font: medium/1.5 monospace; }
p, h1 { margin: 0; }

<h1>Sample Content</h1>
<p>1. This paragraph is embedded in HTML</p>
<script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
<p>3. This paragraph is embedded in HTML</p>
<h1>Sample Content in No-JavaScript Zone</h1>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>
<noscript>
    <p>1. This paragraph is embedded in HTML</p>
    <script>document.write('<p style="color: red;">2. This paragraph is generated by JavaScript</p>');</script>
    <p>3. This paragraph is embedded in HTML</p>
</noscript>

window.onload = function() {
    var noscripts = /* _live_ list */ document.getElementsByTagName("noscript"),
        memorydiv = document.createElement("div"),
        scripts = /* _live_ list */ memorydiv.getElementsByTagName("script"),
        i,
        j;
    for (i = noscripts.length - 1; i >= 0; --i) {
        memorydiv.innerHTML = noscripts[i].textContent || noscripts[i].innerText;
        for (j = scripts.length - 1; j >= 0; --j) {
            memorydiv.removeChild(scripts[j]);
        }
        while (memorydiv.firstChild) {
            noscripts[i].parentNode.insertBefore(memorydiv.firstChild, noscripts[i]);
        }
        noscripts[i].parentNode.removeChild(noscripts[i]);
    }
};

Solution 7 - Javascript

If you want to re-enable script tags later on, my solution was to break the browser environment so that any script that runs will throw an error fairly early. However, it's not totally reliable, so you can't use it as a security feature.

If you try to access global properties Chrome will throw an exception.

setTimeout("Math.random()")
// => VM116:25 Uncaught Error: JavaScript Execution Inhibited  

I'm overwriting all overwritable properties on window, but you could also expand it to break other functionality.

window.allowJSExecution = inhibitJavaScriptExecution();
function inhibitJavaScriptExecution(){

    var windowProperties = {};
    var Object = window.Object
    var console = window.console
    var Error = window.Error

    function getPropertyDescriptor(object, propertyName){
        var descriptor = Object.getOwnPropertyDescriptor(object, propertyName);
        if (!descriptor) {
            return getPropertyDescriptor(Object.getPrototypeOf(object), propertyName);
        }
        return descriptor;
    }

    for (var propName in window){
        try {
            windowProperties[propName] = getPropertyDescriptor(window, propName)
            Object.defineProperty(window, propName, {
                get: function(){
                    throw Error("JavaScript Execution Inhibited")
                },
                set: function(){
                    throw Error("JavaScript Execution Inhibited")
                },
                configurable: true
            })
        } catch (err) {}
    }

    return function allowJSExecution(){
        for (var propName in window){
            if (!(propName in windowProperties)) {
                delete windowProperties[propName]
            }
        }

        for (var propName in windowProperties){
            try {
                Object.defineProperty(window, propName, windowProperties[propName])
            } catch (err) {}
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSeppo Ervi&#228;l&#228;View Question on Stackoverflow
Solution 1 - JavascriptFalcoView Answer on Stackoverflow
Solution 2 - JavascriptOriolView Answer on Stackoverflow
Solution 3 - JavascriptcowlsView Answer on Stackoverflow
Solution 4 - JavascriptAaron DigullaView Answer on Stackoverflow
Solution 5 - JavascriptWissam El-KikView Answer on Stackoverflow
Solution 6 - JavascriptSalman AView Answer on Stackoverflow
Solution 7 - JavascriptMatt ZeunertView Answer on Stackoverflow