Skip to content

Sandboxing

The Linux SDK includes loom-serve-sandbox: a small launcher that runs loom-serve without host networking, with explicit model mounts and accelerator access. The same HTTP API, dashboard, and WebSocket streams remain available through a filesystem Unix socket. Model files stay on disk; even very large weight stores are bind mounted, never copied into a container.

Linux

Install the Linux Loom SDK and your distribution's bubblewrap package. The launcher uses Bash, GNU coreutils, and /usr/bin/bwrap. The host must permit unprivileged user namespaces; an outer container or host security policy can prohibit them even when the kernel supports them. Run as an ordinary user with access to the selected devices.

With a named-device configuration at /srv/serve/devices.json and a deployment at /srv/serve/chat/model.json, start the server with read-only configuration and weights:

loom-serve-sandbox --socket-dir=./serve-socket \
  --ro-bind=/srv/serve --ro-bind=/srv/weights \
  --gpu=all --npu=all -- \
  --devices=/srv/serve/devices.json --model=/srv/serve/chat/model.json

The device configuration selects where models run and their memory policy; the sandbox flags grant access to hardware without changing those choices. An absent GPU or NPU class is reported as empty. For a quick check without a configuration file, model, or accelerator, start an empty CPU server instead:

loom-serve-sandbox --socket-dir=./serve-socket -- --device=task

In another terminal:

curl --fail --unix-socket ./serve-socket/serve.sock http://localhost/readyz
curl --fail --unix-socket ./serve-socket/serve.sock http://localhost/v1/models

localhost is the HTTP authority, not a TCP connection. The launcher exposes this dedicated host directory as /run/loom and adds --listen=unix:/run/loom/serve.sock. A trusted host-side proxy can relay HTTP and WebSocket traffic to it without giving the server network access. The observation workflow describes relaying the dashboard.

The launcher creates a missing socket directory with mode 0700; its parent must already exist. An existing directory must belong to your user and have mode 0700; its permissions are never changed. Its contents are writable by the server. Ctrl-C, SIGTERM, or SIGHUP requests normal shutdown and waits for accepted work to retire; the server removes its socket on successful teardown. An occupied socket path is an error, not permission to delete another server's socket.

Mount models and weights

Acquire models outside the sandbox using the ordinary fetch-and-serve workflow. Expose each input root read-only at its existing absolute path:

loom-serve-sandbox --socket-dir=./serve-socket \
  --ro-bind=/srv/models --ro-bind=/srv/weights \
  --gpu=all -- \
  --device=amdgpu --model=/srv/models/chat/model.json

The example assumes an AMDGPU-compatible package at /srv/models/chat/model.json, with any shared weights under /srv/weights. --ro-bind=PATH is repeatable and accepts an existing regular file or directory. The path is unchanged inside the sandbox, so document-relative references and absolute symlinks work when all their targets have also been exposed. Nothing walks the model tree to discover dependencies or copies its contents. An omitted symlink target produces an ordinary file-access error.

All arguments after -- pass unchanged to loom-serve. Its working directory is /, so --model, --devices, and --flagfile use absolute mounted paths. Model and deployment references remain document-relative. Paths read from a flagfile use the same / working directory, not the flagfile's directory. HTTP registration likewise sees only mounted paths. Additional TCP listeners stay inside the private network namespace; they do not expose a host port.

--binary=/path/to/sdk/bin/loom-serve selects a different installed SDK binary and its adjacent native libraries, for example when comparing two SDK builds. Omitting it selects loom-serve beside the launcher. This expects the installed SDK layout, not an executable that depends on build-tree runfiles. A trusted bubblewrap installation elsewhere can be selected with --bwrap=/path/to/bwrap. The launcher does not select an ambient PATH wrapper or accept raw bubblewrap options.

Select accelerators

--gpu=all exposes the AMD GPU render nodes present at startup and /dev/kfd. --npu=all exposes the AMD NPU nodes present at startup. Selection uses the amdgpu and amdxdna kernel drivers; other device types are not granted. Omitting either flag exposes none of that class. An absent class is reported as empty, while discovery failures and inaccessible selected nodes fail startup. The launcher binds individual nodes, not device directories, so later hotplug does not expand access.

