Skip to content

agentix.sandbox

sandbox

A sandboxing tool executor for untrusted tools and model-generated code.

:class:~agentix.executors.LocalToolExecutor runs tool functions in-process — fine for trusted tools, but it cannot contain code you don't trust (a code-interpreter tool, a shell command the model composed). It also can't honour AgentPolicy.network_allowlist: an in-process Python call can open any socket.

:class:SubprocessExecutor runs each tool as a separate OS process and applies the limits the loop hands it:

  • Network — when the effective allowlist is empty, egress is denied by launching the process in a fresh network namespace (Linux unshare with an unprivileged user namespace, auto-detected). If isolation can't be established and require_network_isolation is set (the default), the call fails closed rather than running untrusted code with network access. (Per-host allowlisting — a non-empty list — is not enforced here; that needs a filtering proxy or firewall. A non-empty list is treated as "network allowed", documented as such.)
  • CPU / memory / file size / processes — POSIX setrlimit in the child.
  • Filesystem — each call runs in a fresh temporary working directory that is removed afterwards. (This is cwd isolation, not a chroot; true FS confinement still wants a container or mount namespace.)
  • Environment — the child gets a minimal env (just PATH) plus any names you explicitly pass through, so secrets in the parent process don't leak.
  • Timeout — the process group is killed if it overruns timeout_s.

Pair it with tool_schemas describing the tools to the model, exactly like any other :class:~agentix.executors.ToolExecutor.

Command dataclass

Command(
    argv: ArgvBuilder | Sequence[str],
    stdin: str | None = None,
)

How a tool name maps to a subprocess.

argv is either a fixed argv list or a callable that builds one from the tool-call args (never passed through a shell). If stdin is set, the named arg's value is fed to the process on standard input — handy for a "run this code" tool (argv=[python, "-"], stdin="code").

SandboxPolicy dataclass

SandboxPolicy(
    cpu_seconds: float | None = 5.0,
    memory_bytes: int | None = 512 * 1024 * 1024,
    file_size_bytes: int | None = 16 * 1024 * 1024,
    max_processes: int | None = 64,
    max_output_bytes: int = 64 * 1024,
    env: Mapping[str, str] | None = None,
    env_passthrough: Sequence[str] = (),
    workdir: str | PathLike[str] | None = None,
    require_network_isolation: bool = True,
    isolator: Sequence[str] | None = None,
)

Resource and isolation limits applied to every sandboxed process.

SubprocessExecutor

SubprocessExecutor(
    commands: Mapping[
        str, Command | ArgvBuilder | Sequence[str]
    ],
    *,
    sandbox: SandboxPolicy | None = None,
)

A sandboxing :class:~agentix.executors.ToolExecutor.

commands maps tool name -> :class:Command (a bare argv list or :class:ArgvBuilder is accepted as shorthand). Example::

import sys
from agentix.sandbox import SubprocessExecutor, Command

executor = SubprocessExecutor(
    {"run_python": Command(argv=[sys.executable, "-"], stdin="code")}
)
# With AgentPolicy().network_allowlist empty (the default), run_python
# executes with no network access — or refuses if it can't guarantee that.

detect_network_isolator cached

detect_network_isolator() -> tuple[str, ...] | None

Return a working argv prefix that drops a child into an empty network namespace, or None if none is available. Probed once and cached.

Uses unshare with an unprivileged user namespace, which needs no root on Linux hosts that allow user namespaces. The probe actually runs it (a bare binary on PATH isn't proof the kernel permits it).