Skip to main content

Security Model

Runtime is designed with security as a primary concern. Every code execution runs in a completely isolated Docker container with strict resource limits and automatic cleanup to prevent abuse and system compromise.

Isolation Layers

The system implements multiple layers of isolation:
1

Container Isolation

Each execution runs in a separate Docker container, providing process and filesystem isolation
2

Resource Limits

Strict memory and CPU limits prevent resource exhaustion attacks
3

Temporary Filesystem

Code is written to unique temporary directories that are deleted after execution
4

No Network Access

Containers run without network capabilities (default Docker behavior)

Container Isolation

Fresh Container Per Execution

Every code execution creates a brand new Docker container. Location: DockerExecutorUtil.java:51-58
CreateContainerResponse container = dockerClient.createContainerCmd(dockerImage)
        .withCmd(command)
        .withHostConfig(HostConfig.newHostConfig()
                .withBinds(new Bind(tempDirPath, new Volume("/code")))
                .withMemory(128 * 1024 * 1024L)
                .withNanoCPUs(500_000_000L))
        .withWorkingDir("/code")
        .exec();
Containers are ephemeral - they exist only for the duration of code execution and are immediately destroyed afterward.

Container Lifecycle

Location: DockerExecutorUtil.java:62-87
// Start container
dockerClient.startContainerCmd(containerId).exec();
System.out.println("Docker container started!!!");

// Wait for completion
dockerClient.waitContainerCmd(containerId).start().awaitStatusCode();

long executionTime = System.currentTimeMillis() - startTime;

// Capture output
dockerClient.logContainerCmd(containerId)
    .withStdOut(true)
    .withStdErr(true)
    .exec(/* ... */).awaitCompletion();

// Clean up container
dockerClient.removeContainerCmd(containerId).exec();
System.out.println("Docker container removed!!!");
Containers are always removed, even if execution fails. This prevents accumulation of stopped containers.

Resource Limits

Memory Limit: 128MB

Each container is limited to 128MB of RAM.
.withMemory(128 * 1024 * 1024L)  // 128MB in bytes
128MB is sufficient for most code snippets while preventing memory-intensive attacks:
  • Prevents infinite array allocations
  • Limits recursive function call depth
  • Stops memory leak exploits
  • Protects host system memory
If code exceeds this limit, the container is killed by Docker’s OOM killer.

CPU Limit: 0.5 Cores

Each container is limited to half a CPU core.
.withNanoCPUs(500_000_000L)  // 0.5 cores (500 million nanoseconds)
Docker measures CPU in nanoseconds:
  • 1 CPU core = 1,000,000,000 nanoseconds
  • 0.5 cores = 500,000,000 nanoseconds
  • 0.1 cores = 100,000,000 nanoseconds
This prevents:
  • Infinite loops from consuming all CPU
  • CPU-intensive cryptomining attempts
  • Fork bomb attacks

Resource Limit Enforcement

HostConfig.newHostConfig()
    .withBinds(new Bind(tempDirPath, new Volume("/code")))
    .withMemory(128 * 1024 * 1024L)      // Memory: 128MB
    .withNanoCPUs(500_000_000L)           // CPU: 0.5 cores

Filesystem Isolation

Temporary Directories

Each execution gets a unique temporary directory on the host. Location: DockerExecutorUtil.java:41-43
String tempDirPath = System.getProperty("java.io.tmpdir") + "/code_" + UUID.randomUUID();
Path tempDir = Paths.get(tempDirPath);
Files.createDirectories(tempDir);

Example Directory

/tmp/code_a3f8c9d2-4b7e-4a1f-9c3e-8d2f1a6b5c4e/
└── Main.java

Volume Binding

The temporary directory is mounted read-write into the container.
.withBinds(new Bind(tempDirPath, new Volume("/code")))
.withWorkingDir("/code")
Host to Container Mapping:
Host:      /tmp/code_a3f8c9d2-4b7e-4a1f-9c3e-8d2f1a6b5c4e/
             ↓ (bind mount)
Container: /code/
Only the specific temporary directory is accessible inside the container. The rest of the host filesystem is isolated.

Automatic Cleanup

Temporary directories are deleted after execution completes. Location: DockerExecutorUtil.java:88
deleteDirectory(tempDir.toFile());
Cleanup Implementation: Location: DockerExecutorUtil.java:143-149
private void deleteDirectory(File dir) {
    File[] files = dir.listFiles();
    if (files != null) {
        for (File f : files) f.delete();
    }
    dir.delete();
}
No Persistence: Code and execution artifacts are never stored permanently. Each execution is stateless.