To select a subset, use --gpu=/dev/dri/renderD128 or --npu=/dev/accel/accel0. Both options are repeatable and can be combined; duplicate grants are harmless. The launcher supplies read-only discovery metadata, including sysfs symlink targets and NUMA information. It does not change host permissions or install drivers.

For an XDNA-compatible package:

loom-serve-sandbox --socket-dir=./serve-socket \
  --ro-bind=/srv/models --ro-bind=/srv/weights \
  --npu=all -- \
  --device=xdna --pool_backing=fixed --model=/srv/models/npu/model.json

For a mixed GPU/NPU server, pass both device options, mount the directory containing your named-device configuration, and use --devices=/absolute/path/devices.json after --. Device exposure and server placement are separate: the mount permits access; the device URI and deployment select where work runs. Discovery metadata can describe devices whose nodes have not been granted.

Elastic backing is the server default and needs no extra flag. Device-specific exceptions such as XDNA fixed backing belong in the device configuration, not the sandbox launcher.

Write profiling captures

--rw-bind=PATH exposes an existing output file or directory read-write at the same absolute path. It is repeatable and opt-in. Keep output directories separate from model and weight trees:

install -d -m 700 ./captures
loom-serve-sandbox --socket-dir=./serve-socket \
  --ro-bind=/srv/models --ro-bind=/srv/weights \
  --rw-bind="$PWD/captures" --gpu=all -- \
  --device=amdgpu --model=/srv/models/chat/model.json \
  --device_profiling_mode=queue-events \
  --device_profiling_output="$PWD/captures/serve.ireeprof"

Send requests, then stop the launcher normally so the HAL profiling session finishes. The capture remains in the host directory. Supported profiling data families depend on the selected driver; loom-serve --help=all lists the linked profiling options. Full compiler-report artifact dumping is not a serving flag.

Ordinary stdout/stderr still reach the caller and can be redirected on the host without a writable mount. Collecting /v1/status, /metrics, or /logz through the socket also needs no server-side writable directory.

Boundary and failures

The launcher creates private user, mount, PID, IPC, UTS, and network namespaces, drops capabilities, prevents new privileges and nested user namespaces, clears the environment, and closes inherited descriptors other than stdout/stderr. Stdin is /dev/null. System runtime directories and the selected SDK directory are read-only; the host home, credentials, and ordinary host filesystem are not implicitly mounted. Temporary files live in a private /tmp and disappear with the sandbox.

Explicit mounts are trusted operator grants. A writable alias of a weight tree makes those files writable despite another read-only mount. A mounted directory containing host filesystem Unix sockets can expose those services despite the private network namespace. Dedicated input, output, and socket directories keep that boundary inspectable. The socket has no built-in authentication: access to it grants the ordinary serving and administration API.

The host kernel, accelerator drivers, and accelerator memory isolation remain trusted. This is not a defense against driver bugs or device denial of service, and it sets no CPU, host-memory, or device-memory quota. Deployment-level resource controls remain separate; a process memory limit does not necessarily account for all accelerator allocations.

The launcher checks the complete namespace/mount policy before starting the server. Failure leaves the policy intact; there is no privileged or unsandboxed retry.

Diagnostic Action
Missing bubblewrap or unsupported option Install a current distribution package, or select a trusted binary with --bwrap=PATH.
Namespace creation or sandbox setup denied Check the host's user-namespace policy and any outer container/LSM restrictions. A host administrator must authorize this boundary; the launcher never retries with elevated privileges.
Device lacks read/write access Check the host driver, device node, group/ACL, and outer device policy using the device-access checklist.
Required sysfs metadata is absent Check that the host driver is loaded and an outer sandbox exposes its discovery metadata as well as its device nodes.
Model file or symlink target is absent Add the narrowest read-only root containing that dependency.
Output path is not writable Create a dedicated output directory and grant it with --rw-bind=PATH; the host user's permissions still apply.

loom-serve-sandbox --help works without initializing a device or creating a sandbox. Platforms other than Linux use the portable loom-serve command with their own deployment isolation tools.