Making WebWorkers a safe environment Making WebWorkers a safe environment javascript javascript

Making WebWorkers a safe environment


The current code (listed below) has been now in use in the Stackoverflow javascript chat room for a whileand so far the toughest problem was Array(5000000000).join("adasdadadasd") instantly crashing somebrowser tabs for me when I was running the code executor bot. Monkeypatching Array.prototype.join seems to have fixed this and the maximumexecution time of 50ms has worked for any other attempt to hog memory or crash the browser.

var global = this;/* Could possibly create some helper functions here so they are always available when executing code in chat?*//* Most extra functions could be possibly unsafe */var wl = {    "self": 1,    "onmessage": 1,    "postMessage": 1,    "global": 1,    "wl": 1,    "eval": 1,    "Array": 1,    "Boolean": 1,    "Date": 1,    "Function": 1,    "Number" : 1,    "Object": 1,    "RegExp": 1,    "String": 1,    "Error": 1,    "EvalError": 1,    "RangeError": 1,    "ReferenceError": 1,    "SyntaxError": 1,    "TypeError": 1,    "URIError": 1,    "decodeURI": 1,    "decodeURIComponent": 1,    "encodeURI": 1,    "encodeURIComponent": 1,    "isFinite": 1,    "isNaN": 1,    "parseFloat": 1,    "parseInt": 1,    "Infinity": 1,    "JSON": 1,    "Math": 1,    "NaN": 1,    "undefined": 1};Object.getOwnPropertyNames( global ).forEach( function( prop ) {    if( !wl.hasOwnProperty( prop ) ) {        Object.defineProperty( global, prop, {            get : function() {                throw "Security Exception: cannot access "+prop;                return 1;            },             configurable : false        });        }});Object.getOwnPropertyNames( global.__proto__ ).forEach( function( prop ) {    if( !wl.hasOwnProperty( prop ) ) {        Object.defineProperty( global.__proto__, prop, {            get : function() {                throw "Security Exception: cannot access "+prop;                return 1;            },             configurable : false        });        }});Object.defineProperty( Array.prototype, "join", {    writable: false,    configurable: false,    enumerable: false,    value: function(old){        return function(arg){            if( this.length > 500 || (arg && arg.length > 500 ) ) {                throw "Exception: too many items";            }            return old.apply( this, arguments );        };    }(Array.prototype.join)});(function(){    var cvalues = [];    var console = {        log: function(){            cvalues = cvalues.concat( [].slice.call( arguments ) );        }    };    function objToResult( obj ) {        var result = obj;        switch( typeof result ) {            case "string":                return '"' + result + '"';                break;            case "number":            case "boolean":            case "undefined":            case "null":            case "function":                return result + "";                break;            case "object":                if( !result ) {                    return "null";                }                else if( result.constructor === Object || result.constructor === Array ) {                    var type = ({}).toString.call( result );                    var stringified;                    try {                        stringified = JSON.stringify(result);                    }                    catch(e) {                        return ""+e;                    }                    return type + " " + stringified;                }                else {                    return ({}).toString.call( result );                }                break;        }    }    onmessage = function( event ) {        "use strict";        var code = event.data.code;        var result;        try {            result = eval( '"use strict";\n'+code );        }        catch(e) {            postMessage( e.toString() );            return;        }        result = objToResult( result );        if( cvalues && cvalues.length ) {            result = result + cvalues.map( function( value, index ) {                return "Console log "+(index+1)+":" + objToResult(value);            }).join(" ");        }        postMessage( (""+result).substr(0,400) );    };})();


The code currently (2014-11-07) shown in the question despite ostensibly disallowing access to XMLHttpRequest (since it is not whitelisted), still allows code to access it.

If I put the code in the question (or the accepted answer) in a web page and worker combo and execute the following code on Chrome 38:

makeWorkerExecuteSomeCode('event.target.XMLHttpRequest', function (answer) { console.log( answer ); });

The result is:

function XMLHttpRequest() { [native code] } 

However it does not work in FF. Bug in Chrome?

Another thing I found but which does not seem to lead very far down the rabit hole is reinstating console.log. This works on FF 31 but not Chrome 38:

makeWorkerExecuteSomeCode(    'var c = self.__proto__.__proto__.__lookupGetter__("console").call(self); c.log("FOO");',     function (answer) { console.log(answer) });

This would log "FOO" to the console without passing through the fake console.log that the web worker provides. The code above uses self, which can be blacklisted (by removing it from the whitelist) but this and global also work. I've found that attempts to blacklist global fail on FF and Chrome: the worker dies with an error.

Note: Chrome refuses to blacklist Intl so it has to be added to the whitelist for the code to run at all.