8

I usually think of global scope as a namespace that always can be accessed from everywhere. I would like to know whether it is theoretically possible to completely hide global scope. For example, assume we have some code we would like to evaluate (in the console of a browser):

var code = 
  "console.log(this);   " + // access the global object directly
  "console.log(window); " + // access the global object as the window object
  "newGlobalVar = 42;   ";  // implicitly create global object
eval(code);

By wrapping the eval call, this and window can be hidden from code:

(function (window) { eval(code); }).call({});

But I can't stop the code implicitly create global variables. Is it possible somehow? I don't want to use this stuff, I'm just curious.

kol
  • 24,444
  • 11
  • 70
  • 104
  • 1
    Are you trying to sandbox `eval`'d/arbitrary code? There's a better way around if that's your aim. – Fabrício Matté May 29 '13 at 20:42
  • No, I was just thinking about shielding global scope from user code. This led me to this question, it's about theory. – kol May 29 '13 at 20:48
  • 1
    Then, in modern browsers, theoretically, you could [freeze](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) the `window` object. Though I've never tried that. – Fabrício Matté May 29 '13 at 20:51
  • @FabrícioMatté Interesting. I've just tried freeze and seal in Safari 5.1.9, and it worked on general objects, but did not work on the global object. – kol May 29 '13 at 21:00
  • @FabrícioMatté how would you do the eval sandboxing? – Zorgatone Apr 27 '16 at 15:52
  • 2
    @Zorgatone Running arbitrary untrusted code is tough business, I'd do everything to avoid it. But if you really have to, these resources may be useful: [`vm.runInNewContext()`](https://nodejs.org/api/vm.html#vm_vm_runinnewcontext_code_sandbox_options) (Node.js only), [Aether](http://aetherjs.com/) (has incomplete sandboxing, ideally should be run inside a Web Worker), and [Dr. SES](http://soft.vub.ac.be/~tvcutsem/invokedynamic/drses) (capability-based security). – Fabrício Matté May 15 '16 at 11:38
  • Great to know. Thanks – Zorgatone May 15 '16 at 11:41

2 Answers2

6

If you're running in fairly modern browsers, you can mostly block window access by making a function that shadows the window and self variables with parameters, and runs the code in strict mode.

var obj = {};

var func = new Function("self", "window", "'use strict';" + code);

func.call(obj, obj, obj);

console.log(obj); // see if there were any attempts to set global variables.

Any attempt to access window or self will merely access our obj object, and the value of this will also be our obj.

Because we're in strict mode, implicit globals aren't allowed. Also, the default this value of functions will be undefined instead of window.

I think there are a couple hacks that may get around this, but this should cover most scenarios.

  • 1
    It appears you can still access global variables. `code="return location.href;";`, `code="return console;";`, and `code="return top;";` all returned results. – jongo45 May 30 '13 at 00:49
  • @jongo45: Yes, you can access globals, because they're global. But you can't do any manipulations to the global scope. I'm pretty sure OP was trying to prevent creation of new globals, not block access to all. –  May 30 '13 at 01:14
  • @squint: Not true, top.myfunc = "123"; allows you to do console.log(myfunc) after the code has been run. It should iterate all global variables and make them arguments before calling the code. – Rahly Mar 18 '15 at 23:41
  • @Rahly: Then `top` should be added to the function parameters. But again, you'd only need to block all globals if the OP was actually trying to prevent all access. I'm pretty sure OP was trying to prevent creation of new globals, not block access to all. –  Mar 19 '15 at 02:55
  • Correct, but your statement was inaccurate, based on the posted code. I was just pointing it out – Rahly Mar 19 '15 at 19:35
  • @Rahly: Yes, `top` should indeed be added to the parameter list. You can do so if you'd like. It's a CW. ;-) –  Mar 19 '15 at 20:21
5

Note: This is still a work in progress and partly inspired by squint's code snippet.

function quarantinedFunction(fnText){
    var exceptionKeys=[
        "eval","Object",  //need exceptions for this else error. (ie, 'Exception: redefining eval is deprecated')
        "Number","String","Boolean","RegExp","JSON","Date",
    ];
    var forbiddenKeys=[
        "fn","fnText","forbiddenKeys","exceptionKeys","empty","oForbiddenKeys",
    ];
    var oForbiddenKeys=Object.create(null);
    var empty=Object.create(null);
    Object.freeze(empty);
    forbiddenKeys.forEach(function(key){
        oForbiddenKeys[key]=null;
    });
    [this,self].forEach(function(obj){
        Object.getOwnPropertyNames(obj).forEach(function(key){
            if(!key.match(/^[\$\w]+$/))return;
            oForbiddenKeys[key]=null;
        });
    });
    exceptionKeys.forEach(function(key){
        delete oForbiddenKeys[key];
    });

    if(0){//debugging.
        return function(){
            return Object.keys(oForbiddenKeys);
            return Object.keys(empty);
        };
    }

    fnText=[
        '"use strict";',
        "var "+Object.keys(oForbiddenKeys).join(", ")+";",
        "{",
        fnText,
        "}"
    ].join("\n");

    var fn= (function(){
        with(empty)
        {
            return new Function("self","window",fnText);
        }
    })();

    return function(){
       return fn.call(Object.create(null));      //self,window undefined
       return fn.call(empty,empty,empty);  //self,window are objects w/o properties
    };
}

Output results (from Firefox scratchpad):

quarantinedFunction("return location.href;")();
/*
Exception: location is undefined
*/
quarantinedFunction("someGlobalVar=15;")();
/*
Exception: assignment to undeclared variable someGlobalVar
*/
quarantinedFunction("return 9*9;")();
/*
81
*/
quarantinedFunction("return console;")();
/*
undefined
*/

And a jsfiddle with some results.

Note: Some unexpected results show up in the fiddle but not in other tools (i.e. the location variable returns the page's url when the fiddle is viewed from firefox aurora, but not on chrome nor on the scratchpad devtool -- possibly the handiwork of Firefox's __noSuchMethod__ or similar 'late-binding' mechanism, resulting in properties being added only when accessed).

jongo45
  • 2,642
  • 2
  • 13
  • 12