Question

I have a javascript file that reads another file which may contain javascript fragments that need to be eval()-ed. The script fragments are supposed to conform to a strict subset of javascript that limits what they can do and which variables they can change, but I want to know if there is some way to enforce this by preventing the eval from seeing variables in the global scope. Something like the following:

function safeEval( fragment )
{
    var localVariable = g_Variable;

    {
        // do magic scoping here so that the eval fragment can see localVariable
        // but not g_Variable or anything else outside function scope

        eval( fragment );
    }
}

The actual code doesn't need to look like this--I'm open to any and all weird tricks with closures, etc. But I do want to know if this is even possible.

Was it helpful?

Solution

Short answer: No. If it's in the global scope, it's available to anything.

Long answer: if you're eval()ing untrusted code that really wants to read or mess with your execution environment, you're screwed. But if you own and trust all code being executed, including that being eval()ed, you can fake it by overriding the execution context:

function maskedEval(scr)
{
    // set up an object to serve as the context for the code
    // being evaluated. 
    var mask = {};
    // mask global properties 
    for (p in this)
        mask[p] = undefined;

    // execute script in private context
    (new Function( "with(this) { " + scr + "}")).call(mask);
}

Again, I must stress:

This will only serve to shield trusted code from the context in which it is executed. If you don't trust the code, DO NOT eval() it (or pass it to new Function(), or use it in any other way that behaves like eval()).

OTHER TIPS

Here's an idea. What if you used a static analyzer (something you could build with esprima, for example) to determine which outside variables the eval'd code uses, and alias them. By "outside code" i mean variables the eval'd code uses but does not declare. Here's an example:

eval(safeEval(
     "var x = window.theX;"
    +"y = Math.random();"
    +"eval('window.z = 500;');"))

where safeEval returns the javascript string modified with a context that blocks access to outside variables:

";(function(y, Math, window) {"
  +"var x = window.theX;"
  +"y = Math.random();"
  +"eval(safeEval('window.z = 500;');"
"})();"

There are a couple things you can do now with this:

  • You can ensure that eval'd code can't read the values of outside variables, nor write to them (by passing undefined as the function arguments, or not passing arguments). Or you could simply throw an exception in cases where variables are being unsafely accessed.
  • You also ensure that variables created by eval don't affect the surrounding scope
  • You could allow eval to create variables in the surrounding scope by declaring those variables outside the closure instead of as function parameters
  • You could allow read-only access by copying values of outside variables and using them as arguments to the function
  • You could allow read-write access to specific variables by telling safeEval to not alias those particular names
  • You can detect cases where the eval does not modify a particular variable and allow it to be automatically excluded from being aliased (eg. Math in this case, is not being modified)
  • You could give the eval a context in which to run, by passing in argument values that may be different than the surrounding context
  • You could capture context changes by also returning the function arguments from the function so you can examine them outside the eval.

Note that the use of eval is a special case, since by its nature, it effectively can't be wrapped in another function (which is why we have to do eval(safeEval(...))).

Of course, doing all this work may slow down your code, but there are certainly places where the hit won't matter. Hope this helps someone. And if anyone creates a proof of concept, I'd love to see a link to it here ; )

Shog9♦'s Answer is great. But if your code is just an expression, the code will be executed and nothing will be returned. For expressions, use

function evalInContext(context, js) {

  return eval('with(context) { ' + js + ' }');

}

Here is how to use it:

var obj = {key: true};

evalInContext(obj, 'key ? "YES" : "NO"');

It will return "YES".

If you are not sure if the code to be executed is expressions or statements, you can combine them:

function evalInContext(context, js) {

  var value;

  try {
    // for expressions
    value = eval('with(context) { ' + js + ' }');
  } catch (e) {
    if (e instanceof SyntaxError) {
      try {
        // for statements
        value = (new Function('with(this) { ' + js + ' }')).call(context);
      } catch (e) {}
    }
  }

  return value;
}

You cant limit the scope of eval

btw see this post

There may be some other way to accomplish what it is you want accomplish in the grand scheme of things but you cannot limit the scope of eval in any way. You may be able to hide certain variables as pseudo private variables in javascript, but I dont think this is what you're going for.

Similar to the dynamic function wrapping script in a with block approach above, this allows you to add pseudo-globals to the code you want to execute. You can "hide" specific things by adding them to the context.

function evalInContext(source, context) {
    source = '(function(' + Object.keys(context).join(', ') + ') {' + source + '})';

    var compiled = eval(source);

    return compiled.apply(context, values());

    // you likely don't need this - use underscore, jQuery, etc
    function values() {
        var result = [];
        for (var property in context)
            if (context.hasOwnProperty(property))
                result.push(context[property]);
        return result;
    }
}

See http://jsfiddle.net/PRh8t/ for an example. Note that Object.keys is not supported in all browsers.

There is a project called Google Caja. You can "sandbox" third party javascript using Caja. https://developers.google.com/caja/

Don't use eval. There's an alternative, js.js: JS interpreter written in JS, so that you can run JS programs in any environment you've managed to setup. Here's an example of its API from the project page:

var jsObjs = JSJS.Init();
var rval = JSJS.EvaluateScript(jsObjs.cx, jsObjs.glob, "1 + 1");
var d = JSJS.ValueToNumber(jsObjs.cx, rval);
window.alert(d); // 2
JSJS.End(jsObjs);

Nothing scary, as you can see.

Don't execute code you don't trust. Globals will always be accessible. If you do trust the code, you can execute it with particular variables in it's scope as follows:

(new Function("a", "b", "alert(a + b);"))(1, 2);

this is equivalent to:

(function (a, b) {
    alert(a + b);
})(1, 2);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top