Process Isolation

Working Directory

All code execution happens in the /code directory inside the container.
.withWorkingDir("/code")
This ensures:
  • Code cannot access system directories
  • Compiled binaries are contained
  • Output files are isolated

User Permissions

Containers run with default Docker user permissions (typically root inside the container, but isolated from host).
While the container process runs as root inside the container, it’s isolated from the host system through Docker’s namespace isolation.

Network Isolation

No Network Access

By default, containers are created without network capabilities.
// No network configuration = no network access
CreateContainerResponse container = dockerClient.createContainerCmd(dockerImage)
    .withCmd(command)
    .withHostConfig(/* ... */)
    .exec();
The absence of network configuration means containers cannot:
  • Make outbound HTTP requests
  • Download additional packages
  • Communicate with external services
  • Scan internal networks

Language-Specific Security

Trusted Base Images

All Docker images are official, trusted images from Docker Hub. Location: DockerExecutorUtil.java:119-127
private String getDockerImage(String language) {
    return switch (language.toLowerCase()) {
        case "java"   -> "eclipse-temurin:17";  // Official Eclipse Temurin
        case "python" -> "python:3.11";         // Official Python
        case "c", "cpp" -> "gcc:latest";        // Official GCC
        case "javascript" -> "node:18";         // Official Node.js
        default -> throw new IllegalArgumentException("Unsupported language: " + language);
    };
}

eclipse-temurin:17

Official OpenJDK build from Eclipse Foundation

python:3.11

Official Python Software Foundation image

gcc:latest

Official GNU Compiler Collection image

node:18

Official Node.js Foundation image

Command Injection Prevention

Commands are built programmatically, not from user input concatenation. Location: DockerExecutorUtil.java:129-141
private String[] getRunCommand(String language, String code) {
    return switch (language.toLowerCase()) {
        case "java" -> {
            String className = extractJavaClassName(code);
            // Class name is extracted via regex, not user-controlled
            yield new String[]{"sh", "-c", "javac " + className + ".java && java " + className};
        }
        case "python" -> new String[]{"sh", "-c", "python main.py"};
        case "c"      -> new String[]{"sh", "-c", "gcc main.c -o main && ./main"};
        case "cpp"    -> new String[]{"sh", "-c", "g++ main.cpp -o main && ./main"};
        case "javascript" -> new String[]{"sh", "-c", "node main.js"};
        default -> throw new IllegalArgumentException("Unsupported language: " + language);
    };
}
File names are validated and constructed programmatically. User code goes into the file contents, not the file name or command.

Error Handling & Security

Graceful Failure

All exceptions are caught and returned as safe error messages. Location: DockerExecutorUtil.java:95-97
catch (Exception e) {
    return ApiResponse.error("Execution failed: " + e.getMessage());
}
Error messages are sanitized to prevent information leakage about the host system.

Container Cleanup on Failure

Even if execution fails, containers are still removed.
try {
    // Container creation and execution
} catch (Exception e) {
    return ApiResponse.error("Execution failed: " + e.getMessage());
} finally {
    // Container is removed in the success path
    // Need to ensure cleanup even on exceptions
}
The current implementation removes containers in the success path. For production, consider adding a finally block to guarantee cleanup.

Security Best Practices

Regularly scan Docker images for vulnerabilities:
docker scan eclipse-temurin:17
docker scan python:3.11
docker scan gcc:latest
docker scan node:18
Monitor container resource usage to detect abuse:
docker stats --no-stream
Implement API rate limiting to prevent abuse:
  • Limit requests per IP address
  • Limit concurrent executions
  • Track execution metrics
Validate all user inputs:
  • Maximum code size (e.g., 10KB)
  • Allowed language values
  • Character encoding validation

Security Checklist

1

Container Isolation

Each execution runs in a fresh, isolated Docker container
2

Resource Limits

128MB memory and 0.5 CPU cores per container
3

Network Isolation

No network access from containers
4

Filesystem Isolation

Unique temporary directories with automatic cleanup
5

Trusted Images

Official Docker images from verified publishers
6

No Persistence

All containers and files are deleted after execution

Potential Improvements

Execution Timeout

Add maximum execution time limit (e.g., 30 seconds)

Read-Only Filesystem

Mount code directory as read-only where possible

User Namespaces

Run containers with non-root user mapping

Seccomp Profiles

Apply restrictive seccomp security profiles

Next Steps

Architecture Overview

Return to the architecture overview

Deployment Guide

Learn how to deploy Runtime securely

Build docs developers (and LLMs) love