Hi Apple,
We are working on a general USB device management solution on macOS for enterprise security. Our goal is to enforce policy-based restrictions on USB devices, such as:
For USB storage devices: block mount, read, or write access.
For other peripherals (e.g., USB headsets or microphones, raspberry pi, etc): block usage entirely.
We know in past, kernel extension would be the way to go, but as kext has been deprecated. And DriverKit is the new advertised framework.
At first, DriverKit looked like the right direction. However, after reviewing the documentation more closely, we noticed that using DriverKit for USB requires specific entitlements:
DriverKit USB Transport – VendorID
DriverKit USB Transport – VendorID and ProductID
This raises a challenge: if our solution is meant to cover all types of USB devices, we would theoretically need entitlements for every VendorID/ProductID in existence.
My questions are:
Is DriverKit actually the right framework for this kind of general-purpose USB device control?
If not, what framework or mechanism should we be looking at for enforcing these kinds of policies?
We also developed an Endpoint Security product, but so far we haven’t found a relevant Endpoint Security event type that would allow us to achieve this.
Any guidance on the correct technical approach would be much appreciated.
Thanks in advance for your help.
So, my first suggestion here is that you get I/O Registry Explorer from our "Additional Tools for Xcode" download so you can actually see what's going on. The same information in text from is in the sysdiagnose (that's how I know what happened), but in my experience, I/O Registry Explorer makes the data much easier to understand. For reference, you'll almost always want to look at the "IOService" plane (the default), and you change planes using the pop-up button in the top left. Also, over time, registry activity has increased to the point where the app can laggy or have other interface glitches. For general "exploration", you can avoid those issues by saving the ioreg file, then opening the file again so you're working with static data.
Moving to the specific issue:
We have test ES_EVENT_TYPE_AUTH_IOKIT_OPEN on 26, and it does not block anything. We report the same issue in FB19420236.
I can see AUTH requests coming from system stats and airportd processes, I can deny them, yet when I plug a keyboard and do it, the keyboard works without changes. I can see in the console that my deny was received.
Yes. You received and denied three auth requests. You were then notified of three events, from two processes.
Case 1-> pid=469, /usr/libexec/airportd:
[2025-08-06 20:53:54.669013579] [1754513634669] ES_EVENT_TYPE_NOTIFY_IOKIT_OPEN == 24, v=10, seq_num=11, global_seq_num=23
- I(pid=469, path=/usr/libexec/airportd, ppid=1,
team_id=(null)), T=1, C=AppleUSBHostDeviceUserClient, RID=4305578448,
RPATH=IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/usb-drd0@82280000/AppleT8103USBXHCI@00000000/usb-drd0-port-hs@00100000
/USB2.0 Hub@00100000/AppleUSB20Hub@00100000/AppleUSB20HubPort@00140000/Gaming Keyboard@00140000
parent_registry_id=4305578448
| name = Gaming Keyboard | 04F3:2014 | OK
To be honest, I'm not sure what's going on here, but I suspect there's an issue with what's being logged. airportd does have user clients open (12 to be exact), but none of them are tied to that device or even USB. My best guess is that you're either being notified of the same request you failed or there's something wrong with your logging; however, either way, this isn't why the keyboard works.
Case 2-> pid=430, path=/System/.../WindowServer
[2025-08-06 20:53:54.755309056] [1754513634755] ES_EVENT_TYPE_NOTIFY_IOKIT_OPEN == 24, v=10, seq_num=12, global_seq_num=24
- I(pid=430, path=/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer, ppid=1,
team_id=(null)),T=1702065507, C=IOHIDEventServiceUserClient, RID=4305578467,
RPATH=IOService:/AppleARMPE/arm-io@10F00000/AppleT810xIO/usb-drd0@82280000/AppleT8103USBXHCI@00000000/usb-drd0-port-hs@00100000
/USB2.0 Hub@00100000/AppleUSB20Hub@00100000/AppleUSB20HubPort@00140000/Gaming Keyboard@00140000
/IOUSBHostInterface@0/AppleUserUSBHostHIDDevice/IOHIDInterface/AppleUserHIDEventDriver
parent_registry_id=4305578467
| name = AppleUserHIDEventDriver | 0000:0000 | OK
The WindowServer is the process that actually receives HID activity from the kernel and turns it into the "events" that applications receive and process. The open went through because you didn't block it, probably because the process was muted (either by you or because the WindowServer is on the default mute list). If you unmute the process and deny the open, the keyboard won't work.
Covering a few other details, it's worth noticing that the connections here are happening at very different levels of the system— you denied at the USB level ("AppleUSBHostDeviceUserClient"), but the WindowServer is connecting through HID ("IOHIDEventServiceUserClient"). That's relevant because the different user client levels have very different roles and capabilities. Ironically, the USB device user client (what you blocked) doesn't actually "do" very much— it exists so a process can attach to a particular device and acquire exclusive access, but actually "doing" something with the device generally means connecting to an interface, which I believe would have triggered a new auth request for the interface to open.
As a side note here, if you choose to unmute the WindowServer, be VERY careful when choosing what to actually block. By my count, it has 147 user client connections, most of which are very likely critical to general system function. Blocking the wrong connections is very likely to cripple your system and/or panic the kernel.
Next, I have to ask "what are you actually trying to do?“ There are actually two different issues with blocking HID devices:
-
The HID specification is widely used as a software control/configuration interface, not just for "keyboards and mice". This is not an abstract edge case as, by my count, there are 131 separate IOHIDEventServiceUserClient's active on your test machine, most of which are obviously not "Human Interface Devices".
-
If your actual goal here is to prevent/control how input enters the system, then IOKit can only block one very specific pathway among many. To start with, the Bluetooth stack is largely implemented in user space, NOT the kernel. I believe the current implementation works by creating an "artificial" HID device in the kernel (so blocking might still work), but nothing requires it to work this way, given that most of its implementation is already outside the kernel. However, the bigger issue is that the WindowServer itself has its own event API which will completely bypass all of this.
If your goal is to block/control input, then I think the only real option is to use CGEventTap to directly interact with the event stream. If you try to do it below that layer, I think you'll find that there are simply too many other edge cases and side channels to try and predict and cover. Note that moving to the higher level also means using an agent running in the user session instead of a daemon running "system wide".
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware