Anga205/ContainerizationAPI

An API to execute code snippets in multiple languages and return the output and execution metrics

Go

3

68 commits

updated Oct 9, 2026

See the code

See what people are saying

SourceMessageScoreDate

A code execution sandbox in Go without Docker, just plain linux tooling (r/golang)

I built a Containerization API that executes untrusted code and returns output plus memory and CPU metrics. Instead of running a container per submission, it creates the boundary directly with Linux primitives: PID, mount, network, and user namespaces, a chroot into a minimal per-language rootfs,…

3

Oct 20, 2026

README

ContainerizationAPI

The general idea of this project is to provide a service similar to the CodeExecutionAPI by The Alcoding Club but to manually implement the containerization using cgroups, namespaces, chroot and a bunch of other unix/linux utility tools. This will help accomplish faster execution times and cut down on sandboxing overhead

Requirements

To use this API, there aren't really any requirements, you just need to be able to send HTTP requests To Host this API, you need to have Linux or WSL (if you're on windows) or MacOS, because this project makes heavy use of unix-specific tools.

Usage

Using this API is designed to be as simple as possible, at it's core, it's just an HTTP request that you send to /v2/execute, or /execute

The following are the routes that this API makes available, click one to see it's documentation


POST Route: /execute


This route is a drop in replacement for CodeExecutionAPI, if you send any request to the API defined in that repository, it's actually going to be handled by this codebase (because it does the same exact thing but faster)

This route takes 4 fields:

ParameterWhat It DoesDefault & LimitsHow To Use
languageSpecifies which programming language to execute your code in. The API needs to know which compiler/interpreter to use.Required (no default)

Supported values:
• python - Python 3.x
• cpp - C++17 (G++)
• c - C11 (GCC)
• java - Java (OpenJDK)
Pass as a string. Must exactly match one of the supported values (all lowercase).

Example: "language": "python"
codeThe actual source code you want to run. This is what gets executed in the isolated environment.Required (no default)

No explicit size limit
Pass as a single string. Use \n for line breaks. Escape any quotes inside your code.

Single line: "code": "print('Hello')"

Multi-line: "code": "def hello():\n print('world')\nhello()"
timeoutMaximum execution time. If your code runs longer than this, it gets killed immediately to prevent infinite loops or runaway processes.Default: 5 seconds
Max: 60 seconds
Pass as an integer (seconds). Your code will be terminated the moment it exceeds this limit.

On timeout: Returns "Execution Timed Out" error with no output.

Example: "timeout": 10 (runs for max 10 seconds)
max_memoryMaximum RAM your code can use. If your code allocates more memory than this, it gets killed to prevent memory exhaustion.Default: 32768 KB (32 MB)
Max: 131072 KB (128 MB)
Pass as an integer (KB). Memory is monitored in real-time during execution.

On limit exceeded: Returns "Memory limit exceeded" error.

Example: "max_memory": 65536 (allows up to 64 MB)
inputsProvides STDIN data for programs that read user input. Each array element becomes one line of input, delivered in order as your code calls input(), scanf(), etc.Default: [] (no input)

No explicit size limit
Pass as an array of strings. Each string is one line of input. Elements are consumed sequentially.

Single input: "inputs": ["bob"]

Multiple inputs: "inputs": ["bob", "alice", "25"]

Use this if your code reads from STDIN

Note: the above mentioned default and max values can be modified by editing the environment variables mentioned at the bottom of this README


Request body format (Example 1):

{
    "language": "python",           // this is the language your code is going to be executed in
    "code": "print('Hello World')"  // a string containing the code itself, with lines separated by \n
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "print('\''Hello World'\'')"
}'

Response body format (Example 1):

{
    "output": "Hello World\n",  // Output of the code
    "error": "",                // If any error occurs during execution
    "memory_used": "13808 KB",  // RAM used (in KB)
    "cpu_time": "9.166097ms"    // as in 9.16 milliseconds
}

Request body format (Example 2):

