Skip to content

v4.2.11-97eb073

Choose a tag to compare

@github-actions github-actions released this 24 Mar 23:17
· 332 commits to main since this release
Immutable release. Only release title and notes can be modified.
97eb073

⚠️ This release is affected by GHSA-w253-42qp-5f2x. Update to v5.0.5-caaf673 or later.

Security notice: This version is affected by GHSA-fpmv-5wgw-qhhr. Upgrade to v4.2.14 or later.

v4.2.11 — Live metrics and telemetry

This release adds a live throughput graph to the GUI and extends pipeline telemetry to cover jail events. It also corrects a misapplied optimisation from v4.2.9.

Live metrics graph

A new Metrics section is available in the sidebar. It shows a rolling 60-second line chart of events per second across five series:

  • Simple events — decisions made on the hot path without ancestry data
  • Ancestry events — decisions that required process ancestry resolution
  • Drops — events shed by the buffer or slow-path queue under load
  • Jail events — file auth events evaluated against a jail rule
  • Jail denies — jail-evaluated events that were denied

Metrics are pushed from the extension to the GUI over XPC once per second and rendered using Swift Charts. The chart appears once two snapshots have been received; until then a waiting placeholder is shown.

Jail event telemetry

FilterInteractor now tracks cumulative jailEvaluatedCount and jailDenyCount counters behind an OSAllocatedUnfairLock. These are sampled each tick alongside the pipeline counters, included in the PipelineMetricsSnapshot sent over XPC, and written to the system log under the metrics category.

Correction: autoreleaseFrequency: .never removed

v4.2.9 added .autoreleaseFrequency: .never to all dispatch queues and introduced a background timer to drain autorelease pools once per minute. This was based on a misunderstanding: .never controls whether the queue itself creates autorelease pools around each work item, not whether the ES callback thread drains them. The flag was a no-op on the queues that mattered and the cleanup timer was draining an always-empty pool. Both have been removed. The ES adapters already manage object lifetimes explicitly via es_retain_message / es_release_message.


Also released on the 24th March:

v4.2.10 — Jail adapter engine parity

This release applies the same stability and threading changes introduced in v4.2.9 to the jail adapter, which did not receive those fixes in that release.

Critical stability fix: ES message lifetime in the jail adapter

The same use-after-free fixed in v4.2.9 for the main ES client was also present in ESJailAdapter. All AUTH file events are now retained with es_retain_message before being dispatched off the ES callback thread. Every response path — the non-jailed fast path, the nil-path guard, and the handleJailEventSync respond closure — calls es_release_message exactly once after responding.

Jail adapter dispatch queue

The jail ES callback now returns to the ES framework immediately. All event handling is dispatched to a dedicated esJailAdapterQueue (userInteractive QoS, autoreleaseFrequency: .never), consistent with the main adapter's esAdapterQueue.

NOTIFY lifecycle events (FORK, EXEC, EXIT) extract the data they need from the message synchronously before dispatching, since these events carry no deferred respond call and do not need to be retained.

Exec removed from the jail auth path

AUTH_EXEC replaced with NOTIFY_EXEC in the jail adapter's subscription list. The jail adapter always allows exec events — it only needs the key swap in its jailed-process map. Subscribing as NOTIFY_EXEC removes one auth round-trip per exec, matching the change made to the main adapter in v4.2.9.

Non-jailed AUTH_OPEN response aligned

The response value for AUTH_OPEN events on processes not subject to any jail rule was changed from reflecting the requested fflag back to UInt32.max, consistent with how the main adapter grants access via es_respond_flags_result.


v4.2.9 — Engine performance and stability

This release is a focused engine overhaul with no changes to policy behaviour or the GUI. Every commit targets latency, throughput, and correctness of the system extension under load.

Critical stability fix: ES message lifetime

AUTH events from Endpoint Security carry a message pointer that is only valid until the callback returns — unless the caller explicitly retains it. The event processing pipeline was dispatching work asynchronously while still holding raw message pointers, creating a window where the ES framework could reclaim the message before the pipeline called respond. This has been corrected: every AUTH message is retained before being dispatched and released immediately after respond is called, closing the use-after-free.

Two-stage file auth pipeline

File auth events are now processed through a two-stage pipeline:

  • Hot path — a single serial consumer handles cheap classifications (globally allowed, no matching rule). Events that can be decided without ancestry data are responded to and exited immediately.
  • Slow path — a bounded concurrent worker pool handles events that require process ancestry. Workers wait for the process to appear in the tree (up to a safety margin before the ES deadline), then evaluate and respond.

The separation keeps the common case (allow) off the slow path entirely and bounds the maximum concurrency on the slow path to prevent thread explosion.

Exec events removed from the auth path

EXEC events were previously subscribed as AUTH_EXEC, requiring an explicit allow response for every process exec in the system. Both the main ES client and the jail adapter now subscribe to NOTIFY_EXEC instead. The event carries identical process-image data without needing a response, removing one auth round-trip per exec from the hot path.

XProtect monitoring path narrowed

The ES muting configuration now restricts monitored paths to only the prefixes covered by active policy rules. Previously a broader set of paths was delivered to the pipeline; paths with no matching rule now never reach the extension.

Threading

All concurrency primitives are created once at startup with explicit QoS and named labels visible in Instruments and the system log:

Queue QoS Role
es-adapter userInteractive ES callback dispatch
es-jail-adapter userInteractive Jail ES callback dispatch
pipeline.hot userInteractive Fast-path event consumer
pipeline.slow userInitiated (concurrent) Ancestry-requiring decisions
process-tree userInitiated Process tree mutations
post-respond background Audit logging, TTY notifications
xpc-server userInitiated All XPC handler work
metrics utility Periodic metrics logging
cleanup background Autorelease drain

All queues use .autoreleaseFrequency: .never and a background timer drains autorelease pools once per minute.

ProcessTree: O(1) ancestor lookups

Ancestor resolution previously walked the tree on every lookup. The process tree now maintains a parent-pointer index so ancestor chains are resolved in O(depth) time with no additional allocations.

Reduced ES cache pressure on startup

The ES authorisation cache was being cleared redundantly during startup and on policy sync. Cache clears are now issued only when the policy actually changes, reducing the burst of re-authorisation events after launch.

Event tracing

A correlation UUID is threaded from the ES callback entry point through the pipeline, audit log, and XPC broadcast. All log lines for a single file auth event share the same UUID, making end-to-end traces straightforward to reconstruct. Debug and info log calls have been removed from the event processing path to eliminate string formatting overhead on every event; warning, error, and fault lines are retained.

Full Changelog: v4.2.9-1bd9a4b...v4.2.10-d078fba