dean.edwards.name/weblog/2006/11/sandbox/

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.

Basically, I want the following code to work:

sandbox.eval("alert('Hello!')"); // => Hello!
try {
	var goodbye = "Goodbye!";
	sandbox.eval("alert(goodbye)"); // => ERROR!
} catch (error) {
	alert("ERROR!");
}

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:

// create an <iframe>
var iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);

// write a script into the <iframe> and create the sandbox
frames[frames.length - 1].document.write(
	"<script>"+
	"var MSIE/*@cc_on =1@*/;"+ // sniff
	"parent.sandbox=MSIE?this:{eval:function(s){return eval(s)}}"+
	"<\/script>"
);

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. ;-)

Comments (40)

Leave a comment

[...] Previous post: ← Sandboxing JavaScript Using <iframe> [...]

useless

  • Comment by: useless
  • Posted:

Sandboxing could be very useful for Unit Testing of Javascript code where each unit test or unit test case runs in its own sandbox.

  • Comment by: Hermann Klinke
  • Posted:

Hey Dean, Nice hack. I look forward to a re-usable package ;-)

What are your thoughts on using this technique to allow safer parsing/eval of JSON strings? I’m not sure if it’s possible, but it seems like a good application.

Also, is there any concerns with memory leaks, having all these object references across iframes?

Hi Dean,

I’ve made a script that makes tries to simulate the iframe.onload behavior which is not present in IE browsers, maybe it’s useful. See it at http://solutoire.com/index.php?resources=iframe

cheers Bas

Maybe obvious to some, but you should add document.open and document.close so avoid having the throbber spinning for ever.

[...] 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.

Any idea how to eval in Safari without timeout in global scope?

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.

You should read: …On the other side, in browsers which allow loaded scripts access the global context, these scripts are shielded…

[...] But I’ve been playing with iframes.  [...]

[...] Sandboxing JavaScript Using <iframe> – could be a useful hack for my work in future. [...]

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:

sandbox.eval("alert('Hello!')"); // => Hello!
try {
    var goodbye = "Goodbye!";
    sandbox.eval("alert(goodbye)"); // => ERROR!
} catch (error) {
    alert("ERROR!");
}

What prevents the sandboxed code from doing this:

sandbox.eval("alert('Hello!')"); // => Hello!
try {
    var goodbye = "Goodbye!";
    sandbox.eval("alert(parent.goodbye)"); // => Goodbye!
} catch (error) {
    alert("ERROR!");
}

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.

I use this tech to make a package/import system.

  • Comment by: hax
  • Posted:

I need to use Iframe with document.write() to dynamically generate the html containing the media player depending on whether QT/WMP/Real is selected. Can you please help?

  • Comment by: Anil
  • Posted:

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:

if (!iframeWin.eval && iframeWin.execScript) {
	iframeWin.execScript("");
}
  • Comment by: Tim Down
  • Posted:

My above example is slightly wrong – IE won’t let you execute the empty string as script, so the example should read something like

if (!iframeWin.eval && iframeWin.execScript) {
	iframeWin.execScript("null");
}
  • Comment by: Tim Down
  • Posted:

[...] (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.

/*** VOID globalEval (STRING _jsString) ***/
function globalEval (_jsString)
{
	if (typeof _jsString != "string")
	{
		return false;
	}

	// Check whether window.eval executes code in the global scope.
	window.eval("var __INCLUDE_TEST_1__ = true;");
	if (typeof window.__INCLUDE_TEST_1__ != "undefined")
	{
		delete window.__INCLUDE_TEST_1__;
		window.eval(_jsString);
	}
	else if (typeof window.execScript != "undefined")	// IE only
	{
		window.execScript(_jsString);
	}
	else
	{
		// Test effectiveness of creating a new SCRIPT element and adding it to the document.
		this._insertScriptTag = function (_jsCode) {
			var _script = document.createElement("script");
			_script.type = "text/javascript";
			_script.defer = false;
			_script.text = _jsCode;
			var _headNodeSet = document.getElementsByTagName("head");
			if (_headNodeSet.length)
			{
				_script = _headNodeSet.item(0).appendChild(_script);
			}
			else
			{
				var _head = document.createElement("head");
				_head = document.documentElement.appendChild(_head);
				_script = _head.appendChild(_script);
			}
			return _script;
		}
		var _testScript = this._insertScriptTag("var __INCLUDE_TEST_2__ = true;");
		if (typeof window.__INCLUDE_TEST_2__ == "boolean")
		{
			_testScript.parentNode.removeChild(_testScript);
			this._insertScriptTag(_jsString);
		}
		else
		{
			// Check whether window.setTimeout works in real time.
			window.setTimeout("var __INCLUDE_TEST_3__ = true;", 0);
			if (typeof window.__INCLUDE_TEST_3__ != "undefined")
			{
				delete window.__INCLUDE_TEST_3__;
				window.setTimeout(_jsString, 0);
			}
		}
	}

	return true;
}
  • Comment by: Erik
  • Posted:

Hi Dean,

Nice post… yet another testament to IE being brain dead.

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:

var code = 'var greet = "Hello "; alert(greet+wizard)';
var context = new Function('context', 'with(context){'+code+'}');
context.wizard = 'Enoch Root';
context(context);

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:

var path = '/jslib/crypt.js';
var req = new XMLHttpRequest('GET', path, false);
if (req.status == 200) {
  var context = new Function(
    'context', 'with(context){'+req.responseText+'}'
  );
  context.debug = function(mesg) { alert(mesg) };
  context(context);
}

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.

Cheers.

  • Comment by: Richard Hundt
  • Posted:

@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).

  • Comment by: Stefan Van Reeth
  • Posted:

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.

