Skip to main content

macOS Observer Tiers

Pavri's pavri-endpointd sensor supports three macOS observer tiers, depending on whether the binary holds Apple's Endpoint Security entitlement.

Bottom line up front: Tier 1 is production-grade. Tier 2 is best-effort and suitable for design-partner deployments only. Tier 3 means process and file observation are off; other subsystems (MCP broker, egress proxy, browser bridge, clipboard observer, git hooks) continue.


Tier 1 — Endpoint Security (production)

Status: active Coverage: Full SOC2-graded: Yes

The sensor uses Apple's Endpoint Security framework to subscribe to ES_EVENT_TYPE_NOTIFY_EXEC (process execution) and ES_EVENT_TYPE_NOTIFY_OPEN/NOTIFY_WRITE/NOTIFY_CREATE/NOTIFY_RENAME (file events). ES delivers events synchronously before the kernel allows the operation to proceed, so there is no race window between event creation and sensor receipt.

Requirement: The sensor binary must hold the com.apple.developer.endpoint-security.client entitlement, granted via the Apple Developer Program after a manual review. Approval is not instantaneous; the typical turnaround is days to weeks. This is Apple's standard process — Pavri cannot expedite it.

Build tag: cap_es_entitled

To escalate: Contact your security team to open an Apple Developer Program entitlement request. Reference the CAP-1.7 roadmap item. Once approved, rebuild the sensor binary with -tags cap_es_entitled and redeploy.


Tier 2 — kauth/FSEvents fallback (best-effort)

Status: degraded_kauth_fallback Coverage: Best-effort (see gaps below) SOC2-graded: No

