Running students' code safely: what a sandbox can and cannot do
A coding judge runs code written by strangers. What we locked down in CirruSoul Arena, what is still open, and what we would use before a large launch.
Running students' code safely: what a sandbox can and cannot do
A coding judge has an unusual job: it runs code written by people you do not know, on your own server, many times a day. Whatever else you build, this part has to be treated as hostile input. This is how the judge in CirruSoul Arena works, and just as importantly, where its limits are.
What each submission gets
Every submission runs in its own throw-away Node.js process, started with the built-in permission model switched on. In practice that means:
- No file access. The process may read only the single runner script that feeds it test cases. It cannot read the application's files, configuration or secrets.
- No child processes. Student code cannot spawn a shell or another program.
- An empty environment. The process is started with no environment variables at all, so there are no database URLs or keys for it to find, even by accident.
- A small memory ceiling. The heap is capped at 64 MB, so an infinite loop that allocates memory fails quickly.
- A wall-clock kill. If the process runs longer than its time limit it is killed from outside. Student code cannot extend its own deadline.
Expected answers are not typed by hand
Another quiet source of bugs is the answer key. In our seed data the expected output of each problem is produced by running a reference solution, not by someone typing the answer. That removes a whole class of mistakes where the key is simply wrong.
What this does not do
This is the honest part. The Node permission model restricts files and child processes, but blocking network access only arrives in newer Node versions (Node 25 and later). On an older Node, submitted code could still try to open network connections.
That matters differently depending on where the judge runs:
- On a small server shared with other services, the empty environment and the missing file access limit the damage, and anything sensitive on the same machine should require a password even on localhost. We set a password on Redis for exactly this reason.
- Before a large public launch, run the judge somewhere isolated: a separate machine, containers with networking disabled, or a dedicated judging service such as Judge0.
A checklist we use
- Run untrusted code in a separate process, never in the web server itself.
- Give it no environment, no files and no child processes.
- Cap memory and time, and enforce time from outside the process.
- Assume network access is possible unless you have blocked it, and protect every local service with a password.
- Move the judge off your main server as traffic grows.
Building a platform that runs other people's code, such as a coding school, an assessment tool or a competition? We have done the groundwork, and we can help you choose the right level of isolation for your scale. Start a conversation.
- #Security
- #Node.js
- #EdTech
- #Sandbox
- #Arena