{
    "language": "python",
    "code": "import time\nprint('Hello World')\ntime.sleep(5)",
    "timeout": 2       // in seconds (defaults to 5, max 60)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import time\nprint('\''Hello World'\'')\ntime.sleep(5)",
    "timeout": 2       
}'

Response body format (Example 2):

{
    "output": "",                   // No output is returned on timeout
    "error": "Execution Timed Out", // Error message in case of timeout
    "memory_used": "15856 KB",      // RAM used (in KB)
    "cpu_time": "2.00014463s"      // Time before code was terminated
}

Request body format (Example 3):

{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000        // in KB (defaults to 32768, max 131072)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000
}'

Response body format (Example 3):

{
    "output": "",
    "error": "Memory limit exceeded",   // code is terminated the moment it takes more memory than allocated
    "memory_used": "136636 KB",
    "cpu_time": "115.779962ms"
}

Request body format (Example 4):

{
    "language": "python",
    "code": "a = input()\nprint(f'first value entered is {a}.')\nb=input()\nprint(f'second value entered is {b}.')",
    "inputs": [
        "bob",
        "alice"
    ]
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "a = input()\nprint(f'\''first value entered is {a}.'\'')\nb=input()\nprint(f'\''second value entered is {b}.'\'')",
    "inputs": [
        "bob",
        "alice"
    ]
}'

Response body format (Example 4):

{
    "output": "first value entered is bob.\nsecond value entered is alice.\n",
    "error": "",
    "memory_used": "24504 KB",
    "cpu_time": "13.625964ms"
}

Note: the route /execute also calls /v2/execute under the hood, its just that the formatting for the input and output are slightly more easy to read and understand.


POST Route: /v2/execute


This route takes 4 fields:

ParameterDescriptionDefaultMaximum
languageThe programming language of the code snippet to be executed.Required (no default)N/A
codeThe actual source code to be executed. Use \n to represent line breaks within the string. The code runs in an isolated environment.Required (no default)No explicit limit
timeoutMaximum execution time in nanoseconds. If the code runs longer than this, it will be forcefully terminated.5,000,000,000 ns
(5 seconds)
60,000,000,000 ns
(60 seconds)
max_memoryMaximum memory allocation in KB (kilobytes). If the code exceeds this limit, it will be terminated immediately.32,768 KB
(32 MB)
131,072 KB
(128 MB)
inputsA single string containing all STDIN data. If your code reads input (e.g., input() in Python, scanf() in C, Scanner in Java), provide the input here. For multiple lines, separate them with \n.Empty string
(no input)
No explicit limit

Note: the above mentioned default and max values can be modified by editing the environment variables mentioned at the bottom of this README


Request body format (Example 1):

{
    "language": "python",
    "code": "print('Hello World')"
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "print('\''Hello World'\'')"
}'

Response body format (Example 1):

{
    "output": "Hello World\n",  // output from STDOUT
    "error": "",                // output from STDERR
    "cpu_time": 10283909,       // run time in nanoseconds (10ms or 0.01 seconds)
    "memory_used": 25116        // RAM used (in KB)
}

Request body format (Example 2):

{
    "language": "python",
    "code": "import time\nprint('Hello World')\ntime.sleep(5)",
    "timeout": 2000000000       // in nanoseconds (defaults to 5000000000, max 60000000000)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import time\nprint('\''Hello World'\'')\ntime.sleep(5)",
    "timeout": 2000000000
}'

Response body format (Example 2):

{
    "output": "",                   // No output is returned on timeout
    "error": "Execution Timed Out", // Error message in case of timeout
    "cpu_time": 2000003280,         // Time (in nanoseconds) before code was terminated
    "memory_used": 15856,           // RAM used (in KB)
}

Request body format (Example 3):

{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000        // in KB (defaults to 32768, max 131072)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000
}'

Response body format (Example 3):

{
    "output": "",
    "error": "Memory limit exceeded",   // code is terminated the moment it takes more memory than allocated
    "cpu_time": 116936122,
    "memory_used": 136628
}

Request body format (Example 4):

