Tool isolation¶
Soulacy uses two different protections. They are intentionally named here so operators do not mistake resource limits for a security boundary.
Privileged builtins: disposable container boundary¶
shell_exec, run_script, python_eval, install_library, write_file, and
download_file all pass through one privileged-tool chokepoint. With the
shipped configuration, command execution uses a new Docker container per call:
- no network (
--network none), including no cloud metadata endpoint; - read-only container root, all Linux capabilities dropped, and
no-new-privileges; - PID, memory, CPU, file-descriptor, file-size, and caller wall-clock bounds;
- only
<workspace>/data/sandboxmounted at/workspace; - no gateway environment, config file, credential vault, database, logs, agent manifests, or other host directories;
- failure to contact Docker refuses the call; there is no host fallback.
Scripts must therefore be written into the isolated workspace before
run_script can execute them. Package installation is ephemeral and, with the
default network-off policy, cannot download from public registries.
runtime:
sandbox:
enabled: true
mode: docker
image: python:3.12-slim
cpu_seconds: 30
memory_mb: 512
open_files: 256
file_size_mb: 64
pids: 128
mode: unsandboxed (or legacy enabled: false) is an explicit compatibility
escape hatch. It runs commands as the gateway user and emits an error-level
warning on every startup. Do not use it on shared or production systems.
Filesystem-native write_file and download_file still execute in the host
process after the chokepoint, but their targets pass the same symlink-aware
workspace containment policy used by all filesystem tools. They cannot write
outside configured roots.
Ordinary Python tools: resource limits by default¶
Agent/plugin Python execution still uses the __exec-sandbox POSIX rlimit
wrapper by default. It caps CPU, address space, open descriptors, and
single-file size and filters the environment. This wrapper is a
resource-exhaustion guard, not a filesystem or network boundary.
For untrusted agent tools, select the Docker backend per agent:
Each tool call then runs in a disposable container with the same hardened
profile as privileged builtins: read-only root, all capabilities dropped,
no-new-privileges, no network by default, a noexec tmpfs /tmp (also
HOME), and the runtime.sandbox CPU, memory, PID, file-descriptor and
file-size caps. No gateway environment is forwarded, and only the volumes you
list are mounted. If Docker is unreachable, the call fails; it never falls back
to the host.
# config.yaml
executor:
docker_image: python:3.12-slim
docker_network: none # an explicit choice; "none" also blocks cloud metadata
docker_user: auto # auto (gateway uid:gid) | image | "uid:gid"
docker_volumes:
- /srv/agent-data:/data:rw
docker_user: auto runs the tool as the gateway's own user, so it is not
root inside the container and files it writes to a volume belong to you. Use
image to keep the image's default user.
Current limits: tools defined with python_file load from a host path, so
they only work in Docker if that directory is mounted at the same path. Plugin
tools don't honour execution.backend yet. Both are planned; see
the design memo.
On macOS RLIMIT_AS is advisory; on non-Unix systems the rlimit wrapper is a
no-op. These limitations do not weaken the Docker boundary for privileged
builtins.