1

I'm working on an IDE that should run a user's JavaScript in the Browser. I don't want to just eval arbitrary code, especially as code may be shared between users. From here and other sources, I learned iframes to be the likely tool for this.

On the other hand, things like Caja are also suggested, and I haven't found any example code particularly on how to use iframes for this purpose. This makes me feel too uneasy to just accept what I have come up with, without any feedback.

To reiterate, I want to:

  • let users write small JavaScript programs that interact with the "world" over a small set of allowed APIs,
  • execute them in their browser,
  • make this secure for users who try code they didn't write themselves.

Here's what I have come up with: index.html can launch a sandboxed iframe for src child.html. The iframe is sent the user's code, which is then eval'd and invokes some APIs (dispatched via postMessage). (the origin checks are missing and targetOrigin is '*' because I'm testing on file://.)

index.html

<html>
<head>
</head>
<body>
<div id="mountpoint">
</div>
<script type="text/javascript">
const handlers = {
    // print program output to the console
    print: function print(source, text) {
        console.log(event.data.payload);
    },
    // clean up an iframe after execution
    exit: function exit(source) {
        const i = frames.findIndex(function(item) {
            return item[0] == source;
        });
        frames[i][1].remove();
        delete frames[i];
    },
};

// dispatch commands from the iframe
window.addEventListener('message', function receiveMessage(event) {
    // if (event.origin !== "http://example.com")
    //   return;

    const handler = handlers[event.data.command];
    if(handler) {
        handler(event.source, event.data.payload);
    }
}, false);

frames = [];

// create a new iframe and send it code
function execute(code) {
    let frame = document.createElement('iframe');
    frame.setAttribute('src', 'child.html');
    frame.setAttribute('sandbox', 'allow-scripts');
    frame.style.display = 'none';
    document.getElementById('mountpoint').appendChild(frame);

    frames.push([frame.contentWindow, frame]);

    function sendMessage(command, payload) {
        frame.contentWindow.postMessage({
            command: command,
            payload: payload,
        }, '*');
    }

    // wait for the iframe to load before sending `execute`
    setTimeout(function() {
        sendMessage('execute', code);
    }, 0);
}

execute(`
sendMessage('print', 'this is the child!!');
`);
</script>
</body>
</html>

child.html

<html>
<head>
</head>
<body>
<script type="text/javascript">
const handlers = {
    // execute the given program
    execute: function execute(source, code) {
        function sendMessage(command, payload) {
            event.source.postMessage({
                command: command,
                payload: payload,
            }, '*');
        }

        try {
            // run the code
            eval(code);
        } finally {
            // ask parent to clean up
            sendMessage('exit');
        }
    },
};

// dispatch commands from the parent
window.addEventListener('message', function receiveMessage(event) {
    // if (event.origin !== "http://example.com")
    //   return;

    const handler = handlers[event.data.command];
    if(handler) {
        handler(event.source, event.data.payload);
    }
}, false);
</script>
</body>
</html>

Is this a proper sandboxing approach? Are there ways to escape the sandbox with what I've written here? As a bonus, what better results could I get from using Caja, or other libraries that get mentioned with respect to this topic?

  • 1
    1) Your question has little to do with information security. You would get much more answers on the central Stack Overflow site. 2) Why do you want to implement any sandboxing? All common browsers do provide such sandboxing. Script from your site cannot affect any other pages like web banking, web sites of social networks etc. In the worst case the code one users loaded is affected by the code he loaded from another user. But in such case it is sufficient to clean up browser local storage and reload the original code. Please explain better what problem are you trying to solve. – mentallurg Aug 25 at 14:23
  • 1) I was thinking about maybe Code Review instead of here, but it seems pretty on topic to me according to the help center. 2) As the title says, I want to isolate user programs from my application. I am fully aware that the browser isolates websites from each other and the operating system. But user code naively executed would have the ability to access my application and users' accounts to my application. – Silly Freak Aug 25 at 14:58
  • @mentallurg I think this question fits in well here. Not sure it would be on topic on SO. – Anders Aug 25 at 17:40
  • @Anders: It is not off topic here. But the author could get much more responses if the question were on SO. But to me it fine. – mentallurg Aug 25 at 19:44
  • 1
    @SillyFreak To be honest, I don't know. But I would still use a "real" separate origin, to protect myself against silly mistakes like misspelling sandbox, browser bugs, or older browsers not supporting the attribute (like IE 8 and 9). You could make a case that it is overkill, but sometimes overkill is good. – Anders Aug 26 at 7:33
2

Use 2 separate hosts. On the IDE host deploy you services that provide IDE functionality: Editing of JavaScript, saving, comparing, etc. When user want to see how his code works, deploy it to another host, say USER host. Browser should load users code from this USER host and execute it. If you need to call some services on USER host from your IDE script, allow CORS requests on your USER host.

  • Thanks for the answer! I thought I said that, but apparently I didn't: I want to avoid involving the server side in code execution. The IDE should work offline as much as possible. sandbox will "treat the content as being from a unique origin" (w3schools.com/tags/att_iframe_sandbox.asp), so if that's your only caveat to my code, it should be fine, right? – Silly Freak Aug 26 at 6:36
  • 1) "avoid involving the server side in code execution" - JavaScript will be executed in browser. But you said "code may be shared between users". I assume this sharing will be done via server: One user saves or some publishes his JS to server, the other user loads this. For this you need a server logic. 2) You want to isolate the IDE code from the user code. The simplest way to do that is to load the IDE code from one host (domain) and load user code from another domain. Then you get isolation for free, because browser does it for you. – mentallurg Aug 28 at 0:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.