{
    "language": "python",
    "code": "a = input()\nprint(f'first value entered is {a}.')\nb=input()\nprint(f'second value entered is {b}.')",
    "inputs": "bob\nalice"
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "a = input()\nprint(f'\''first value entered is {a}.'\'')\nb=input()\nprint(f'\''second value entered is {b}.'\'')",
    "inputs": "bob\nalice"
}'

Response body format (Example 4):

{
    "output": "first value entered is bob.\nsecond value entered is alice.\n",
    "error": "",
    "cpu_time": 9058716,
    "memory_used": 25136
}

GET Route: /debug/config


This route returns the current server configuration, including memory limits and timeout settings. This is a debug route and must be enabled via the ENABLE_DEBUG environment variable.

Request format:

GET /debug/config
Click to copy curl command
curl --location 'https://codeapi.anga.codes/debug/config'

Response body format:

{
    "limits": {
        "max_timeout": 60000000000,
        "max_memory_limit": 131072,
        "default_timeout": 5000000000,
        "default_memory_limit": 32768
    },
    "globals": {
        "ram_limit": 1048576,
        "enable_debug_routes": false
    }
}

I'm making this as part of a bigger project to host my own coding contests on a platform a little better than HackerRank. The Alcoding Club's API is fine but I need one that's more efficient and has less overhead than firejail.

Click to expand the Self-Hosting Guide

Environment Variables

  • MAX_TIMEOUT: This is the maximum timeout that a request can set, if an incoming request has a higher timeout than the value you set here, then it will ignore the request's set timeout only be executed for MAX_TIMEOUT time.
    • this env variable uses the Go Duration String format e.g - 1m30s, 4h15s, 5s
    • if you dont set this env variable, it defaults to 60s
    • the public version of this api has this value set to 60s
  • MAX_MEMORY_LIMIT: Maximum RAM allocation that a request can be set to, if an incoming request tries to set to a higher memory limit, it will be ignored and reset to this value
    • this must be set to an unsigned integer (positive number) and is measured in Kilobytes (KB)
    • if you dont set this env variable, it defaults to 131072 which is 128 MB or 131072 KB
    • the public version of this api has this value set to 131072
  • DEFAULT_TIMEOUT: If an incoming request does not have a timeout parameter (i.e, request does not specify a time limit) then the sandbox will set the time limit to this value
    • same format as MAX_TIMEOUT
    • if you dont set this env variable, it defaults to 5s
    • the public version of this api has this value set to 5s
  • DEFAULT_MEMORY_LIMIT: If an incoming request does not have a max_memory parameter (i.e, request does not specify a memory limit) then the sandbox will set the maximum memory limit to this value
    • same format as MAX_MEMORY_LIMIT
    • if you dont set this env variable, it defaults to 32 MB
    • the public version of this api has this value set to 32 MB
  • GLOBAL_RAM_LIMIT: The total amount of RAM that is available globally to the server, cumulatively, the API will not allocate more RAM than what you define here, for example, if RAM limit if 1GB, and there are already 10 sandboxes that have reserved 100MB (either by default or by user-specification in the max_memory parameter) and an 11th process tries to reserve even more RAM, the server will NOT allocate that RAM
    • same format as MAX_MEMORY_LIMIT
    • default value is 1048576 or 1 GB
    • public version of this API sets this to 1 GB
  • ENABLE_DEBUG: if true, the server will expose debug routes (like /check-ram) for monitoring and debugging purposes
    • this can only be set to true or false
    • by default this is false
    • the public API has this set to true
  • GIN_MODE: whether to run the gin-gonic server in release mode or debug mode. For this API there won't be much of a performance difference regardless of which one you pick, and it is recommended that you read the docs for gin-gonic for more information
    • this can be set to release or debug
    • by default it is set to debug
    • the public API has this set to release
  • PORT: Which network port to run the server on
    • this can be set to any unsigned integer value between 0 and 65535
    • by default this is 8080
    • public API has this set to 8080

Note: if you are self-hosting this api, remember to run it with superuser permissions, or a user which has permissions to alter controlgroups and namespaces, normally you can achieve this just by using "sudo" before running the code

Optional: Security Verification