When the Endpoint Security entitlement is absent, the sensor automatically falls back to two deprecated-but-functional kernel APIs:

  • Process observation: two paths (hardened in S2.3).

    1. Event-driven fast-path — kqueue(2) EVFILT_PROC with NOTE_EXEC | NOTE_FORK | NOTE_EXIT. Low latency; a per-PID start-time "generation" token detects PID reuse and a proactive probe at NOTE_FORK recovers children that already exec'd, with dedup so nothing double-fires.
    2. Poll-based discovery sweepproc_listpids every kauthSweepInterval (1 s), diffing the live PID set against the watch set to emit REAL (libproc) exec events for newly-seen processes and prune exited ones. This is the load-bearing path on modern macOS: on macOS 26 (Darwin 25)+ an unprivileged EVFILT_PROC watcher receives only NOTE_EXIT (NOTE_FORK/NOTE_EXEC are withheld without ES), and posix_spawn (used by Go's os/exec and much modern software) does not fire NOTE_FORK on the parent — so the fast-path alone would miss most process launches. The sweep is real polling of real processes (not synthetic telemetry).

    The sensor enriches live process events with best-effort libproc ancestry, executable path, binary hash, and app bundle identifier when those are readable without special entitlement.

  • File observation: FSEvents (FSEventStreamCreate, FSEventStreamSetDispatchQueue, FSEventStreamStart). FSEvents is Apple's recommended inotify-equivalent and is not deprecated. No entitlement required for user-launched binaries.

Honest coverage gaps

GapRoot causeImpact
Short-lived process missA process that starts AND exits within one discovery-sweep interval (1 s) whose NOTE_FORK/NOTE_EXEC was not delivered (macOS 26+/posix_spawn) is missed.Bounded (was unbounded pre-S2.3): sub-second commands that leave no live footprint at a sweep may not appear. Examples: ls, which.
Discovery latencyOn hosts where the fast-path is inert (macOS 26+/posix_spawn), newly-spawned processes are reported by the poll sweep up to one interval (1 s) late.Process telemetry is near-real-time, not instantaneous.
Exec-in-place missA watched process that execs a new image without forking keeps the same PID and start-time, so neither path re-fires when NOTE_EXEC is withheld.Rare (most launches fork/spawn a child); such in-place replacements are not re-emitted.
macOS 26 EVFILT_PROC restrictionUnprivileged EVFILT_PROC on Darwin 25+ delivers only NOTE_EXIT.Exec/fork detection relies entirely on the discovery sweep on these hosts; PID reuse is handled via NOTE_EXIT + next sweep.
File path scopeOnly paths matching the configured allowlist are watched.Events on paths outside ~/.aws, ~/.ssh, /etc/, etc. are not delivered.
File writer attributionFSEvents reports the changed path but not the writer PID/process chain.File-write telemetry is emitted as file_attribution=path_only; it can be correlated to workspace/config/session context, but it is not treated as assistant-process attribution.
FSEvents coalescingThe kernel coalesces rapid successive writes into a single event.Event count will be lower than actual operation count during heavy I/O bursts. Now surfaced, not silent (S2.3): when the kernel signals event loss (KernelDropped/UserDropped/MustScanSubDirs), the observer increments pavri_endpoint_observer_events_total{observer="file",outcome="kernel_event_loss"} so the telemetry gap is visible to operators.
Fork-to-watch raceInherent in the kqueue(2) design. There is no atomic "watch me and all my future children" operation without ES.Cannot be closed without Tier 1.
Direct socket attributionmacOS connect/DNS observation requires Network Extension entitlement.net_observer_status=not_entitled; deep model-API visibility is available only through the optional local egress proxy, and web-app visibility is available only through the browser bridge.

When is Tier 2 acceptable?

  • Design-partner-grade deployments where operators understand the gaps.
  • Development and staging environments where coverage completeness is not a compliance requirement.
  • Incident triage where the missing short-lived-process events are not the primary signal.

Tier 2 is not acceptable for SOC2 Type II evidence collection. Use Tier 1.

Opting out of the fallback

If your security posture requires that an unentitled macOS endpoint report unavailable (Tier 3) rather than run the fallback, set:

export PAVRI_DARWIN_DISABLE_KAUTH_FALLBACK=1

This is useful for SOC2 environments where a "deprecated kauth API" finding would be flagged by an auditor. With this flag set, the sensor's process and file observers surface unavailable on the endpoint detail page, and the dashboard does not show the kauth fallback banner.

GHA macos-13 / macos-14 runner note

GitHub Actions macos-13 and macos-14 runners are not Endpoint Security entitled by default. The Tier 2 kauth fallback tests (livemacos build tag) run on these runners. The test setup helper automatically skips if ES IS entitled (meaning we're not in fallback mode), so the test suite is safe to run on both entitled and unentitled macOS hosts.


Tier 3 — Unentitled, fallback disabled

Status: unavailable Coverage: None (process and file) SOC2-graded: N/A

When PAVRI_DARWIN_DISABLE_KAUTH_FALLBACK=1 is set and the ES entitlement is absent, both the process observer and the file observer report Unavailable. The sensor continues to function: the MCP broker, egress proxy, git hook scanner, browser bridge, clipboard observer, and heartbeat all remain active. The direct macOS network observer remains not_entitled until Apple grants Network Extension approval; it does not emit synthetic socket telemetry.

This is the pre-CAP-2.6-fu behavior. It is appropriate for operators who cannot tolerate any use of deprecated kernel APIs and who have not yet obtained the ES entitlement.


Dashboard indicator

When Tier 2 is active, the endpoint detail page (/endpoints/[id]) shows a warning banner with the text:

macOS endpoint running in kauth fallback mode — Apple Endpoint Security entitlement not granted. Process/file observation is best-effort; some events may be missed. Contact security team to escalate Apple Developer Program entitlement review.

The banner is read from endpoint.posture.attributes:

  • process_observer_status = degraded_kauth_fallback
  • file_observer_status = degraded_kauth_fallback
  • net_observer_status = not_entitled
  • network_visibility = egress_proxy | browser_bridge | none

Either attribute being set to degraded_kauth_fallback triggers the banner. network_visibility is coverage evidence: egress_proxy means the local model-API proxy is the active deep network path; browser_bridge means page interaction/upload/download evidence is available from the browser extension; none means no no-entitlement network fallback is currently active.

Browser bridge, clipboard observer, and egress proxy events also carry metadata-only CAP-4 control labels such as cap4_control_sensitive_upload, cap4_control_sensitive_download, cap4_control_cross_app_laundering, and cap4_control_model_api_key_misuse. These labels are correlation evidence for the existing policy/detection stack; they do not imply direct macOS socket attribution.


Roadmap

SprintItem
CAP-1.2macOS Endpoint Security stubs (CGO; production codesigning deferred to CAP-1.7)
CAP-1.7Production ES entitlement and codesigning (Tier 1)
CAP-2.6-fukauth/FSEvents fallback (Tier 2); PAVRI_DARWIN_DISABLE_KAUTH_FALLBACK opt-out; dashboard banner; this doc