Sandbox Model

Threat model

Image decoders are historically among the most exploited code paths in software. A malformed JPEG or PNG can trigger heap overflows, integer overflows, or out-of-bounds reads — all reachable before a single pixel is rendered. termimage's sandbox constrains what damage a successful exploit can do.

How it works

When Sandboxed: true:

  1. The parent resolves the source (file / data: URI / http(s):// URL). Remote fetches and base64 decode happen here, outside the sandbox.
  2. The parent spawns os.Executable() as a subprocess with TERMIMAGE_WORKER=1 and TERMIMAGE_WORKER_MODE=path|stdin in the environment. For path mode it also sets TERMIMAGE_WORKER_PATH=<path>; for stdin mode it pipes the bytes to the child's stdin.
  3. The child calls MaybeRunWorker(), which detects the env var and takes over.
  4. Before touching any input, the child applies OS restrictions.
  5. The child decodes and writes raw RGBA pixels (width[4] + height[4] + pixels) to stdout.
  6. The parent reads the pixel data over the pipe and renders it.
Caution

The sandboxed child re-execs your binary, not a dedicated helper. If your binary performs side effects during init() (network calls, file writes, etc.), those will run in the child before MaybeRunWorker() intercepts. Keep init() clean or move side effects behind a flag check.

Landlock (Linux ≥5.13)

In path mode (local file), Landlock restricts filesystem access to the target file only, read-only:

landlock.V3.BestEffort().RestrictPaths(landlock.ROFiles(path))

In stdin mode (remote URL / data URI), no file access is needed — the worker reads bytes from the pipe — so Landlock is configured with no granted paths, denying all filesystem access:

landlock.V3.BestEffort().RestrictPaths() // empty = deny-all

BestEffort() silently degrades on older kernels — the binary still runs, just without Landlock protection.

Note

Network access for remote URLs happens in the parent, before the sandboxed child is spawned. The child itself never gets to make network calls — the bytes are already in memory by the time it starts.

Seccomp (upcoming)

A syscall allowlist via BPF is in progress. The allowlist must accommodate the Go runtime scheduler (futex, mmap, brk, etc.) plus stb_image's memory usage pattern. Until it ships, only filesystem access is restricted.

Note

Even without seccomp, Landlock alone eliminates file-system exfiltration — the most common post-exploit primitive. A compromised decoder cannot read /etc/passwd, SSH keys, or any file other than the target image.

Wire protocol

The parent–child pipe carries a minimal binary protocol:

offset  size  field
0       4     width  (little-endian uint32)
4       4     height (little-endian uint32)
8       w*h*4 RGBA pixels (4 bytes per pixel, row-major)