Sandboxing JavaScript Using <iframe>

I’ve been experimenting with running code in an iframe and the results are very encouraging.

Sandbox.eval()

I’m currently developing a templating system in JavaScript and ran into a problem with scope. The problem is that my template scripts have access to all of the global (window) object’s properties and methods. I don’t want this. I want my template scripts to run in a separate, closed environment. Template scripts shouldn’t be able to address anything in the browser window. This could potentially lead to disaster.

The second eval should fail because goodbye is defined in the global scope but not in the templating environment. A standard eval would work as the code is evaluated in the same scope that goodbye is defined.

So I hacked around with an iframe and eval for a couple of hours and came up with this:

I won’t bore you with the details. There is a browser sniff there because Internet Explorer handles eval slightly different to other browsers. The important thing is that the code above allows us to evaluate JavaScript outside of the current and global scope.

In my next post I’ll show you some more iframe tricks, including a way to subclass the JavaScript Array object.

[…] I’m currently developing a templating system in JavaScript and ran into a problem with scope. The problem is that my template scripts have access to all of the global (window) object’s properties and methods. I don’t want this. I want my template scripts to run in a separate, closed environment. Template scripts shouldn’t be able to address anything in the browser window. This could potentially lead to disaster.Continue for more info…. PLAIN TEXT JAVA: […]

If I get that right, the script you wright into that iframe returns the iframe itself for MSIE and a eval function that calls eval for every other browser. Therefore a call to sandbox.eval calls the eval directly on the iframe in MSIE and a wrapper function for others.

Is there any way to evaluate in global context for everybrowser? So far I know that eval.call(window, data) doesn’t work for IE and Safari. IE provides window.execScript as a workaround, which is fine. The only solution is to use window.setTimeout(data, 0) to evaluate in Safari in global context. But the timeout is asynchronous, therefore introducing new problems.

I think that Dean is using here for IE the same feature that was causing troubles in jQuery. Knowing that IE eval-uates loaded scripts without getting access to the global context, Dean is using the iframe in IE only as if it were an ajaxed tag, ie bypassing his own hack.

On the other side, browsers which do get access to the global context are shielded by the iframe, because eval-uation takes place inside the window-like object of the iframe by means of the Dean’s closure-like hack: a sandbox is made equal to an object defined in the iframe, with just the needed eval function.

Dean, I’ve also been using the iframe trick to get JavaScript code to run in its own global context. It’s a great way to prevent code that modifies global objects from stepping on other code.

If all you need is to prevent inadvertent access to the window or document object, that’s good enough. But if you are really trying to prevent all access, it doesn’t seem like it will do it. In your example:

Hi, dean, what is your templating system for? If u just want to wrap the codes, why not use new Function(code)() instead of eval(code) ? It’s also possible to forbid some symbols, for example new Function(‘parent’, code)(null) will forbid property parent.

Something I just discovered which I think is relevant: in IE, it seems that for an iframe window object there is no eval method. But if you call execScript on the iframe window just once (e.g. iframeWin.execScript(“”)) then the eval method magically appears. Tested and true in IE5 and 6 in Windows (for me, at least). So I have the following workaround in my code:

[…] (PS: Having found the magic term execScript, I was then able to find some related articles on this topic by Dean Edwards and Jeff Watkins. However much of the details are buried in the comments, so I hope this article will increase both the findability and conciseness of this information). […]

This is not exactly about sandboxing, but rather a response to Jörn Zaefferer’s question about how to “eval” code in Safari in the global scope synchronously. In fact, the function below works in every modern browser I’ve seen and in every platform: IE, Firefox, Mozilla, Safari, Camino, Opera etc; Mac, Windows, Linux etc.