However I’ve found bug with your implementation:

sandbox.eval('var foo = 10');
alert(sandbox.eval('foo'));

This throws an error in mozilla, but runs ok in IE.

Here is a fixed version:

var MSIE/*@cc_on =1@*/, // sniff
    iframe = document.createElement("iframe");

iframe.style.display = 'none';
document.body.appendChild(iframe);

var frame_win = frames[frames.length - 1];
if (MSIE) {
    frame_win.document.write('<script><\/script>'); // activating Global object
    safe_eval = frame_win.eval;
} else {
    function safe_eval()
    {
        return eval.apply(frame_win, arguments);
    }
}

safe_eval function is public. It runs in ie6 and mozilla 1.5, I haven’t tested different browsers yet.

  • Comment by: YuppY
  • Posted:

Oops… Mozilla evaluates everything in parent scope now :(

  • Comment by: YuppY
  • Posted:

Sorry for flooding…

Error described above is solved by calling window.eval instead of eval in mozilla. Your code could be fixed like this:

frames[frames.length - 1].document.write(
    "<script>"+
    "var MSIE/*@cc_on =1@*/;"+ // sniff
    "parent.sandbox=MSIE?this:{eval:function(s){return window.eval(s)}}"+
    "<\/script>");
  • Comment by: YuppY
  • Posted:

[...] Sandboxing JavaScript Using (tags: javascript sandbox iframe articles) [...]

@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.

  • Comment by: Riddle
  • Posted:

hey this better work or imma be pissed! nothing else is working :(

  • Comment by: katie hen
  • Posted:

[...] 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) [...]

[...] to SQL,ADO,C# – Environment SpecialFolder Usage in C# – Common StringBuilder Mistake in C# .NET – Sandboxing JavaScript Using <iframe> – MayHorizontal JavaScript Accordion 1kb – 40 Tips for optimizing your php code – Modern JavaScript [...]

Thanks ! Very nice code, perfect for my actual needs. Merci !

  • Comment by: Chris
  • Posted:

Even if a script is placed inside an iframe, it can still access any object in the document using the window.parent object.. Am I getting this issue right?

  • Comment by: Julius
  • Posted:

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).

  • Comment by: Morris
  • Posted:

[...] in the web browser. Silly, I know. This is made possible by a wonderful hack of using iframes (http://dean.edwards.name/weblog/2006/11/sandbox/) to provide a fresh JS environment to each new “program”, along with a couple of basic [...]

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:

frames[frames.length - 1].document.write(
	        "<script>"+
	        "parent.createTable=function(json){"+
	            createTable + // createTable is a stringified function that returns a table element.
	        " return createTable(json)};"+
	        "</script><body></body>"
        );

As far as I can tell this will only be a temporary workaround as the script stack space quota is reached in the iframe as the data I’m using grows. I just thought I’d share my use of the technique.

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.

URGENT

  • Comment by: nithya
  • Posted:

Uh nithya… in case you haven’t noticed, PDF’s are displayed using the Adobe plugin.

  • Comment by: inportb
  • Posted:

WTF

  • Comment by: msp
  • Posted:

So I know it’s been ages since a post on this topic, but given the current hotness of way to get CommonJS modules running in the browser, I thought of this topic once more.

In fact, I’m creating a little script that wraps up the functionality in this article into a nice little API that can be built on top of. Check it out if you care:

http://github.com/TooTallNate/SandboxJS

[...] 注1:http://dean.edwards.name/weblog/2006/11/sandbox/注2:http://jsfiddle.net/注3:http://blog.csdn.net/aimingoo/archive/2009/09/08/4532496.aspx [...]

I may have a use for this inside of jPaq. If I do use it, I will definitely give you credit. :)

  • Comment by: Chris West
  • Posted:

Leave A Comment

Line and paragraph breaks automatic, email address never displayed. Some HTML allowed.