Skip to content

feat(macos): process and file telemetry via Endpoint Security#46

Merged
Karib0u merged 10 commits into
Karib0u:mainfrom
mostafa:feat/macos-esf-process
Jun 1, 2026
Merged

feat(macos): process and file telemetry via Endpoint Security#46
Karib0u merged 10 commits into
Karib0u:mainfrom
mostafa:feat/macos-esf-process

Conversation

@mostafa
Copy link
Copy Markdown
Contributor

@mostafa mostafa commented May 31, 2026

Summary

Adds the macOS Endpoint Security sensor and wires it into the runtime in place of the placeholder. Process and file events map into the shared event model with Sysmon-compatible identifiers, so existing Sigma, YARA, and IOC detection works on macOS process and file activity.

  • Add the endpoint-sec dependency and an EsfSensor that owns the ES client on a dedicated thread and surfaces client init failures (not root, missing entitlement, TCC denial) synchronously.
  • Map process exec and exit: executable path, arguments, pid, parent pid, working directory, user, and a pid plus start-time key.
  • Map file create, delete, rename, and modify (modify is derived from close-after-write to keep the high-volume close stream down to real content changes).
  • Add the Endpoint Security entitlement plist and document ad-hoc signing and SIP requirements for local builds.

Process and file events emit the raw uid and defer username resolution to the normalizer, matching the Linux sensor.

Type of change

  • feat / enhancement - new feature

Test plan

  • Tested on Windows
  • Tested on Linux
  • Tested on macOS: unit tests for the event mappings, plus an ECS contract test for file events
  • Existing tests pass (cargo test)
  • New tests added (exec, exit, file mappings and ECS contract)

Note for maintainers: creating an Endpoint Security client requires root and the com.apple.developer.endpoint-security.client entitlement on signed and notarized builds. For local testing you can run with SIP and AMFI relaxed. The live client path was exercised manually under those conditions; the mappings are covered by unit tests.

Checklist

  • Label added to this PR
  • Docs updated (if behaviour changed)

Notes

Second of four in the #41 series, on top of #42 (merged). Verified on macOS arm64: cargo fmt --check, cargo clippy --locked --all-targets -- -D clippy::all, and cargo test --locked (134 passed) are all clean.

Part of #41.

mostafa added 8 commits May 31, 2026 21:32
Add the endpoint-sec dependency and an EsfSensor that owns the ES client on
a dedicated thread, subscribes to process exec and exit, and surfaces client
init failures (not root, missing entitlement, TCC denial) synchronously.
Replace the Phase 0 placeholder sensor in the macOS runtime. Event
translation is added in following commits.
Translate NOTIFY_EXEC into a process-start SensorEvent: executable path,
joined argv, pid, parent pid, working directory, and resolved user, keyed by
audit-token pid and process start time. Extract an FFI-free assembly step so
the mapping is unit-tested without a live client, and enable the libc
username lookup on macOS.
Translate NOTIFY_EXIT into a process-stop SensorEvent carrying pid and user,
mirroring the Linux terminate event, with a unit test for the assembly.
Add the com.apple.developer.endpoint-security.client entitlement plist and
document the macOS build, ad-hoc signing, and SIP/root requirements for
running the sensor locally.
Subscribe to NOTIFY_CREATE and NOTIFY_UNLINK and translate them into file
SensorEvents with Sysmon-compatible create and delete IDs, resolving the
create destination from either an existing file or a new directory plus
filename. The assembler is unit-tested without a live client.
Subscribe to NOTIFY_RENAME and translate it into a file SensorEvent carrying
both the source and destination paths, resolving the destination from an
existing file or a new directory plus filename.
Subscribe to NOTIFY_CLOSE and emit a file-change SensorEvent only when the
closed file was modified, which keeps the high-volume close stream down to
actual content changes.
Assert that a macOS file-create event maps through the normalizer and ECS
encoder to the expected dataset, category, type, action, file path, and the
macos/darwin host.os fields. Uses the repo's field-level ECS contract style
rather than a byte-exact golden, since host and timestamp fields vary.
Copy link
Copy Markdown
Owner

@Karib0u Karib0u left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the threading model (client created/dropped on a dedicated thread, NOTIFY-only subscriptions, non-blocking try_send with drop-on-full) and the FFI-free RawExec/RawFile split are clean and genuinely testable. Two things I'd like addressed before merge (both verified against the Linux sensor), plus a couple of minor notes inline.

Comment thread src/sensor/macos/esf.rs Outdated
Comment thread src/sensor/macos/esf.rs
@Karib0u Karib0u added the enhancement New feature or request label May 31, 2026
mostafa added 2 commits May 31, 2026 22:49
Report file-modify under FileCreate (event id 11), matching the Linux sensor,
instead of 65. Sysmon has no dedicated file-modify event, so file-change Sigma
rules key on EventID 11; the previous value silently broke that parity. The
action code stays 65. Test now asserts 11.
Emit the raw uid as a string for process and file events and defer username
resolution to the normalizer, matching the Linux sensor. This removes a
getpwuid_r directory-services lookup that ran on Endpoint Security's dispatch
queue for every event, including high-volume unlink and modified-close, where
it risked the handler falling behind and dropping events. lookup_username_by_uid
goes back to Linux-only since the macOS sensor no longer calls it.
@mostafa
Copy link
Copy Markdown
Contributor Author

mostafa commented May 31, 2026

Thanks, both good catches. Addressed against events.rs as the canonical Linux behavior:

1. File-modify event id parity (913f850)
You're right, the event id was mistakenly set to the action code. Modify now reports under EVENT_ID_FILE_CREATE (11) with action code 65, matching linux/events.rs:207. The test now asserts 11 so the parity is locked in.

2. Per-event getpwuid_r on the file hot path (34757d2)
Switched the sensor to emit the raw uid.to_string() for process and file events and defer username resolution to the normalizer, exactly like events.rs (which uses event.uid.to_string() for process, network, and file). This removes the directory-services lookup from Endpoint Security's dispatch queue entirely, so unlink and modified-close no longer risk the handler falling behind. I went with deferral rather than memoization to keep parity with the Linux sensor. lookup_username_by_uid is back to Linux-only since the macOS sensor no longer calls it.

Verified on macOS arm64: cargo fmt --all -- --check, cargo clippy --locked --all-targets -- -D clippy::all, and cargo test --locked (134 passed) are all clean.

Copy link
Copy Markdown
Owner

@Karib0u Karib0u left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, and for the fast turnaround on both review points.

The threading model is solid — owning the ES client on a dedicated thread, NOTIFY-only subscriptions, and the non-blocking try_send with drop-on-full are exactly the right calls for not stalling the dispatch queue. The FFI-free RawExec/RawFile split also makes the mappings genuinely unit-testable, which is great.

Both fixes look perfect:

  • file-modify now reports under EventID 11 (action code stays 65), matching the Sysmon scheme the other sensors use, so Sigma file-change rules work cross-platform.
  • raw uid on the sensor path, deferring like the Linux sensor — keeps getpwuid_r off the high-volume file path.

Verified CI is green on macOS, Linux, and Windows. Merging. 🎉

A couple of non-blocking follow-ups I'll track as issues (no action needed here): self-muting the agent's own log writes, and the stale "via libproc" comment. Really nice contribution to the macOS series.

@Karib0u Karib0u merged commit 81cc7ab into Karib0u:main Jun 1, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants