How to safely run user-supplied Javascript code inside the browser?
After much consideration and with the help of other posters in this thread (thank you so much for your help!), I found a first bunch of answers to my questions. I am re-writing my answer here though, because it summarizes the concepts and also gives you some actual code to experiment with.
Generally, there are two solutions to this problem: We can either use iframe
or Worker
to run code in an isolated environment, thus making it impossible to read or write the current page's information (which is my first major security concern).
Caja + Closure
There are more complete sandbox solutions such as Google Caja, which (by default) also runs its code in an iframe
. Caja does not only sandbox JS, but also HTML and CSS. However, it does not seem very actively maintained anymore.
Update: Caja has been deprecated since the original posting. It has been replaced with the Closure Toolkit, specifically Closure Library and Closure Templates.
WebWorker + Blacklisting
As proposed by Juan Garcia, I am going with the web worker API, but that is not the complete story. Even though, the worker cannot directly access anything from the hosting page, there are still quite a few security risks. This site lists all built-ins available in a Worker's context.
This JSFiddle demonstrates a way to run a string of code outside the window
's context without having to go through the server, which is still unsafe, as pointed out in the comments.
I have extended on that and employed a black-list based approach to disable all outside communication by taking away all of the following:
Worker
WebSocket
XMLHttpRequest
importScripts
Webworker + Whitelisting
The actually safest approach however, is a white-list approach, (mentioned here), as it will stay safe into the future (given that the semantics of any whitelisted globals will never change in such a way that they allow more access in any future release; which is a somewhat reasonable assumption).
I ended up implementing the whitelist approach in my in-browser WumpusGame that allows you to write your own AI script while playing the game, a few years back. Source code here. You can see that the Whitelist is maintained in GuestScriptContext while the workers are managed by the HostScriptContext.
The GameScriptContext shows how you can use it.
It has several features, including guest<->host communication, guest can execute non-privileged actions (and if it has a security token even privileged actions) on the host, and a lot more.
NOTE: It does not play well with some extensions, as they will prevent overriding globals (when I try to run it, it complains about TEMPORARY
being one of them). The fix would be to either whitelist those, or figure out how to make it play nice with modern browser security features, or just security-focused extensions, such as AdBlock etc.
The game is theoretically playable here, but because of reasons above it does not seem to work in Chrome (and I haven't tried other browsers yet).
Some more references:
- The W3Schools web worker tutorial.
- This thread and this html5rocks tutorial on how to run a
Worker
without separate files.
You can do this with workers. The best thing about workers is that they run in a different process, so if that user code gets into an infinity loop will not hang your page. The only interface with the worker is a message interface, so strings only are exchanged which is extremely secure but limited in some situations.
As not all currently used browsers support workers, iframes are a helpful alternative. You can create them in javascript, set display to 'none', add them to the document, get the eval function from the iframe contentWindow, then 'destroy' the iframe, like setting the outerHTML to '' (empty string). It is tricky, as sometimes the iframe window get garbage collected, but if you manage to do it right, you will have an eval that has a different global object bound to it, which has an empty document. You can then create an interface with the code running it that global with the code running in your page global. The security depends on how do you implement that interface. You should not expose your global object, that means you should nullify the 'parent' property in the iframe global before running any user code there. You should also make sure you don't pass any Element object from the page document to the code running in the iframe global, or the user running that code will be able to navigate your whole document through the parentElement property, you should wrap them to your Element interface using Object.defineProperty for example. It is a lot of work, it did it once, but it is not sure that will be compatible between browsers. For example, if I well remember and I am not making a mistake, Chrome let me nullify the parent property in the iframe global, while in Safari on an iPad I wasn't able. Sorry I don't have any code to share with you right now, I will post it if I have time. However, iframes scripts run in the same process than the page, which it means that an infinite loop will hang your page scripts at well, and today browser compiling the code to machine instructions can effectively hang the client os (depending of the browser and the os).
Third you can use a sandbox, there are JavaScript self interpreters or like in T.J. Crowder comment compilers that will simplify that for you, but the side effect is that it will run slower, specially interpreters.
IMHO, I think the folks at ECMA should better worry less about ugly arrow syntax and stuff like that and implement a secure way of running process with different global that have an interface an specific document element and its descendants but unable to get that element parent and access to the document. This will effectively enable a way of writing JavaScript plugins and also secure advertisement system.