This directory contains the Swift implementation of the ephemeral sandbox runner shipped inside dist/PolicyWitness.app.
PolicyWitness is specimen-first:
- The controller (
policy-witness) starts a fresh XPC runner instance per specimen. - The runner starts unsandboxed, applies a requested seatbelt profile exactly once (SBPL source + parameters), executes a probe plan, replies with JSON, and exits.
-
runner/PWRunnerAPI.swiftPWRunnerProtocol(runSpecimen(Data) -> Data)- Codable JSON types:
PWRunnerRunSpec,PWRunnerPolicySpec,PWRunnerProbeStep, and the returnedPWRunnerRunResult
-
runner/SandboxLib.swift- Explicit
dlopen+dlsymbindings for libsandbox.
- Explicit
-
runner/SandboxApply.swift- Policy hashing and single-shot
sandbox_applypath.
- Policy hashing and single-shot
-
runner/Instrumentation.swift- Instrumentation ports and
InstrumentationState.
- Instrumentation ports and
-
runner/ProbeRunner.swiftsandbox_checkand probe attempts (file + mach-lookup).
-
runner/PathUtils.swift- Path normalization and fd-based observation helpers.
-
runner/Signals.swift- Deny-signal handler and counters.
-
runner/PWRunnerService.swift- Orchestrates the run flow (decode → instrumentation → sandbox apply → probes → reply).
-
runner/runner-client/main.swift→ buildsdist/PolicyWitness.app/Contents/MacOS/pw-runner-client- Thin
NSXPCConnectionwrapper that forwards JSON bytes and prints the runner’s JSON reply.
- Thin
-
runner/services/PWRunner/Info.plist,Entitlements.plist,main.swiftfor the standard runner XPC service bundle.
-
runner/services/PWRunnerDebug/Info.plist,Entitlements.plist,main.swiftfor the debuggable runner XPC service bundle.
The runner consumes a PWRunnerRunSpec which contains:
policy:sbplsource (with optionalparams)probe_plan: ordered probe steps (sandbox_check + attempt)
Each probe step reports both a sandbox_check and an attempt result:
sandbox_checkincludesscope(post_sandbox) plus the originalfilter_valueand a best-efforteffective_filter_value(forpathfilters, this is the runner’s normalized path). It also reportspid,operation,filter_type_id, anderrno/errorwhen the check call fails.attemptalways includesexit_codeandsyscall_errno(explicitnullwhen not applicable), and for file attempts it includesrequested_path,normalized_path, andobserved_path(fd-based when available). Thercanderrnofields are retained for compatibility.
The standard built-in runner ships with minimal entitlements. The debuggable built-in runner (and some external runners) include hardened-runtime exceptions for inspection and controlled extensibility (debug attach / dynamic loading / dyld env / executable memory). These do not make sandbox policy “dynamic”.
Built-in runners can require a signed caller before accepting XPC connections. The check is controlled via Info.plist keys:
PWRunnerRequireSignedCaller(bool)PWRunnerAllowedIdentifiers(optional array of code signing identifiers)
When enabled, the runner compares the caller’s Team ID to its own Team ID and optionally enforces the allowlist. External runners are unaffected unless they opt in by adding the same keys.
Sandbox policy variation is driven by the specimen itself:
- the controller supplies SBPL,
- the runner applies it once to itself,
- the runner’s witness is defined by mandatory multi-channel evidence (see the controller docs).
PolicyWitness can target external runner services when entitlements are required. An external runner is the same PWRunner implementation, but signed with user-selected entitlements and registered with launchd in one of two modes:
- BYOXPC: a signed
.xpcbundle, addressed byCFBundleIdentifier. - MachMe: a signed raw binary, addressed as a Mach service name.
Invariants:
- The protocol is unchanged (
PWRunnerProtocolJSON-over-Data). - One specimen -> one runner process; the runner applies the sandbox once and exits.
- Evidence schema remains identical; the controller records runner provenance.
The controller provides a policy-witness runner manager to install/register
these services and to enforce entitlements supersets before dispatch.
The specimen schema includes an optional instrumentation object that activates
entitlement-backed adapters in a controlled, auditable way. When present, the
runner executes requested ports at phase: pre_sandbox (default) or
phase: post_sandbox, then includes instrumentation results in the run JSON.
Supported ports (v1):
dylib_load:dlopena specified dylib and optionally call a symbol (usescom.apple.security.cs.disable-library-validation).debug_wait: sleep forsleep_msbefore sandbox apply to allow debugger attach (usescom.apple.security.get-task-allow).execmem_probe: attempt a JIT mapping (MAP_JIT,PROT_READ|PROT_WRITE) and report success/failure (usescom.apple.security.cs.allow-jit).dyld_env: report whether expectedDYLD_*env vars are present; the runner cannot set these at runtime, so use an external runner with launchdEnvironmentVariables.
Default behavior is unchanged: if instrumentation is omitted, nothing runs.
Some development harnesses run tools inside an OS sandbox. In those environments:
- XPC lookup can fail early with
NSCocoaErrorDomain4099 / error 159"Sandbox restriction"(before the service launches). - Unified Logging access can also be restricted, making deny-evidence capture impossible from inside the harness.
Treat this as an environment constraint, not a PolicyWitness regression.
If you suspect you are running under a sandboxed automation harness, re-run from a normal Terminal (or with escalation) before debugging PolicyWitness itself.