Skip Navigation
Tyrone Williams

Running User Javascript Code on Server

Recently I had the idea to create a code challenge website that allows users to pair up and tackle a coding challenge. The biggest challenge with this project is securely testing user code. Some examples of user code that can cause serious problems are:

while(true)
process.exit(1)
fs.readFileSync('...', '...')

These problems, if not handled correctly, could cause slower responses times or more dangerously an attacker having control over the server. After looking for inspiration from other sites, I found main solutions were in browser with the use of iframes, like JSFIddle or rendering the code on server, like Codewar. The limitation with the front-end solutions is that it can't be used for other languages outside of Javascript. Since I plan to add support for a few other languages in the future and speed isn't a concern, a more backend solution seems more appealing.

After some more digging, I came across this blog post, which was a step in the right direction for the project.

Solution

The general solution consists of creating a sandbox-like environment for the code to be tested in to avoid possible security issues. The solution below is specifically for Javascript/NodeJS code, but can work other languages with a few tweaks.

  1. 1. Code test is requested

    When a code test is requested, a message is sent using RabbitMQ to a separate server. Having two separate servers improves the overall security, while also making sure that running tests doesn't affect other user actions like fetching API data or socket interactions.

  2. 2. Docker

    The testing server receives and unpacks the message, then creates a container with the user code, testing tool, and test(s). Using docker allows for the process to be separate from the main service, which helps with easier cleanup and security. I was also able to limit the time a docker container can be running before notifying the user that their test timed out (8 seconds currently).while(true) { }Crisis averted

  3. 3. Sandbox all the Code

    Specific to Javascript/Node, the container executes the test command and runs the user's code using VM2. Node does have an in-build sandbox environment called VM, however according to it's docs:The vm module is not a security mechanism. Do not use it to run untrusted code.SourceThis is where VM2 comes in, which allows for sandboxing Javascript/Node code by limiting what functionality, like process.exit() or fs.read(), the code has access to. In the future, the launching container will be specific to the language and the method of sandboxing and testing will change.

  4. 4. Testing

    Running tests against the user code was tricky due to needing to get the results from the container to send back to the user. I tested with Jest, however I wasn't completely sure how secure it would be to let jest write it's output to a file. So instead opted to write a bare bones testing tool that printed the results to the console in the docker container. This did mean I had to limit the user from being able to console themselves, although I don't see a reason they really need it yet.

  5. 5. Results

    Up to this point, I was able to run the code safely, but was having trouble getting the results in a place easy to read and parse. Currently I'm using a custom testing tool to run tests and console the results in JSON and reading directly from the console. Later, I would like to improve this by using either Mocha or Jest to run tests and write the results to a file, but for now the custom testing tool works.

Conclusion

While this was mostly to be used for a demo project, there were many challenging aspects to keep the project safely to deploy. The project source file can be found on my Github and you can check out the deploy version here.