I am building Ouroboros (ouro-jail), a contained developer workload runner. Before advertising native macOS support, I need a supported lifetime boundary that covers ordinary fork/spawn/exec descendants, including detached sessions and double-fork daemons. Execution must stay contained when every userspace custodian dies, rather than relying on a surviving process to enumerate and kill descendants.
Does macOS provide a supported facility that can:
- Establish an immutable workload scope before its first instruction and atomically inherit membership through fork/spawn/exec, without modifying the workload or preventing ordinary Node, shell and Git subprocess behavior?
- Stop or terminate every member after loss of the final control owner, with enforcement retained in the kernel and no userspace callback required?
- Handle concurrent creation during teardown and expose a verifiable final drain, using stable process identities rather than PID-only scans?
Development evidence on macOS 27.0 build 26A428, XNU 13432.1.9 (isolated VM, SIP already disabled in the guest; this is development evidence, not normal-SIP qualification):
- Descendant-scoped Endpoint Security clients observe the tested subtree. Reciprocal custodians handle either client's loss, but killing both leaves all known members running. Uncached fail-closed AUTH_EXEC lets a survivor execute a new command after both clients disappear (200 reciprocal-custody trials).
- PT_ATTACHEXC gives kernel-enforced debugger-death cleanup for attached targets, but later fork/spawn children survive (80 debugger-lifetime trials).
- A locally installed, identity-protected Mach exception handler is inherited through the tested fork/spawn/exec paths. Owner death changes it to a dead handler without stopping normal execution; a child can clear it. Breakpoint controls confirm real kernel exception delivery (24 Mach-lifetime trials).
- Published XNU contains per-task fatal-port no-senders cleanup, but machdep.task_get_fatal_port is absent on this tested release kernel. I have not treated it as a supported application API.
- LaunchServices creates a real application coalition, and XPC service lifetime follows app death. However, all seven workload descendants survive, retain coalition membership and can execute a new command. Shared and private audit sessions both fail; eight-second follow-ups also retain all seven survivors. A temporary root launchd service restarts and reaps seven recorded processes after a crash, but does not restart a stopped custodian (native-service trials).
I understand Endpoint Security entitlement is separate from a lifetime guarantee. Please clarify whether an approved descendant-scoped ES deployment can provide that owner-loss guarantee through a documented API, whether another supported facility is appropriate, or whether meeting this contract requires a VM boundary.
Below are the relevant parts of a focused C command-line reproduction (192 lines total, Xcode 27.0, SDK 27.0, -Wall -Wextra -Werror, no signing or third-party libraries). The code ran in the isolated VM; the packaged Xcode project has only been build-checked.
1. The owner installs a protected exception handler on itself, then forks the workload. No cleanup callback follows.
static const exception_mask_t masks = EXC_MASK_SYSCALL | EXC_MASK_MACH_SYSCALL | EXC_MASK_BREAKPOINT;
static const exception_behavior_t behavior = EXCEPTION_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES;
mach_port_t channel = MACH_PORT_NULL;
mach_port_options_t options = {.flags = MPO_EXCEPTION_PORT | MPO_INSERT_SEND_RIGHT};
kern_return_t rc = mach_port_construct(mach_task_self(), &options, 0, &channel);
if (rc != KERN_SUCCESS) return 4;
rc = task_set_exception_ports(mach_task_self(), masks, channel, behavior, THREAD_STATE_NONE);
if (rc != KERN_SUCCESS) return 4;
pid_t child = fork();
if (!child) {
/* Ordinary receive rights are not inherited. Only the kernel's task
* exception-handler state provides supervision of this child. */
_exit(payload(directory, mode, exe));
}
2. The workload detaches (double-fork shown; fork, setsid and posix_spawn SETPGROUP/SETSID also tested; a child can also clear the handler with task_set_exception_ports(..., MACH_PORT_NULL, ...)).
child = fork();
if (!child) {
if (strcmp(mode, "fork") && setsid() < 0) _exit(4);
if (!strcmp(mode, "double-fork")) {
pid_t second = fork(); if (second < 0) _exit(4); if (second) _exit(0);
}
child_work(directory, mode);
}
3. Each member reads its inherited handler state and keeps running ordinary BSD syscalls and a Mach trap.
kern_return_t rc = task_get_exception_ports(mach_task_self(), masks, observed, &count,
ports, behaviors, flavors);
/* port == MACH_PORT_DEAD -> dead_mask, valid send right -> live_mask */
for (;;) {
if (write(fd, ".", 1) != 1) _exit(3); /* heartbeat file */
(void)getpid();
mach_port_t thread = mach_thread_self();
mach_port_deallocate(mach_task_self(), thread);
usleep(10000);
}
Observed output (double-fork case, original VM run). Workload, before and after SIGKILL of the owner (pid 1472):
{"kind":"exception_state","role":"root","phase":"before_owner_loss","rc":0,"live_mask":448,"dead_mask":0}
{"kind":"exception_state","role":"late-child","phase":"before_owner_loss","rc":0,"live_mask":448,"dead_mask":0}
{"kind":"exception_state","role":"root","phase":"after_owner_loss","rc":0,"live_mask":0,"dead_mask":448}
{"kind":"exception_state","role":"late-child","phase":"after_owner_loss","rc":0,"live_mask":0,"dead_mask":448}
After owner loss, both root (pid 1473) and the detached late-child (pid 1475, own session) were still alive under the same uniqueid/pidversion, reparented to launchd, with heartbeat files still growing. Across 21 owner-death trials in 7 modes, 39 known fixture processes survived and no case left the fixture empty. The 3 breakpoint controls (owner alive) confirmed real exception delivery.
Expected contract: a documented kernel-owned boundary prevents continued execution by every inherited member after all control owners disappear, without enumerating descendants after the fact or requiring workload cooperation. I am asking which supported macOS API meets that contract, not asserting that exception ports promise it.
Apple DTS asked me to start this thread (Case-ID: 22799543) and will review once I reply with the link.
The full reproduction steps and environment details are in the attached text.