This security verification suite is intended only for those users who want to self-host this API, the tests detailed below are for verifying if the sandboxing implemented by the codebase for running arbitrary user code is compatible with the host where the tests are running, run these commands on your host to verify that it's suitable for the kind of sandboxing I've implemented here. If one or more of these tests is failing then its probably a good idea to self-host this api inside of another more hardened container/VM. The host running the publically deployed version of this API is completely compatible with the sandboxing used in this codebase and is safe to use, but your host might not be.

Prerequisites

Run these safe checks on the host:

uname -a
test "$(uname -s)" = Linux
test -r /proc/self/status
test -e /sys/fs/cgroup/cgroup.controllers
test -e /proc/self/ns/user
test -e /proc/self/ns/pid
command -v gcc
command -v go
command -v python3

The integration suite also requires a writable cgroup v2 hierarchy, working user namespaces, and permission to create the required namespaces. Java tests additionally require java and javac. Check active mandatory access-control policy with the host's normal tools, for example command -v getenforce && getenforce for SELinux or command -v aa-status && sudo aa-status for AppArmor. A policy mismatch is an investigation item, not a reason to disable isolation.

Run the complete security suite

From the repository root, run:

sudo -E go test ./...
sudo -E go test -race ./executor
bash .github/scripts/check_regressions.sh
git diff --check

Run the repeated hostile boundary probe with:

sudo -E go test -count=3 -run '^TestSecurityBoundaryNamespacesAndInheritedResources$' ./...
sudo -E go test -count=3 -run '^TestSandboxHardeningC$/^hostile boundary attack chain cannot cross sandbox$' ./...

sudo is required because the test harness intentionally exercises privileged Linux isolation and cgroup operations. Never ignore a failed command, add || true, or treat a skipped privileged test as success.

Sentinel-file verification

Use a disposable directory, never a real secret:

sentinel_dir=$(mktemp -d)
printf '%s\n' 'operator-sentinel-must-not-change' > "$sentinel_dir/sentinel"
sha256sum "$sentinel_dir/sentinel"
sudo -E go test -run '^TestSecurityBoundaryNamespacesAndInheritedResources$' ./...
sha256sum "$sentinel_dir/sentinel"
rm -rf "$sentinel_dir"

The adversarial workload attempts filesystem, descriptor, procfs, sysfs, IPC, UTS, socket, and temporary-path access. The host sentinel must still exist with the same checksum. Delete the disposable sentinel manually after verification.

Manual host-isolation checklist

Review the test output and host state after the suite. Confirm that host files are unreadable and unchanged, host processes cannot be enumerated or signalled, devices and mounts cannot be manipulated, /sys cannot be written, Unix sockets are not reachable, host environment secrets and file descriptors are absent, the configured network policy holds, sandbox descendants disappear after termination, and sandbox temporary resources do not remain on the host.

For cross-sandbox verification, run the repeated boundary probe while another terminal runs sudo -E go test -count=10 -run '^TestSecurityBoundaryNamespacesAndInheritedResources$' ./.... Neither run may report inherited descriptors, host process access, IPC visibility, host socket access, or host sentinel changes.

Failure interpretation

Missing cgroup v2 means resource enforcement cannot be established. Missing user namespaces or insufficient privileges means namespace isolation cannot be established. Missing compiler/runtime dependencies mean the relevant workload coverage did not execute. Unexpected mounts, devices, security-module state, namespace creation errors, or seccomp failures indicate a host or implementation mismatch. Do not disable the corresponding security mechanism as a workaround; investigate or move the service to a dedicated hardened Linux VM.

Generic GitHub-hosted runners cannot prove every kernel, LSM, device, mount-topology, cloud-metadata, or privileged-host-socket property. Those checks require a dedicated hardened VM with pinned kernel configuration and explicit network/device policy. See SECURITY_TEST_MATRIX.md for the boundary-by-boundary status and assumptions.

api
code
containerization
execution
http
sandbox

Anga205/ContainerizationAPI

An API to execute code snippets in multiple languages and return the output and execution metrics

Go

3

68 commits

updated Oct 9, 2026

See the code

See what people are saying

SourceMessageScoreDate

A code execution sandbox in Go without Docker, just plain linux tooling (r/golang)

I built a Containerization API that executes untrusted code and returns output plus memory and CPU metrics. Instead of running a container per submission, it creates the boundary directly with Linux primitives: PID, mount, network, and user namespaces, a chroot into a minimal per-language rootfs,…

3

Oct 20, 2026

README

ContainerizationAPI

The general idea of this project is to provide a service similar to the CodeExecutionAPI by The Alcoding Club but to manually implement the containerization using cgroups, namespaces, chroot and a bunch of other unix/linux utility tools. This will help accomplish faster execution times and cut down on sandboxing overhead

Requirements

To use this API, there aren't really any requirements, you just need to be able to send HTTP requests To Host this API, you need to have Linux or WSL (if you're on windows) or MacOS, because this project makes heavy use of unix-specific tools.

Usage

Using this API is designed to be as simple as possible, at it's core, it's just an HTTP request that you send to /v2/execute, or /execute

The following are the routes that this API makes available, click one to see it's documentation


POST Route: /execute


This route is a drop in replacement for CodeExecutionAPI, if you send any request to the API defined in that repository, it's actually going to be handled by this codebase (because it does the same exact thing but faster)

This route takes 4 fields:

ParameterWhat It DoesDefault & LimitsHow To Use
languageSpecifies which programming language to execute your code in. The API needs to know which compiler/interpreter to use.Required (no default)

Supported values:
• python - Python 3.x
• cpp - C++17 (G++)
• c - C11 (GCC)
• java - Java (OpenJDK)
Pass as a string. Must exactly match one of the supported values (all lowercase).

Example: "language": "python"
codeThe actual source code you want to run. This is what gets executed in the isolated environment.Required (no default)

No explicit size limit
Pass as a single string. Use \n for line breaks. Escape any quotes inside your code.

Single line: "code": "print('Hello')"

Multi-line: "code": "def hello():\n print('world')\nhello()"
timeoutMaximum execution time. If your code runs longer than this, it gets killed immediately to prevent infinite loops or runaway processes.Default: 5 seconds
Max: 60 seconds
Pass as an integer (seconds). Your code will be terminated the moment it exceeds this limit.

On timeout: Returns "Execution Timed Out" error with no output.

Example: "timeout": 10 (runs for max 10 seconds)
max_memoryMaximum RAM your code can use. If your code allocates more memory than this, it gets killed to prevent memory exhaustion.Default: 32768 KB (32 MB)
Max: 131072 KB (128 MB)
Pass as an integer (KB). Memory is monitored in real-time during execution.

On limit exceeded: Returns "Memory limit exceeded" error.

Example: "max_memory": 65536 (allows up to 64 MB)
inputsProvides STDIN data for programs that read user input. Each array element becomes one line of input, delivered in order as your code calls input(), scanf(), etc.Default: [] (no input)

No explicit size limit
Pass as an array of strings. Each string is one line of input. Elements are consumed sequentially.

Single input: "inputs": ["bob"]

Multiple inputs: "inputs": ["bob", "alice", "25"]

Use this if your code reads from STDIN

Note: the above mentioned default and max values can be modified by editing the environment variables mentioned at the bottom of this README


Request body format (Example 1):

{
    "language": "python",           // this is the language your code is going to be executed in
    "code": "print('Hello World')"  // a string containing the code itself, with lines separated by \n
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "print('\''Hello World'\'')"
}'

Response body format (Example 1):

{
    "output": "Hello World\n",  // Output of the code
    "error": "",                // If any error occurs during execution
    "memory_used": "13808 KB",  // RAM used (in KB)
    "cpu_time": "9.166097ms"    // as in 9.16 milliseconds
}

Request body format (Example 2):

{
    "language": "python",
    "code": "import time\nprint('Hello World')\ntime.sleep(5)",
    "timeout": 2       // in seconds (defaults to 5, max 60)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import time\nprint('\''Hello World'\'')\ntime.sleep(5)",
    "timeout": 2       
}'

Response body format (Example 2):

{
    "output": "",                   // No output is returned on timeout
    "error": "Execution Timed Out", // Error message in case of timeout
    "memory_used": "15856 KB",      // RAM used (in KB)
    "cpu_time": "2.00014463s"      // Time before code was terminated
}

Request body format (Example 3):

{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000        // in KB (defaults to 32768, max 131072)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000
}'

Response body format (Example 3):

{
    "output": "",
    "error": "Memory limit exceeded",   // code is terminated the moment it takes more memory than allocated
    "memory_used": "136636 KB",
    "cpu_time": "115.779962ms"
}

Request body format (Example 4):

{
    "language": "python",
    "code": "a = input()\nprint(f'first value entered is {a}.')\nb=input()\nprint(f'second value entered is {b}.')",
    "inputs": [
        "bob",
        "alice"
    ]
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "a = input()\nprint(f'\''first value entered is {a}.'\'')\nb=input()\nprint(f'\''second value entered is {b}.'\'')",
    "inputs": [
        "bob",
        "alice"
    ]
}'

Response body format (Example 4):

{
    "output": "first value entered is bob.\nsecond value entered is alice.\n",
    "error": "",
    "memory_used": "24504 KB",
    "cpu_time": "13.625964ms"
}

Note: the route /execute also calls /v2/execute under the hood, its just that the formatting for the input and output are slightly more easy to read and understand.


POST Route: /v2/execute


This route takes 4 fields:

ParameterDescriptionDefaultMaximum
languageThe programming language of the code snippet to be executed.Required (no default)N/A
codeThe actual source code to be executed. Use \n to represent line breaks within the string. The code runs in an isolated environment.Required (no default)No explicit limit
timeoutMaximum execution time in nanoseconds. If the code runs longer than this, it will be forcefully terminated.5,000,000,000 ns
(5 seconds)
60,000,000,000 ns
(60 seconds)
max_memoryMaximum memory allocation in KB (kilobytes). If the code exceeds this limit, it will be terminated immediately.32,768 KB
(32 MB)
131,072 KB
(128 MB)
inputsA single string containing all STDIN data. If your code reads input (e.g., input() in Python, scanf() in C, Scanner in Java), provide the input here. For multiple lines, separate them with \n.Empty string
(no input)
No explicit limit

Note: the above mentioned default and max values can be modified by editing the environment variables mentioned at the bottom of this README


Request body format (Example 1):

{
    "language": "python",
    "code": "print('Hello World')"
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "print('\''Hello World'\'')"
}'

Response body format (Example 1):

{
    "output": "Hello World\n",  // output from STDOUT
    "error": "",                // output from STDERR
    "cpu_time": 10283909,       // run time in nanoseconds (10ms or 0.01 seconds)
    "memory_used": 25116        // RAM used (in KB)
}

Request body format (Example 2):

{
    "language": "python",
    "code": "import time\nprint('Hello World')\ntime.sleep(5)",
    "timeout": 2000000000       // in nanoseconds (defaults to 5000000000, max 60000000000)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import time\nprint('\''Hello World'\'')\ntime.sleep(5)",
    "timeout": 2000000000
}'

Response body format (Example 2):

{
    "output": "",                   // No output is returned on timeout
    "error": "Execution Timed Out", // Error message in case of timeout
    "cpu_time": 2000003280,         // Time (in nanoseconds) before code was terminated
    "memory_used": 15856,           // RAM used (in KB)
}

Request body format (Example 3):

{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000        // in KB (defaults to 32768, max 131072)
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "import random;[random.random() for x in range(10**7)]",
    "max_memory": 300000
}'

Response body format (Example 3):

{
    "output": "",
    "error": "Memory limit exceeded",   // code is terminated the moment it takes more memory than allocated
    "cpu_time": 116936122,
    "memory_used": 136628
}

Request body format (Example 4):

{
    "language": "python",
    "code": "a = input()\nprint(f'first value entered is {a}.')\nb=input()\nprint(f'second value entered is {b}.')",
    "inputs": "bob\nalice"
}
Click to copy curl command
curl --location 'https://codeapi.anga.codes/v2/execute' \
--header 'Content-Type: application/json' \
--data '{
    "language": "python",
    "code": "a = input()\nprint(f'\''first value entered is {a}.'\'')\nb=input()\nprint(f'\''second value entered is {b}.'\'')",
    "inputs": "bob\nalice"
}'

Response body format (Example 4):

{
    "output": "first value entered is bob.\nsecond value entered is alice.\n",
    "error": "",
    "cpu_time": 9058716,
    "memory_used": 25136
}

GET Route: /debug/config


This route returns the current server configuration, including memory limits and timeout settings. This is a debug route and must be enabled via the ENABLE_DEBUG environment variable.

Request format:

GET /debug/config
Click to copy curl command
curl --location 'https://codeapi.anga.codes/debug/config'

Response body format:

{
    "limits": {
        "max_timeout": 60000000000,
        "max_memory_limit": 131072,
        "default_timeout": 5000000000,
        "default_memory_limit": 32768
    },
    "globals": {
        "ram_limit": 1048576,
        "enable_debug_routes": false
    }
}

I'm making this as part of a bigger project to host my own coding contests on a platform a little better than HackerRank. The Alcoding Club's API is fine but I need one that's more efficient and has less overhead than firejail.

Click to expand the Self-Hosting Guide

Environment Variables

  • MAX_TIMEOUT: This is the maximum timeout that a request can set, if an incoming request has a higher timeout than the value you set here, then it will ignore the request's set timeout only be executed for MAX_TIMEOUT time.
    • this env variable uses the Go Duration String format e.g - 1m30s, 4h15s, 5s
    • if you dont set this env variable, it defaults to 60s
    • the public version of this api has this value set to 60s
  • MAX_MEMORY_LIMIT: Maximum RAM allocation that a request can be set to, if an incoming request tries to set to a higher memory limit, it will be ignored and reset to this value
    • this must be set to an unsigned integer (positive number) and is measured in Kilobytes (KB)
    • if you dont set this env variable, it defaults to 131072 which is 128 MB or 131072 KB
    • the public version of this api has this value set to 131072
  • DEFAULT_TIMEOUT: If an incoming request does not have a timeout parameter (i.e, request does not specify a time limit) then the sandbox will set the time limit to this value
    • same format as MAX_TIMEOUT
    • if you dont set this env variable, it defaults to 5s
    • the public version of this api has this value set to 5s
  • DEFAULT_MEMORY_LIMIT: If an incoming request does not have a max_memory parameter (i.e, request does not specify a memory limit) then the sandbox will set the maximum memory limit to this value
    • same format as MAX_MEMORY_LIMIT
    • if you dont set this env variable, it defaults to 32 MB
    • the public version of this api has this value set to 32 MB
  • GLOBAL_RAM_LIMIT: The total amount of RAM that is available globally to the server, cumulatively, the API will not allocate more RAM than what you define here, for example, if RAM limit if 1GB, and there are already 10 sandboxes that have reserved 100MB (either by default or by user-specification in the max_memory parameter) and an 11th process tries to reserve even more RAM, the server will NOT allocate that RAM
    • same format as MAX_MEMORY_LIMIT
    • default value is 1048576 or 1 GB
    • public version of this API sets this to 1 GB
  • ENABLE_DEBUG: if true, the server will expose debug routes (like /check-ram) for monitoring and debugging purposes
    • this can only be set to true or false
    • by default this is false
    • the public API has this set to true
  • GIN_MODE: whether to run the gin-gonic server in release mode or debug mode. For this API there won't be much of a performance difference regardless of which one you pick, and it is recommended that you read the docs for gin-gonic for more information
    • this can be set to release or debug
    • by default it is set to debug
    • the public API has this set to release
  • PORT: Which network port to run the server on
    • this can be set to any unsigned integer value between 0 and 65535
    • by default this is 8080
    • public API has this set to 8080

Note: if you are self-hosting this api, remember to run it with superuser permissions, or a user which has permissions to alter controlgroups and namespaces, normally you can achieve this just by using "sudo" before running the code

Optional: Security Verification

This security verification suite is intended only for those users who want to self-host this API, the tests detailed below are for verifying if the sandboxing implemented by the codebase for running arbitrary user code is compatible with the host where the tests are running, run these commands on your host to verify that it's suitable for the kind of sandboxing I've implemented here. If one or more of these tests is failing then its probably a good idea to self-host this api inside of another more hardened container/VM. The host running the publically deployed version of this API is completely compatible with the sandboxing used in this codebase and is safe to use, but your host might not be.

Prerequisites

Run these safe checks on the host:

uname -a
test "$(uname -s)" = Linux
test -r /proc/self/status
test -e /sys/fs/cgroup/cgroup.controllers
test -e /proc/self/ns/user
test -e /proc/self/ns/pid
command -v gcc
command -v go
command -v python3

The integration suite also requires a writable cgroup v2 hierarchy, working user namespaces, and permission to create the required namespaces. Java tests additionally require java and javac. Check active mandatory access-control policy with the host's normal tools, for example command -v getenforce && getenforce for SELinux or command -v aa-status && sudo aa-status for AppArmor. A policy mismatch is an investigation item, not a reason to disable isolation.

Run the complete security suite

From the repository root, run:

sudo -E go test ./...
sudo -E go test -race ./executor
bash .github/scripts/check_regressions.sh
git diff --check

Run the repeated hostile boundary probe with:

sudo -E go test -count=3 -run '^TestSecurityBoundaryNamespacesAndInheritedResources$' ./...
sudo -E go test -count=3 -run '^TestSandboxHardeningC$/^hostile boundary attack chain cannot cross sandbox$' ./...

sudo is required because the test harness intentionally exercises privileged Linux isolation and cgroup operations. Never ignore a failed command, add || true, or treat a skipped privileged test as success.

Sentinel-file verification

Use a disposable directory, never a real secret:

sentinel_dir=$(mktemp -d)
printf '%s\n' 'operator-sentinel-must-not-change' > "$sentinel_dir/sentinel"
sha256sum "$sentinel_dir/sentinel"
sudo -E go test -run '^TestSecurityBoundaryNamespacesAndInheritedResources$' ./...
sha256sum "$sentinel_dir/sentinel"
rm -rf "$sentinel_dir"

The adversarial workload attempts filesystem, descriptor, procfs, sysfs, IPC, UTS, socket, and temporary-path access. The host sentinel must still exist with the same checksum. Delete the disposable sentinel manually after verification.

Manual host-isolation checklist

Review the test output and host state after the suite. Confirm that host files are unreadable and unchanged, host processes cannot be enumerated or signalled, devices and mounts cannot be manipulated, /sys cannot be written, Unix sockets are not reachable, host environment secrets and file descriptors are absent, the configured network policy holds, sandbox descendants disappear after termination, and sandbox temporary resources do not remain on the host.

For cross-sandbox verification, run the repeated boundary probe while another terminal runs sudo -E go test -count=10 -run '^TestSecurityBoundaryNamespacesAndInheritedResources$' ./.... Neither run may report inherited descriptors, host process access, IPC visibility, host socket access, or host sentinel changes.

Failure interpretation

Missing cgroup v2 means resource enforcement cannot be established. Missing user namespaces or insufficient privileges means namespace isolation cannot be established. Missing compiler/runtime dependencies mean the relevant workload coverage did not execute. Unexpected mounts, devices, security-module state, namespace creation errors, or seccomp failures indicate a host or implementation mismatch. Do not disable the corresponding security mechanism as a workaround; investigate or move the service to a dedicated hardened Linux VM.

Generic GitHub-hosted runners cannot prove every kernel, LSM, device, mount-topology, cloud-metadata, or privileged-host-socket property. Those checks require a dedicated hardened VM with pinned kernel configuration and explicit network/device policy. See SECURITY_TEST_MATRIX.md for the boundary-by-boundary status and assumptions.

api
code
containerization
execution
http
sandbox