Another interesting way of avoiding global namespace pollution is to use a Function object and `with’. This isn’t the same as your sandbox in that the global scope is still visible, but it allows you to declare non-global variables and to manipulate the scope visible inside the function body from outside the function; consider:

Here `wizard’ appears in scope from the perspective of the function body. The above example is a bit contrived, but the real usefulness can be seen when used in conjunction with code libraries fetched using XMLHttpRequest. Suppose that we had a method called `debug’ which we would like to be available to all libraries loaded this way:

So now in ‘/jslib/crypt.js’ we can freely call `debug(…)’ as if it were global and furthermore, so as long as the code in this file is declaring variables with `var’, then these will not pollute the global namespace.

@Dean
Nice work man, great idea!!! I am working on a library of my own, but was not happy with having all my classes polluting the global scope. Building an object tree for simulating namespaces was an improvement, but still I wasn’t very happy with it. And suddenly the light shines on the horizon…

@Michael Geary
As in your example: nothing. Except that it will propably be his own code that gets evaluated, so why bother. And setting parent to null in the iframe script would cut off all attempts :).

@Erik
Works like a charm… except when trying to get some return values from eval. Only the pure unaltered eval choice in that function provides. All others (execScript, timeout, script tag) do eval, admitted, but they are crippled. Half the times I ever used eval was with a return value, so…

@Jörn Zaefferer
Just use eval() instead of eval.call(). Even browsers from the stone age could do that :).

Some further thoughts on the comments of Erik and Jörn:

All those workarounds for something I really can’t imagine the use for. What’s wrong with just using eval when needed? I can follow Dean’s intentions with using eval outside the global scope. But why on earth is it so important in what scope eval runs otherwise? Because if it is to make sure something gets created in a specific scope, there are far better ways to go than eval. Even with arbitrary values:

someObject[arbitrary] = someValue;

and NOT

eval("someObject."+arbitrary+" = "+someValue+";");

For those who think I don’t get my scopes… ah well, think whatever you like ;). I just meant that I don’t see the point in using eval.call(window,…) from within a function. Never met a situation where I needed it. Can’t really come up with one also. Come to think of it, there aren’t that many situations where one really needs eval at all.

Dean’s example is one worthy use for it. And he didn’t need a lengthy function to ‘fix’ (what’s in a word…) it. But in all, there are far more interesting ways for using the iframe technique, as demonstrated by the man himself with the Array2 example (next post).

Thanks for such a great idea! I am currently working on project where application have to execute untrusted user scripts. There was a complex idea of using narcissus to run scripts in some secure environment.

@Stefan Van Reeth: When you have huge JS framework, and loading it all onload slows browser to crawl, you have to load strings and evaluate them to speed things up (read: to make site work again). Look at Dojo and its loader system for example.

[…] globalEval javascript function A function that works in every browser for evaluating JavaScript code in the global namespace. Oddly enough, it’s in a comment attached to Dean Edwards’ blog post about sandboxing code so that it’s *not* in the global space. (tags: javascript programming) […]

One bad problem with execScript is that if there is an error, then in IE6 you only get “Could not complete the operation due to error 80020101″. You lose all information about the original error. If you don’t have access to the machine, then you can’t work out why the problem occurred (e.g. prevent you from diagnosing the cause of an installation specific fault to a customer – which happens on occasion with IE)

[PS: If you are reading this because you have got that error, then try replacing execScript(code); with (window.eval || eval)(code, null); !]

Try it out using:
javascript:execScript(’syntaxerror;’);
And see if you get an 80020101 error (this behaviour may be specific to particular versions or patches of IE).

I recently had a problem with parsing a large amount of JSON data to be converted to a table. The JSON-to-table function I’m using creates the table as an XHTML string and innerHTMLs it to the body. The resulting string is so large it was causing a “script stack space quota exhausted” error in firefox 3.0.5 and 3.1b2 (not sure about other versions). I’m using deans technique to get around this:

i would like to know how to achieve the javascripts on the iframes.
ondragstart, oncopy, ondblclick, etc are not avoilabel on iframes.
Particularly, i want to disable the right click on the pdf which is displayed on the iframe.
Please help.