Skip to content

Instantly share code, notes, and snippets.

@josephgimenez
Created March 1, 2026 20:24
Show Gist options
  • Select an option

  • Save josephgimenez/861a1c4d8be0b02601ef4b32983f3152 to your computer and use it in GitHub Desktop.

Select an option

Save josephgimenez/861a1c4d8be0b02601ef4b32983f3152 to your computer and use it in GitHub Desktop.

How Endpoint Security Works

The Endpoint Security framework is Apple's supported API for intercepting system operations in real-time. It replaced the deprecated kernel extensions (kexts) starting in macOS 10.15.

The Model

A privileged ES client process (the supervisor) subscribes to event types. When a monitored process performs an operation, the kernel holds the syscall and delivers an AUTH event to the ES client. The client inspects the event and responds with ALLOW or DENY. Only then does the kernel proceed.

Child process (sandboxed)              Kernel                    ES Client (nono supervisor)
        │                                │                              │
        ├── open("/home/.aws/config") ──►│                              │
        │                                ├── ES_EVENT_TYPE_AUTH_OPEN ──►│
        │                                │   (blocked in kernel)        │
        │                                │                              ├── Check policy
        │                                │                              ├── Prompt user?
        │                                │◄── es_respond_auth_result ───┤
        │◄── ALLOW or EPERM ─────────────│                              │

Key AUTH Events

Event What it intercepts
ES_EVENT_TYPE_AUTH_OPEN File open — can inspect path AND downgrade flags (e.g., allow read-only but deny write) via es_respond_flags_result
ES_EVENT_TYPE_AUTH_EXEC Process execution — can block specific binaries
ES_EVENT_TYPE_AUTH_CREATE File/directory creation
ES_EVENT_TYPE_AUTH_UNLINK File deletion
ES_EVENT_TYPE_AUTH_RENAME File rename/move
ES_EVENT_TYPE_AUTH_LINK Hard link creation
ES_EVENT_TYPE_AUTH_CLONE File cloning (copyfile)

Why This Solves nono's Problems

1. No exec() limitation. The ES client runs as a separate process. The monitored child can fork + exec freely — the kernel delivers events based on the process being monitored, regardless of how many times it execs. This completely sidesteps the extension token problem.

2. No DYLD hacks. The interception happens in the kernel, not in userspace. Hardened runtime, SIP, code signing — none of it matters. The child process doesn't need any special setup, env vars, or libraries.

3. Per-operation granularity. Instead of declaring all permissions upfront in a static Seatbelt profile, the supervisor makes allow/deny decisions on each open() call. This enables:

  • Interactive prompting ("Claude wants to read ~/.aws/config — allow?")
  • Dynamic permission accumulation (approve once, allow for the session)
  • Fine-grained flags — AUTH_OPEN can downgrade read-write to read-only

4. Works with unmodified programs. cat, git, ssh, Claude Code — anything. The child doesn't need to integrate an SDK or be aware it's monitored.

5. Survives the full process tree. You can monitor a process and all its descendants. When Claude Code spawns bash, which spawns git, which spawns ssh — all of them are covered.

The Catch: Entitlement Requirements

The com.apple.developer.endpoint-security.client entitlement is restricted:

  • Requires an Apple Developer account
  • Must be requested via Apple's System Extensions request form
  • App must be structured as an Application Bundle with a provisioning profile
  • Apple can grant development access but deny distribution access
  • The app also needs user approval via System Settings → Privacy & Security → Full Disk Access (or the ES-specific prompt)

This is the same entitlement used by security products like Santa (Google), LuLu, and commercial EDR tools.

What nono Would Look Like With ES

nono (ES client, entitled, runs as supervisor)
  │
  ├── es_new_client() — subscribe to AUTH_OPEN, AUTH_EXEC, etc.
  ├── es_mute_process() — mute self + system processes for performance
  │
  └── fork + exec child (Claude Code, no sandbox_init needed)
        │
        └── Every file open/exec from the child tree
              → kernel delivers AUTH event to nono
              → nono checks policy (deny_credentials, never_grant, etc.)
              → nono prompts user if needed
              → nono responds ALLOW or DENY

No Seatbelt profiles. No DYLD interposition. No extension tokens. No shell naming hacks. Just a clean kernel-level interception with real-time policy decisions.

Deadline Consideration

AUTH events have a deadline (~30-60 seconds). If the ES client doesn't respond in time, the system kills the client. For interactive prompts, nono would need to either respond quickly or use a strategy like deny-then-retry: deny the operation immediately, prompt the user in the background, and when approved, the child's next attempt succeeds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment