Audit remediation: cgroup fail-closed + exec race, EINTR, error surfacing, API consistency, test & CI hardening - #12
Merged
Merged
Conversation
nyo16
force-pushed
the
audit-remediation
branch
2 times, most recently
from
September 1, 2026 02:41
ded757b to
2c401b2
Compare
Security (shepherd/NIF):
- cgroup isolation fails closed: cgroup.procs write/flush checked, failures
surface as MSG_ERROR; pre-existing leaf dir fatal; mkdir 0700
- sync pipe gates child execvp on cgroup attach (both pipe and PTY paths);
descendants can no longer escape limits or cgroup.kill teardown
- direct nif_kill / Watcher probe only fire when the shepherd port is dead
(pid-reuse safety); nif_read realloc failure can't expose uninitialized heap
Fixed:
- EINTR retried in NIF read/write and shepherd I/O
- spawn-stage shepherd MSG_ERROR returns {:error, {:shepherd_error, msg}};
post-spawn errors logged and recorded
- shepherd UDS writes nonblocking with bounded poll(POLLOUT)
Changed:
- NetRunner.Process.Nif -> NetRunner.Nif; wire protocol centralized in
NetRunner.Process.Protocol; option validation unified (ArgumentError for
malformed opts, {:error, _} for runtime failures; stream! wraps uniformly)
- Daemon.os_pid cached; full @SPEC coverage; docs drift corrected
(--token-fd handshake, Watcher no-escalation, owner-monitor cleanup)
Tests: vacuity/margin fixes, single eventually/2 primitive, new coverage
(PTY resize, forwarder death, mid-stream raise, iodata+stats property,
MSG_ERROR receive path, cgroup fail-closed). 236 -> 242 passing.
CI/deps: Dependabot actions ecosystem, SHA-pinned actions, .credo.exs,
deps.unlock gate, alpine digest pin, delegated cgroup subtree for the
Linux positive path; elixir_make ~> 0.10.0; stream_data only: :test.
Verified: format, compile --warnings-as-errors, credo --strict, dialyzer,
242 tests green (3 seeds).
nyo16
force-pushed
the
audit-remediation
branch
from
September 1, 2026 02:54
2c401b2 to
84c1075
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes all 39 findings from the 2026-08-31 project-health audit (2 high, 13 medium, 24 low). Reports under
.claude/audit/(not committed).Security — shepherd/NIF boundary
cgroup_setupnow checks thecgroup.procswrite and flush — migration errors (EPERM/EBUSY/ENOENT) surface atfclosebehind stdio buffering and previously succeeded silently, leaving the child unconfined. Every failure sends a descriptive
MSG_ERROR. A pre-existing cgroup leaf dir isfatal (teardown wouldn't be owned) and the dir is created
0700.execvp(HIGH). The child blocks on a sync pipe afterforkand execs only once the shepherd has migrated it into the cgroup, soearly-forked descendants can't escape limits or
cgroup.killteardown. Async-signal-safe calls only between fork and exec; non-cgroup spawns are byte-for-byteunchanged. Applies to both pipe and PTY paths.
Nif.nif_killfallback inNetRunner.Processand the Watcher probe only fire when the shepherd port is dead; while itlives, the shepherd holds the child as a zombie and is the only safe signaller.
nif_read's >64 KiB shrink handlesenif_realloc_binaryfailure via alloc+copy fallback.Fixed
read(2)/write(2)loops and shepherd I/O — a transient signal could previously wedge a drain loop permanently.MSG_ERRORnow returns{:error, {:shepherd_error, msg}}(was a misleading fd-count mismatch); post-spawn errors arelogged and recorded in state.
poll(POLLOUT)so a wedged peer can't park the shepherd post-reap and skip cgroup cleanup.Changed
NetRunner.Process.Nif→NetRunner.Nif(internal;ERL_NIF_INITupdated to match).NetRunner.Process.Protocol— no inline frame bytes remain.ArgumentErrorat every entry point; runtime spawn failures return{:error, reason};stream!/2wraps spawn-stage reasons uniformly as
%NetRunner.Error{reason: {:spawn_failed, reason}}. Mildly breaking for callers matching the old tuple returns on invalidoptions.
Daemon.os_pid/1answers from cached state (was a blocking double GenServer hop).@speccoverage onNetRunner.Process; publicStats.t(); duplicate validator lifted toInputWriter.--token-fdhandshake (never argv), Watcher's deliberate single-SIGTERM/no-escalation design, Layer-3 cleanup via NIF owner monitor (not GC),write-budget loop exit, 1 MiB read cap, non-FIFO reader wakeup;
LINUX_VERIFICATION.mdrefs refreshed to function anchors.Tests (236 → 242 passing)
assert_receive, self-relative supervisor sampling, relative latencyprobe with absolute floor.
eventually/2polling primitive, retrying transient:noproc/:timeoutexits.stty size, Daemon forwarder-death, mid-streamstream!raise, iodata + stats-ledger property,MSG_ERRORreceive path over areal UDS pair, cgroup fail-closed assertion (
:linux_only).CI / deps
.credo.exs;mix deps.unlock --check-unusedgate; musl container pinned by digest./sys/fs/cgroup/net_runnerso the cgroup positive path actually executes in CI for the first time.elixir_make "~> 0.10.0"(was~> 0.9, spanning breaking 0.x minors);stream_datascopedonly: :test.Verification
mix format --check-formatted✓ ·mix compile --warnings-as-errors✓ ·mix credo --strict0 issues ·mix dialyzer0 errors:linux_only, stable across 3 seeds (macOS)-Werroron macOS — watch the first Linux CI run, which now exercises the real cgroup2positive path.