Quick Start

Installation

# Core library (expects nsjail on PATH)
pip install nsjail-python

# Include pre-built nsjail binary (Linux x86_64/aarch64)
pip install nsjail-python[binary]

# Build nsjail from source during install
pip install nsjail-python[build]

# Enable protobuf validation
pip install nsjail-python[proto]

Three API Levels

Low-level: Direct dataclass construction

from nsjail import NsJailConfig, MountPt, Exe

cfg = NsJailConfig(
    hostname="sandbox",
    time_limit=30,
    mount=[MountPt(src="/", dst="/", is_bind=True, rw=False)],
    exec_bin=Exe(path="/bin/sh", arg=["-c", "echo hello"]),
)

Mid-level: Presets

from nsjail import sandbox

cfg = sandbox(
    command=["python", "script.py"],
    memory_mb=512,
    timeout_sec=60,
    writable_dirs=["/workspace", "/tmp"],
)

High-level: Fluent builder

from nsjail import Jail

cfg = (
    Jail()
    .sh("pytest tests/ -v")
    .memory(512, "MB")
    .timeout(60)
    .readonly_root()
    .writable("/workspace")
    .writable("/tmp", tmpfs=True, size="64M")
    .no_network()
    .build()
)

Serialization

from nsjail.serializers import to_textproto, to_cli_args, to_file

# Protobuf text format (for --config flag)
print(to_textproto(cfg))

# CLI arguments
args = to_cli_args(cfg, on_unsupported="skip")

# Write to file
to_file(cfg, "sandbox.cfg")

Running nsjail

Sync execution:

from nsjail import Runner, Jail

runner = Runner(
    base_config=Jail()
        .command("python", "-m", "pytest")
        .memory(512, "MB")
        .timeout(300)
        .readonly_root()
        .writable("/workspace")
        .build(),
)

result = runner.run(extra_args=["tests/unit/", "-x"])
print(result.returncode, result.stdout)

Async execution:

result = await runner.async_run(extra_args=["tests/unit/"])

Direct from builder:

result = (
    Jail()
    .sh("echo hello")
    .timeout(10)
    .run()  # Creates a default Runner
)

Mount Helpers

Ergonomic functions for common filesystem patterns:

from nsjail import (
    Jail, system_libs, dev_minimal, python_env,
    bind_tree, tmpfs_mount, overlay_mount,
)

cfg = (
    Jail()
    .sh("python script.py")
    .readonly_root()
    .mounts(system_libs())       # /lib, /usr/lib, /usr/bin, etc.
    .mounts(dev_minimal())       # /dev/null, /dev/zero, /dev/urandom
    .mounts(python_env())        # Current Python installation
    .mounts(tmpfs_mount("/tmp", size="64M"))
    .writable("/workspace")
    .build()
)

Overlay filesystem (copy-on-write):

from nsjail import overlay_mount

cfg = (
    Jail()
    .sh("make build")
    .mounts(overlay_mount(
        lower="/workspace",           # read-only base
        upper="/tmp/overlay/upper",   # writable layer
        work="/tmp/overlay/work",     # overlay workdir
        dst="/workspace",
    ))
    .build()
)

Seccomp Policies

Build seccomp policies in Python instead of writing raw Kafel:

from nsjail import Jail, SeccompPolicy, MINIMAL

# Use a preset
cfg = Jail().sh("echo hi").seccomp(MINIMAL).build()

# Or build a custom policy
policy = (
    SeccompPolicy("custom")
    .allow("read", "write", "close", "exit_group")
    .deny("execve", "fork")
    .default_kill()
)
cfg = Jail().sh("echo hi").seccomp(policy).build()

Available presets: MINIMAL, DEFAULT_LOG, READONLY

Cgroup Stats

Capture resource usage during sandbox execution:

from nsjail import Runner

runner = Runner(
    base_config=cfg,
    collect_cgroup_stats=True,
)
result = runner.run()

if result.cgroup_stats:
    print(f"Peak memory: {result.cgroup_stats.memory_peak_bytes}")
    print(f"CPU time: {result.cgroup_stats.cpu_usage_ns}ns")
    print(f"Processes: {result.cgroup_stats.pids_current}")

Jailed Execution

Call Python functions inside nsjail sandboxes:

Explicit call:

from nsjail import jail_call

result = jail_call(
    my_function,
    args=(data,),
    memory_mb=512,
    timeout_sec=30,
)

Decorator:

from nsjail import jailed

@jailed(memory_mb=512, timeout_sec=30)
def untrusted_compute(data):
    return expensive_transform(data)

result = untrusted_compute(my_data)  # Runs in nsjail

Context manager:

from nsjail import JailContext

with JailContext(memory_mb=512, timeout_sec=30) as jail:
    result1 = jail.call(function_a, data)
    result2 = jail.call(function_b, result1)

Install cloudpickle for lambda/closure support:

pip install nsjail-python[call]