yuzu

peripherals

What it doesUSB, PCI and Thunderbolt/USB4 device inventory
Version1.0.0
KindCollector · read-only · gathered (crossplatform.peripherals.usb, crossplatform.peripherals.pci, crossplatform.peripherals.thunderbolt)
PlatformsWindows ✅ · macOS ✅ · Linux ✅
Actionspci (definition crossplatform.peripherals.pci) · thunderbolt (definition crossplatform.peripherals.thunderbolt) · usb (definition crossplatform.peripherals.usb)
Securitysecurable Inventory · operation Read · risk Low · dispatch ReadOnly · approval gate None
Rolesexecute: endpoint-admin, endpoint-operator · author: content-author

How it works

usb, pci and thunderbolt each read one bus’s device tree from the operating system and emit one row per device or node. All three are pure reads: nothing is configured, authorised or written. A bus with no devices answers with one <kind>|none row and a complete status; a read that could not be attempted answers with one <kind>|unavailable|<token> row and a CONSTRAINED / PARTIAL status, so an empty list is never standing in for a failure.

Windows asks SetupAPI (SetupDiGetClassDevsW) for present devices under the USB or PCI enumerator, and thunderbolt is the PCI walk filtered to devices whose description names Thunderbolt or USB4. macOS asks IOKit for IOUSBHostDevice, IOPCIDevice or IOThunderboltSwitch services and reads their registry properties. Linux lists /sys/bus/usb/devices, /sys/bus/pci/devices or /sys/bus/thunderbolt/devices and reads the attribute files in each. No leg starts a subprocess.

The plugin does not translate ids into names: no pci.ids or usb.ids database is shipped, so vendor and product text appears only where the operating system already supplies it. It does not cover displays, Bluetooth, audio or camera devices, and it does not report whether a device is working or what is plugged in physically beyond what each bus tree exposes.

flowchart LR
  OP["Operator or workflow"] --> SRV["Server authz: Inventory.Read"]
  SRV -- "gRPC mTLS" --> HOST["Agent plugin host"] --> EX["peripherals.execute"]
  EX --> WIN["Windows leg: SetupAPI"]
  EX --> MAC["macOS leg: IOKit registry"]
  EX --> LIN["Linux leg: sysfs bus directories"]
  WIN --> ROWS["Rows and typed result status"]
  MAC --> ROWS
  LIN --> ROWS
  ROWS --> RS[("ResponseStore")] --> API["REST api/responses"]

What you can decide with it

QuestionActionWindowsmacOSLinux
Which USB devices are attached?usbEvery present node under the USB enumerator, including each interface of a composite device (instance ids ending &MI_nn) and root hubs. Serial and speed are always -.One row per USB device (IOUSBHostDevice), with serial and negotiated speed.One row per USB device; interface entries are skipped. Serial and speed come from sysfs.
Which PCI devices does the host have (GPU, NIC, storage controller)?pciVendor, device and class ids and a description; subsystem ids always 0000 and driver always -.Ids, class, subsystem ids, the registry name as description and a child class name as driver.Ids, class, subsystem ids and the bound driver; description always -.
Is there a Thunderbolt or USB4 controller, and are attached devices authorised?thunderboltThe host controller only, found by a text match on the description. No attached devices, no authorisation state.Controllers and attached switches; authorized is not exposed (-).Domains (host controller) and attached devices with authorized from sysfs; no live Thunderbolt bus has run this leg.
Is this specific model of dock, token or adapter in use?usb, match vendor_id and product_idYes, via the hardware id.Yes.Yes.

Bus by OS, with what each leg cannot show:

BusSourceRow identityNot visible
USBSetupAPI, IOKit, sysfsWindows: device instance id, one row per interface on composite devices. macOS: IOKit location id. Linux: sysfs name such as 1-2.1.Windows: serial, speed. All: devices behind a hub the OS has not enumerated, and unplugged devices.
PCISetupAPI, IOKit, sysfsWindows: instance id. macOS: the registry pcidebug location, which several bridges can share. Linux: 0000:bus:slot.fn.Windows: subsystem ids and driver. Linux: description.
ThunderboltSetupAPI text match, IOKit, sysfsWindows: instance id. macOS: route string. Linux: domainN or N-M.Windows: any downstream device. macOS: authorisation. Linux: retimer children.

What a result does and does not prove: a row is a node in the OS device tree, not a physical device, so one dock or keyboard can appear as several USB rows on Windows. usb|none or pci|none with OK means the tree was read and was empty. Two machines with the same peripheral report the same ids, so ids can be joined across the fleet, but only serial (where present) distinguishes one unit from another.

Diagrams

Every action ends in one of three outcomes:

stateDiagram-v2
  [*] --> Read
  Read --> Rows: devices found
  Read --> None: tree read, no devices
  Read --> Unavailable: tree could not be read
  Rows --> OK
  None --> OK
  Unavailable --> Constrained
  OK --> [*]
  Constrained --> [*]

OK carries either device rows or a single <kind>|none row. Constrained carries a single <kind>|unavailable|<token> row naming the cause (Windows getclassdevs_failed with the Win32 error, macOS matching_failed, Linux eacces or read_failed). An absent bus directory on Linux, such as no Thunderbolt bus, is none, not a failure.

OS capability

ActionWindowsmacOSLinux
pci✅ supported · rung 1 · SetupAPI (PCI enumerator)✅ supported · rung 1 · IOKit IOServiceMatching(IOPCIDevice)✅ supported · rung 1 · /sys/bus/pci/devices sysfs attribute reads
thunderbolt🟡 constrained · rung 1 · SetupAPI PCI enumerator, DEVICEDESC contains Thunderbolt/USB4✅ supported · rung 1 · IOKit IOServiceMatching(IOThunderboltSwitch)🟡 constrained · rung 1 · /sys/bus/thunderbolt/devices sysfs reads
usb✅ supported · rung 1 · SetupAPI SetupDiGetClassDevsW(USB enumerator) + SPDRP_HARDWAREID/COMPATIBLEIDS✅ supported · rung 1 · IOKit IOServiceMatching(IOUSBHostDevice)✅ supported · rung 1 · /sys/bus/usb/devices sysfs attribute reads

Declared limits per leg (descriptor fallback text, verbatim):

Privileges and prerequisites

OSRuns asExtra grant neededMeasuredIf the read is refused
Windowsagent service account (LocalSystem today)None documented for this plugin (docs/agent-privilege-model.md)Bare-metal Windows 10.0.26200, LocalSystem (elevated), 2026-09-08; see Sample outputSetupDiGetClassDevsW fails: the action reports <kind>|unavailable|windows:setupapi:getclassdevs_failed:<GetLastError>
macOSagent daemon, root today (the LaunchDaemon has no UserName key; see docs/agent-privilege-model.md)None: IOKit registry property reads are unprivilegedBare-metal macOS, unprivileged (uid 501), 2026-09-14; no root-privileged run has been captured, so root is the documented production identity, not a measured oneIOServiceGetMatchingServices fails: <kind>|unavailable|macos:iokit:matching_failed. A query that succeeds but matches nothing is a clean <kind>|none
Linuxagent daemon, dedicated unprivileged account, never root by design (docs/agent-privilege-model.md)None: the sysfs bus directories under /sys/bus/{usb,pci,thunderbolt}/devices are world-readable by defaultDebian container, root (euid 0), 2026-09-14; the Thunderbolt walk is exercised only against a fixture treeA bus directory that exists but cannot be read reports <kind>|unavailable|linux:sysfs:eacces or linux:sysfs:read_failed. An absent bus directory is a clean <kind>|none

Binaries and subprocesses: none. Every leg is an in-process call (SetupAPI, IOKit, or a direct filesystem read of sysfs). Network: none.

Data contract

Inputs

No action takes parameters.

Outputs

Pipe-delimited rows, one per device or node, with the action name as the first field. Every free-text field goes through the shared untrusted-output escaper, and a field the platform does not supply is -. A bus with no devices reports a single <kind>|none row; a read that could not be attempted reports <kind>|unavailable|<token> instead of device rows. A USB or PCI row is a node in the OS device tree, not necessarily a physical device: Windows lists each interface of a composite USB device separately.

crossplatform.peripherals.pci — bus_path|vendor_id|device_id|class|subsystem_vendor|subsystem_device|driver|description

FieldTypeValuesAvailableExampleDescription
bus_pathstring-Windows, Linux, macOS0000:00:02.0Platform-native PCI bus location (e.g. a SetupAPI device instance path, a sysfs bus path, or an IOKit registry path).
vendor_idstring-Windows, Linux, macOS8086PCI vendor id, 4 lowercase hex digits.
device_idstring-Windows, Linux, macOS9a49PCI device id, 4 lowercase hex digits.
classstring-Windows, Linux, macOS030000PCI class code (base class + subclass + programming interface), 6 lowercase hex digits.
subsystem_vendorstring-Windows, Linux, macOS1028PCI subsystem vendor id, 4 lowercase hex digits; “0000” where unread.
subsystem_devicestring-Windows, Linux, macOS0a5cPCI subsystem device id, 4 lowercase hex digits; “0000” where unread.
driverstring-Windows, Linux, macOSi915Bound driver name, where the platform exposes one; ”-” otherwise.
descriptionstring-Windows, Linux, macOSIntel Iris Xe GraphicsHuman-readable device description, where the platform exposes one; ”-” otherwise.

crossplatform.peripherals.thunderbolt — path|role|vendor|model|unique_id|generation|authorized

FieldTypeValuesAvailableExampleDescription
pathstring-Windows, Linux, macOSdomain0/0-0Platform-native location for this node (e.g. a SetupAPI device instance path, a sysfs bus path, or an IOKit registry path).
rolestring-Windows, Linux, macOShost_controllerReported node role. Values: host_controller, device.
vendorstring-Windows, Linux, macOSApple Inc.Vendor string, where the platform exposes one; ”-” otherwise.
modelstring-Windows, Linux, macOSThunderbolt 4 Pro DockModel/device name string, where the platform exposes one; ”-” otherwise.
unique_idstring-Windows, Linux, macOS00340000-0044-4123-8000-000103ffffffNode-unique identifier, where the platform exposes one; ”-” otherwise.
generationstring-Windows, Linux, macOS4Reported Thunderbolt/USB4 generation, where the platform exposes one (e.g. “4”); ”-” otherwise.
authorizedstring-Windows, Linux, macOS1Security-level authorization state, where the platform exposes one. Values: 1 (authorized), 0 (not authorized), - (not exposed by this platform mechanism).

crossplatform.peripherals.usb — bus_path|vendor_id|product_id|class|subclass|vendor|product|serial|speed|is_hub

FieldTypeValuesAvailableExampleDescription
bus_pathstring-Windows, Linux, macOS1-2.1Platform-native bus location for this device (e.g. a SetupAPI device instance path, a sysfs bus path, or an IOKit registry path).
vendor_idstring-Windows, Linux, macOS046dUSB vendor id, 4 lowercase hex digits.
product_idstring-Windows, Linux, macOSc52bUSB product id, 4 lowercase hex digits.
classstring-Windows, Linux, macOS09USB device class code, 2 lowercase hex digits.
subclassstring-Windows, Linux, macOS00USB device subclass code, 2 lowercase hex digits.
vendorstring-Windows, Linux, macOSLogitechVendor string, where the device or platform exposes one; ”-” otherwise.
productstring-Windows, Linux, macOSUSB ReceiverProduct string, where the device or platform exposes one; ”-” otherwise.
serialstring-Windows, Linux, macOS-Device serial number, where exposed; ”-” otherwise.
speedstring-Windows, Linux, macOS480Negotiated link speed, platform-native token (e.g. “480” Mbps); ”-” where unread.
is_hubboolean-Windows, Linux, macOS0Whether this node is itself a USB hub. Values: 1 (hub), 0 (not a hub) — a digit, never true/false.

Result status

StatusCompletenessProvenanceWhen
OKFULL—The leg attempted the read and either found devices or found none (<kind>|none) — an empty bus is a complete answer, not a degradation.
CONSTRAINEDPARTIALlinux:sysfs:eaccesLinux: the bus’s devices directory exists but could not be opened (permission denied).
CONSTRAINEDPARTIALlinux:sysfs:read_failedLinux: the bus’s devices directory exists but listing it failed for a reason other than EACCES.
CONSTRAINEDPARTIALmacos:iokit:matching_failedmacOS: IOServiceGetMatchingServices itself returned a non-success kern_return_t — the IOKit main port was unreachable, not merely “no matches”.
CONSTRAINEDPARTIALwindows:setupapi:getclassdevs_failedWindows: SetupDiGetClassDevsW itself failed; the row and status reason carry the literal token with GetLastError()’s numeric code appended.

Where the data goes

Sample output

Windows — captured: windows Windows 10.0.26200 x86_64 · bare-metal · 2026-09-08 · LocalSystem (elevated) · leg-hash 25220299bc79

== action=usb
usb|USB/VID_046D&PID_C548&MI_03/7&247BA993&0&0003|046d|c548|03|00|(Standard system devices)|USB Input Device|-|-|0
usb|USB/VID_0B05&PID_18F3/9876543210|0b05|18f3|00|00|(Standard USB Host Controller)|USB Composite Device|-|-|0
usb|USB/VID_0B0E&PID_2E56&MI_03/9&3135CDCD&0&0003|0b0e|2e56|03|00|(Standard system devices)|USB Input Device|-|-|0
usb|USB/VID_0B0E&PID_2E56/6CFBEDC4770B|0b0e|2e56|00|00|(Standard USB Host Controller)|USB Composite Device|-|-|0
usb|USB/VID_8087&PID_0029/8&384B90AF&0&6|8087|0029|e0|01|Intel Corporation|Intel(R) Wireless Bluetooth(R)|-|-|0
usb|USB/VID_0B05&PID_18F3&MI_02/A&2EFC6B98&0&0002|0b05|18f3|03|00|(Standard system devices)|USB Input Device|-|-|0
usb|USB/ROOT_HUB30/7&14C6E8AD&0&0|0000|0000|00|00|(Standard USB HUBs)|USB Root Hub (USB 3.0)|-|-|0
usb|USB/VID_174C&PID_3074/8&1B878445&0&8|174c|3074|00|00|(Standard USB HUBs)|Generic SuperSpeed USB Hub|-|-|0
usb|USB/VID_046D&PID_C548&MI_01/9&32226396&0&0001|046d|c548|03|01|(Standard system devices)|USB Input Device|-|-|0
usb|USB/VID_046D&PID_C548&MI_02/9&32226396&0&0002|046d|c548|03|00|(Standard system devices)|USB Input Device|-|-|0
usb|USB/VID_046D&PID_C548&MI_00/7&247BA993&0&0000|046d|c548|03|01|Logitech (x64)|Logitech USB Input Device|-|-|0
usb|USB/VID_0B0E&PID_2E56&MI_00/9&3135CDCD&0&0000|0b0e|2e56|01|01|(Generic USB Audio)|Jabra Link 390|-|-|0
… 12 of 24 rows shown
[result_status] OK / FULL

== action=pci
pci|PCI/VEN_1022&DEV_1444&SUBSYS_00000000&REV_00/3&11583659&0&C4|1022|1444|060000|0000|0000|-|PCI standard host CPU bridge
pci|PCI/VEN_1022&DEV_1486&SUBSYS_88081043&REV_00/4&231A312E&0&0141|1022|1486|108000|0000|0000|-|AMD PSP 11.0 Device
pci|PCI/VEN_8086&DEV_1539&SUBSYS_85F01043&REV_03/6&2AD155D1&0&0028000A|8086|1539|020000|0000|0000|-|Intel(R) I211 Gigabit Network Connection
pci|PCI/VEN_1022&DEV_1441&SUBSYS_00000000&REV_00/3&11583659&0&C1|1022|1441|060000|0000|0000|-|PCI standard host CPU bridge
pci|PCI/VEN_1022&DEV_1485&SUBSYS_88081043&REV_00/6&313998C&0&0040000A|1022|1485|130000|0000|0000|-|AMD PCI
pci|PCI/VEN_1022&DEV_790E&SUBSYS_87C01043&REV_51/3&11583659&0&A3|1022|790e|060100|0000|0000|-|PCI standard ISA bridge
pci|PCI/VEN_1022&DEV_1484&SUBSYS_88081043&REV_00/3&11583659&0&39|1022|1484|060400|0000|0000|-|PCI-to-PCI Bridge
pci|PCI/VEN_1022&DEV_1484&SUBSYS_88081043&REV_00/3&11583659&0&41|1022|1484|060400|0000|0000|-|PCI-to-PCI Bridge
pci|PCI/VEN_1022&DEV_149C&SUBSYS_88081043&REV_00/6&313998C&0&0140000A|1022|149c|0c0330|0000|0000|-|USB xHCI Compliant Host Controller
pci|PCI/VEN_10DE&DEV_228B&SUBSYS_40761458&REV_A1/4&1D81E16&0&0119|10de|228b|040300|0000|0000|-|High Definition Audio Controller
pci|PCI/VEN_1022&DEV_57A3&SUBSYS_88081043&REV_00/5&2BFB86CE&0&08000A|1022|57a3|060400|0000|0000|-|PCI-to-PCI Bridge
pci|PCI/VEN_1022&DEV_1446&SUBSYS_00000000&REV_00/3&11583659&0&C6|1022|1446|060000|0000|0000|-|PCI standard host CPU bridge
… 12 of 48 rows shown
[result_status] OK / FULL

== action=thunderbolt
thunderbolt|none
[result_status] OK / FULL

macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-09-14 · euid 501 (USB hub serial and Thunderbolt controller IDs replaced with placeholders) · leg-hash 25220299bc79

== action=usb
usb|02200000|05ac|800c|09|00|Apple|USB3 Gen2 Hub|0000001|super+|1
usb|02100000|05ac|800b|09|00|Apple|USB2 Hub|0000001|high|1
[result_status] OK / FULL

== action=pci
pci|0:0:0(1:128)|106b|1017|060400|0000|0000|ApplePCIECHostBridge|pci-bridge
pci|0:0:0(1:128)|106b|1017|060400|0000|0000|ApplePCIECHostBridge|pci-bridge
pci|0:0:0(1:128)|106b|1017|060400|0000|0000|ApplePCIECHostBridge|pci-bridge
pci|0:0:0(2:2)|106b|100c|060400|0000|0000|ApplePCIEHostBridge|pci-bridge
pci|0:2:0(1:1)|106b|100c|060400|0000|0000|ApplePCIEHostBridge|pci-bridge
pci|1:0:0|14e4|1682|020000|14e4|1682|BCM5701Enet|ethernet
pci|2:0:0|14e4|4434|028000|106b|4388|IOUserService|pci14e4,4434
pci|2:0:1|14e4|5f72|028000|106b|4388|AppleConvergedPCI|pci14e4,5f72
[result_status] OK / FULL

== action=thunderbolt
thunderbolt|0|host_controller|Apple Inc.|iOS|05ac000000000001|-|-
thunderbolt|0|host_controller|Apple Inc.|iOS|05ac000000000002|-|-
thunderbolt|0|host_controller|Apple Inc.|iOS|05ac000000000003|-|-
[result_status] OK / FULL

Linux — captured: linux Debian GNU/Linux 13 (trixie) aarch64 · container · 2026-09-14 · euid 0 · leg-hash 25220299bc79

== action=usb
usb|usb1|1d6b|0002|09|00|Linux 7.0.12-linuxkit vhci_hcd|USB/IP Virtual Host Controller|vhci_hcd.0|480|1
usb|usb2|1d6b|0003|09|00|Linux 7.0.12-linuxkit vhci_hcd|USB/IP Virtual Host Controller|vhci_hcd.0|10000|1
[result_status] OK / FULL

== action=pci
pci|0000:00:08.0|1af4|105a|018000|1af4|005a|virtio-pci|-
pci|0000:00:0d.0|1af4|105a|018000|1af4|005a|virtio-pci|-
pci|0000:00:10.0|1af4|1045|058000|1af4|0045|virtio-pci|-
pci|0000:00:01.0|1af4|1041|020000|1af4|0041|virtio-pci|-
pci|0000:00:07.0|1af4|1042|018000|1af4|0042|virtio-pci|-
pci|0000:00:0c.0|1af4|105a|018000|1af4|005a|virtio-pci|-
pci|0000:00:0f.0|1af4|1044|100000|1af4|0044|virtio-pci|-
pci|0000:00:00.0|106b|1a05|060000|0000|0000|-|-
pci|0000:00:06.0|1af4|1042|018000|1af4|0042|virtio-pci|-
pci|0000:00:0b.0|1af4|105a|018000|1af4|005a|virtio-pci|-
pci|0000:00:09.0|1af4|105a|018000|1af4|005a|virtio-pci|-
pci|0000:00:0e.0|1af4|1053|078000|1af4|0053|virtio-pci|-
… 12 of 14 rows shown
[result_status] OK / FULL

== action=thunderbolt
thunderbolt|none
[result_status] OK / FULL

Caveats and known gaps

  1. Windows Thunderbolt identification is a text match and sees only the host controller. SetupAPI has no Thunderbolt device class, so the leg matches a PCI device description containing “Thunderbolt” or “USB4” and reports every match as role host_controller. Downstream Thunderbolt devices and their authorisation state are not visible on Windows.
  2. Linux Thunderbolt is verified against a fixture tree only. The /sys/bus/thunderbolt/devices walk has been exercised against a synthetic sysfs tree; no live Linux host with a Thunderbolt bus has run it. The Linux sample is a container showing only virtual USB and virtio PCI devices, so it does not show real hardware rows.
  3. Some fields are always unfilled. Linux pci description is always - because sysfs carries none. Windows pci has subsystem ids 0000 and driver -, and Windows usb has serial and speed -; the Windows USB vendor is the driver manufacturer string, so it can read like (Standard USB Host Controller) rather than a company.
  4. macOS pci and thunderbolt identities are weak. driver is the registry child’s class name (for example a bridge class), not a driver bundle identifier, and the pcidebug location used as bus_path can repeat across bridges, as the sample shows. authorized is always - on macOS.
  5. Coverage is limited to the three buses. There is no vendor-name lookup and no coverage of displays, Bluetooth, audio or camera devices. A name that looks incomplete, such as a blank vendor, means the operating system did not supply one.

Source and tests