Architecture

Class Role
SecureRunner Validates the command, builds the Bubblewrap argv, starts it with proc_open() (array form, no shell) and supervises it.
SandboxConfig Immutable policy: bwrap/prlimit locations, extra read-only paths, environment allow-list, network opt-in.
JobWorkspace A random 0700 directory per job, mounted at /work; safe path building; output resolution; symlink-safe cleanup.
ResourceLimits Wall-clock deadline, CPU, file size, open files, optional address space, output cap. Core dumps always off.
ProcessResult Exit code, captured stdout/stderr, timedOut, outputLimitExceeded, duration.
SelfTest Canary-based verification used by bin/pdfx-sandbox-check.
Exceptions\* SandboxUnavailableException, InvalidWorkspaceException, InvalidCommandException, ProcessFailedException, all extending SecureRunnerException.

Execution flow

  1. Validate: executable is an absolute, existing, executable file; arguments are NUL-free strings; workspace is a real directory.
  2. Resolve bwrap (and prlimit). Missing bwrapSandboxUnavailableException. Nothing runs.
  3. Build: [prlimit …] bwrap --unshare-all --die-with-parent --new-session --cap-drop ALL --clearenv --setenv … --dev /dev --tmpfs /tmp --ro-bind <exe> <exe> --bind <workspace> /work --chdir /work -- <exe> <args…>.
  4. Supervise: non-blocking reads with an output cap; on deadline or cap, SIGKILL the bwrap process. The PID namespace and --die-with-parent take every descendant with it.
  5. Return a ProcessResult. The caller decides what a non-zero exit means.