macOS Kernel & System Extensions · Lesson 1

System Extensions on Your Mac

What the common extensions are, what languages you write them in — and how to read the three that are already running on this machine.

🎯
By the end of this lesson you'll run one CLI command and correctly decode every column of real System Extension output from your own Mac — and you'll know exactly which kind you're going to build in the next lessons, and why.

Mission tie-in. You want to build, install, and watch a System Extension run on this Mac, entirely from the CLI. Before you build one, you need the map: what kinds exist, what each one replaced, what language each uses, and how to see them. The fastest way in is that your Mac is already a live specimen — it's running three of them right now.

3System Extensions running on your Mac right now
4families: Network · Endpoint Security · Driver · Camera
0third-party kexts in your kernel (all 257 are Apple's)
2019year Apple began deprecating kexts (macOS 10.15)

1The one big idea

A kernel extension (kext) runs inside the kernel with full privileges — one bug is a system-wide kernel panic. A System Extension does the same jobs (filter network traffic, watch security events, drive hardware) but runs as an ordinary sandboxed user-space process. If it crashes, only that process dies — the OS keeps running.

The shift in one sentence

Apple moved third-party extension code out of the kernel and into user space, so the whole class of "a vendor's driver panicked my Mac" problems goes away — and, crucially for you, System Extensions load with SIP fully enabled. No recovery reboots, no lowered security. That's why we're building one of these, not a kext.

2Do this now — see your three live extensions

This is your tangible win. Run it in Terminal (no sudo needed):

▶ run me
$ systemextensionsctl list

Here's the actual output from this Mac, annotated. Match it against what you see:

systemextensionsctl list
3 extension(s)
--- com.apple.system_extension.network_extension
enabled  active  teamID      bundleID (version)              name                          [state]
*        *       W5364U7YZB  io.tailscale.ipn.macsys.network-extension (1.98.5/101.98.5)  Tailscale Network Extension  [activated enabled]
--- com.apple.system_extension.cmio
enabled  active  teamID      bundleID (version)              name                          [state]
         *       2MMRE5MTB8  com.obsproject.obs-studio.mac-camera-extension (32.1.2/…)  OBS Virtual Camera  [activated waiting for user]
--- com.apple.system_extension.endpoint_security
enabled  active  teamID      bundleID (version)              name                          [state]
*        *       4HMJQ7V3SX  com.swiftlydetecting.agent.securityextension (2.1.0/1)  Mac Monitor Security Extension  [activated enabled]
Live output, 2026-06-16. Three different families on one machine — a perfect specimen.

How to read a row

ColumnMeans
--- com.apple.system_extension.network_extensionThe category header. Everything below it until the next --- is that family. You can see three categories here: network_extension, cmio (camera), endpoint_security.
enabled *The user has switched it on in System Settings → Login Items & Extensions. Notice OBS has no star here — it's installed and running but still "waiting for user" to flip the toggle.
active *The extension process is currently loaded and running. OBS is active even though not user-enabled — the two columns are independent.
teamIDThe 10-character Apple Developer Team ID that code-signed it. W5364U7YZB is Tailscale. This is how macOS pins an extension to a verified publisher.
bundleID (version)Reverse-DNS identifier and (marketingVersion/buildVersion). The bundle ID is what your activation request names.
[state]Lifecycle state. activated enabled = fully live. activated waiting for user = installed but needs the System Settings toggle.
✓ Win unlocked

You just read the live extension table on your own machine and can explain why OBS shows active but not enabled. That's the same command you'll use in Lesson 4 to confirm your own extension went live.

3The four families (the "common ones")

"What are the common system extensions?" Almost everything third parties ship falls into one of four families. Here's each, what it replaced, and a real example — two of them are on your Mac.

Network Extension NE

Inspect, filter, route, or tunnel network traffic in user space. Sub-types: content filter, DNS proxy, app proxy, packet tunnel (VPNs), transparent proxy.

Replaces socket/IP filter kexts (sflt_*, ipf_*)

▸ On your Mac: Tailscale

Endpoint Security ES

Observe and authorize system events — process exec, file opens, mounts, signals. The backbone of EDR/antivirus/DLP tools.

Replaces KAUTH kexts (kauth_*) + the old MAC framework

▸ On your Mac: Mac Monitor

DriverKit / DEXT DK

Actual device drivers in user space — USB, HID, PCI, serial, audio, networking hardware. A built driver is a .dext.

Replaces IOKit C++ kexts subclassing IOService

e.g. a vendor printer/USB-gadget driver

Camera (CMIO) CAM

Virtual cameras & capture devices via Core Media I/O. Shows up under the same systemextensionsctl umbrella.

Replaces DAL plug-ins (the old .plugin camera path)

▸ On your Mac: OBS Virtual Camera

This is the deprecation story made concrete: every legacy kext category got a user-space replacement framework. (Full mapping lives in the course glossary and Apple's deprecated-kext list.)

4What language do I write one in?

Your direct question. The container app (the thing that ships and activates the extension) and the provider (the extension itself) can be different languages. Quick reference:

You're writing…Language(s)API shape
Container / host appSwift or Objective-CSwiftUI/AppKit + OSSystemExtensionRequest
Network Extension providerSwift or Objective-CSubclass NEFilterDataProvider, NEDNSProxyProvider, etc.
Endpoint Security clientC (callable from C++/Obj-C/Swift)C functions: es_new_client(), es_subscribe()
DriverKit driverC++ (a restricted dialect)IOKit-style object model, IOService subclass
legacy Kernel extensionC / C++ (+ a little asm)IOKit (C++) or BSD/KPI (C), runs in-kernel
For our build

We'll write a tiny SwiftUI container app + a Swift NEFilterDataProvider. Modern, fully Swift, no C/C++ needed — and it runs with SIP on.

5Anatomy: where an extension lives & how it loads

A System Extension isn't a standalone install. It's an .appex bundle nested inside a normal app. The app asks the OS to activate it; the OS asks you to approve it; then a separate provider process runs.

USER SPACE (sandboxed · SIP on) MyFilter.app in /Applications Contents/Library/ SystemExtensions/ Filter.appex sysextd the OS broker User approves System Settings toggle Provider process runs on its own crash = just this dies, OS lives visible in `list` activate launch KERNEL (the old way) .kext loaded in-kernel crash = panic · needs SIP off what we're NOT doing ① App calls OSSystemExtensionRequest.activationRequest(…)
The activation path: the app embeds the .appex, asks sysextd to activate it, the user approves once, then the provider runs as its own user-space process — all with SIP enabled.

6The entitlement reality check (why we pick Network Extension)

Two separate gates decide whether you can build and run your own extension. First: does the family need a special request form that only Apple can grant? Second: even once you can sign it, how does macOS let it load?

FamilyNeeds Apple's special request form?Notes
Network ExtensionNoThe capability is self-service in the Developer portal. You can build, sign, and provision it yourself.
Endpoint SecurityYesNeeds com.apple.developer.endpoint-security.client — granted only via Apple's System Extensions request form.
DriverKitYesDriverKit entitlements are Apple-granted too; and you'd want real hardware to drive.
Camera (CMIO)NoSelf-service, but more moving parts (virtual-device plumbing) than a filter.
The second gate — verified on your Mac

Your signature carries com.apple.developer.networking.networkextension, which is a restricted entitlement. macOS only lets a restricted-entitlement extension load through one of two doors:

① A provisioning profile that authorizes the entitlement → it loads with SIP on (the pro path; the NE capability is self-service, so no Apple wait).
② Developer mode (systemextensionsctl developer on) → which on macOS 15 requires SIP to be disabled first.

Both were proven on this machine while building this course: a from-scratch build carrying the NE entitlement with no profile is Killed: 9 at launch (that's AMFI, working as designed), and developer on refuses while SIP is enabled. Lesson 5 walks the SIP-on provisioning door.

Decision for this course

Still a Network Extension content filter (NEFilterDataProvider): it's the only interesting family with no Apple request form, it sees every network flow (immediately observable with log stream), and your paid Developer account can provision it with SIP left on. Endpoint Security and DriverKit we cover conceptually — they're gated behind the form.

7Quick check

Tap an answer — you'll get instant feedback. No score saved; this is just to make the ideas stick.

Q1. Your systemextensionsctl list shows OBS with a star under active but no star under enabled. What does that mean?

Right. active = the process is loaded; enabled = the user toggled it on. They're independent — OBS is "activated waiting for user."
Not quite. active means it is running; the empty enabled column just means the user hasn't flipped the System Settings toggle. State reads "activated waiting for user."

Q2. You want to build something on this Mac today without filing a request form and waiting on Apple. Which family?

That one needs a restricted, Apple-granted entitlement via the request form — there's a wait, even for local dev.
Exactly. The Network Extension capability is self-service — no Apple request form. You provision it yourself (or use developer mode). That's why it's our build target.

Q3. The big safety win of a System Extension over a kext is…

Yes. Out of the kernel, into user space: a crash is a dead process, not a panic — and no need to reduce security.
No — that describes a kext (in-kernel). The System Extension win is the opposite: out of the kernel, sandboxed, crash-isolated, SIP stays on.

8Flashcards

What is a System Extension, in one line?

A third-party extension that does kext-style jobs (network filtering, security monitoring, drivers) but runs as a sandboxed user-space process, loading with SIP enabled.

Name the four System Extension families.

Network Extension (VPN/filter/DNS/proxy), Endpoint Security (EDR/security events), DriverKit (hardware drivers, .dext), and Camera/CMIO (virtual cameras).

In systemextensionsctl list, what's the difference between the active and enabled columns?

active = the extension process is currently loaded/running. enabled = the user has switched it on in System Settings. They're independent (see OBS: active, not enabled).

Where does the extension binary physically live?

As an .appex inside the container app: YourApp.app/Contents/Library/SystemExtensions/. The app activates it via OSSystemExtensionRequest.

Which two families need an Apple-granted restricted entitlement even for local dev?

Endpoint Security (com.apple.developer.endpoint-security.client) and DriverKit. Network Extensions can run locally with developer mode on.

What language will our build use?

All Swift: a SwiftUI container app plus a Swift NEFilterDataProvider. No C or C++ required.

What are the two ways macOS will load a System Extension that carries a restricted entitlement?

① a provisioning profile authorizing the entitlement (loads with SIP on — the path properly-shipped vendor extensions like Tailscale & Mac Monitor use), or ② developer mode (systemextensionsctl developer on), which on macOS 15 requires SIP off.

9What's next

💬 I'm your teacher — ask me anything. Before Lesson 2: want me to explain what a "network flow" actually is, how NEFilterDataProvider decides allow/deny, or anything from the entitlement table? Or if you'd rather aim the build at a DNS proxy or transparent proxy instead of a content filter, say so and I'll re-plan. Just tell me to start Lesson 2 when you're ready.