Cordium documentation · Latest

Running Untrusted Code

This example builds a code-execution sandbox for running untrusted code, such as code generated by an LLM in a "code interpreter" feature of your product, code submitted by your users, or the tool calls of an autonomous agent. Every execution runs in its own ephemeral Workspace with no network access, a read-only root filesystem, tight resource limits and no access to any API, and the Workspace is deleted right after the execution.

The layers of defense are:

  1. The Workspace's sandbox itself: a rootless container in its own user, PID, mount and network namespaces, inside a restricted supervisor container (read more here).

  2. A default-deny network policy, enforced outside of the sandbox (read more here).

  3. A read-only root filesystem and dropped Linux capabilities (read more here).

  4. An Octelium Policy that denies the sandboxes' Sessions any access to the Cluster, including the Cordium and Octelium APIs.

  5. Small resource limits, and a dedicated Space with SSH disabled.

The Space

A dedicated ORGANIZATION Space keeps the sandboxes separate from your development environments, caps their resources, and disables SSH access to them (read more here):

spec: limit: defaultLimit: cpu: millicores: 1000 memory: megabytes: 1024 storage: megabytes: 4000 maxLimit: cpu: millicores: 2000 memory: megabytes: 4096 storage: megabytes: 8000 authorization: disableSSH: true
cordium create space sandboxes.cordium

The Space's configuration above can then be applied from the Settings tab of the Space's page in the web portal, or via the UpdateSpace API.

The Template

The sandbox Template bakes the Python packages into its image. This matters because the network policy also applies to the pre-builds' tasks, while the image is built before the network policy is applied:

spec: image: dockerfile: inline: | FROM python:3.13-slim RUN pip install --no-cache-dir numpy pandas matplotlib scipy RUN useradd -m -u 1000 sandbox runtime: filesystem: readOnly: true capabilities: drop: - NET_RAW - NET_BIND_SERVICE - SYS_CHROOT - MKNOD network: egress: defaultAction: DENY
cordium create template python-sandbox.sandboxes.cordium --file python-sandbox.yaml cordium build python-sandbox.sandboxes.cordium

With the pre-build ready, each sandbox is restored from it instead of building the image again, and starts within seconds. The code runs as the image's sandbox user, and the read-only root filesystem prevents it from modifying the image, its packages or any system file. /workspace, the home directory and /tmp remain writable.

Denying the Cluster to the Sandboxes

Every Workspace, including a sandbox, has its own Octelium Session that belongs to its owner, and the cordium, octelium and octeliumctl CLIs inside it are authenticated with that Session through a local socket, which does not depend on the Workspace's network. A global Policy makes sure that the sandboxes' Sessions cannot do anything at all, whatever their owners are allowed to do (read more here):

kind: Policy metadata: name: deny-sandboxes spec: rules: - effect: DENY condition: all: of: - match: '"ext" in ctx.session.status && "cordium" in ctx.session.status.ext' - match: ctx.session.status.ext.cordium.spaceRef.name == "sandboxes.cordium" --- kind: ClusterConfig spec: authorization: policies: ["deny-sandboxes"]
note

The second resource above is the Octelium ClusterConfig, which is applied via octeliumctl, not the Cordium ClusterConfig. Remember to include your other global Policies in its list, if any.

Running Code

Your application, typically a backend service authenticated as a WORKLOAD User that is a Member of the sandboxes Space, runs each piece of code in a fresh sandbox via the SDKs. Here is an example via the Python SDK that runs a Python program, returns its output, and collects the chart that it may have generated:

from dataclasses import dataclass from cordium import Cordium @dataclass class Execution: exit_code: int output: str chart: bytes | None def run_untrusted(client: Cordium, code: str) -> Execution: sandbox = client.workspaces.run( template="python-sandbox.sandboxes.cordium", ephemeral=True, timeout=120, ) try: sandbox.files.write_text("/workspace/main.py", code) result = sandbox.exec( "cd /workspace && MPLBACKEND=Agg timeout 60 python main.py < /dev/null", timeout=90, max_capture_bytes=256 * 1024, ) chart = None if sandbox.exec("test -f /workspace/chart.png").exit_code == 0: chart = sandbox.files.read_bytes("/workspace/chart.png", max_bytes=10 * 1024 * 1024) return Execution(result.exit_code, result.stdout + result.stderr, chart) finally: sandbox.delete() if __name__ == "__main__": code = """ import numpy as np import matplotlib.pyplot as plt x = np.linspace(0, 10, 200) plt.plot(x, np.sin(x)) plt.savefig("/workspace/chart.png") print("mean:", np.sin(x).mean()) """ with Cordium() as client: execution = run_untrusted(client, code) print(execution.exit_code, execution.output)

Here are a few notes about this program:

  • timeout 60 inside the sandbox and the exec timeout bound the execution time, while the Space's limits bound its CPU, memory and storage. The output capture is bounded too.

  • The sandbox is deleted in every case, which discards its storage.

  • The WORKLOAD User creates one sandbox per execution, which counts towards its quota of non-stopped Workspaces (128 by default). Raise it via the ClusterConfig according to your peak concurrency (read more here).

note

For multi-step agent sessions that need to keep state between tool calls (e.g. variables, files and installed packages), keep one ephemeral sandbox per session instead of per execution, and delete it when the session ends or after an idle period.