yuzu

wifi

What it doesScans visible WiFi networks and reports current connection status
Version1.1.0
KindCollector · read-only · gathered (device.wifi.list_networks, device.wifi.connected)
PlatformsWindows ✅ · macOS 🟡 constrained · Linux 🟡 constrained
Actionsconnected (definition device.wifi.connected) · list_networks (definition device.wifi.list_networks)
Securitysecurable Infrastructure · operation Read · risk Low · dispatch ReadOnly · approval gate None
Rolesexecute: endpoint-admin, endpoint-operator · author: content-author

How it works

Both actions are read-only: the plugin never joins, leaves or reconfigures a network. list_networks returns one row per visible access point; connected returns the network the radio is currently associated with. Each OS answers from its own Wi-Fi stack, and the Linux and macOS legs fall back down a ladder of tools when the preferred source does not answer.

A tool counts as having answered only if it exited with status zero. A spawn failure or a non-zero exit is therefore reported as an unavailable or constrained result, never as “Not connected” or an empty airspace. The plugin does not tell WEP from Open on the Linux D-Bus leg.

flowchart LR
  OP["Operator or workflow"] --> SRV["Server<br/>authz: Infrastructure.Read"]
  SRV -- "gRPC mTLS" --> HOST["Agent plugin host"] --> EX["wifi.execute"]
  EX --> WIN["Windows leg<br/>WlanGetAvailableNetworkList, WlanQueryInterface"]
  EX --> MAC["macOS leg<br/>airport, system_profiler for list_networks<br/>CoreWLAN for connected"]
  EX --> LIN["Linux leg<br/>NetworkManager D-Bus, then nmcli, then iw, iwlist, iwconfig"]
  WIN & MAC & LIN --> ROWS["rows and typed result status"]
  ROWS --> RS[("ResponseStore")] --> API["REST /api/responses and MCP"]

What you can decide with it

QuestionActionWindowsmacOSLinux
Is this device on the corporate network name?connectedSSID, signal percent, authentication type, BSSID and adapter description, one row per connected adapterAssociation, signal in dBm, channel and security are reliable; SSID and BSSID are usually the <ssid-withheld> marker on macOS 14 and laterSSID, signal percent, security, BSSID and the interface name, when NetworkManager (or nmcli or iwconfig) can see the radio
Is any open or unexpected network visible?list_networksThe networks Windows has cached for each adapter (no BSSID or channel), with an Open authentication label for unsecured onesNormally one wifi|info row saying the scan is unavailable on macOS 14 and laterSSID, signal percent, security, channel and BSSID for each AP NetworkManager or nmcli reports
Does this device have a Wi-Fi radio at all?eitherlist_networks writes a No wireless interfaces found error row; connected cannot tell youNeither action says so; connected reports “Not connected” for a host with no Wi-Fi interfacelist_networks writes an info row naming the missing interface or tools

How to read a negative: Not connected on Windows and macOS also appears when the host has no wireless adapter, so it does not prove a radio exists. connected|unknown|... means nothing answered, which is different from not connected. A wifi|info|...no networks visible row means a source answered and found none; a wifi|error|... row means the scan did not happen. This is a per-device snapshot, not a site survey: signal units differ by OS (percent on Windows and Linux, dBm on macOS) and are not comparable across a mixed fleet.

Diagrams

flowchart TD
  A["list_networks on Linux"] --> B{"NetworkManager reachable, has a Wi-Fi device, finished scan?"}
  B -- "yes" --> C["Rows from cached AccessPoints, or an info row if none"]
  B -- "no" --> D["Record wifi:nm_dbus_fallthrough"] --> E{"nmcli exited 0?"}
  E -- "yes" --> F["Rows from nmcli, or an info row if none"]
  E -- "no" --> G{"iw dev lists interfaces?"}
  G -- "yes" --> H["iwlist scan per interface, 45 s budget between scans"]
  H --> I{"Did any scan exit 0?"}
  I -- "yes" --> J["scan_output rows, or an info row"]
  I -- "no" --> K["Error row, UNAVAILABLE wifi:iwlist_scan_failed"]
  G -- "no" --> L["Error row, or info row if iw ran and found none"]

The ladder stops at the first rung that answers. A fallthrough from D-Bus is recorded as provenance but is not a failure, because the next rung answers in full. The 45 second budget is checked between iwlist runs, so it bounds the slowest rung, not the whole action. connected descends the same way: D-Bus, then the active row of nmcli device wifi list, then an iwconfig ESSID blob, then a connected|unknown row when none of them answered.

OS capability

ActionWindowsmacOSLinux
connected✅ supported · rung 1 · WlanQueryInterface🟡 constrained · rung 1 · CoreWLAN🟡 constrained · rung 1 · NetworkManager D-Bus (sd-bus)
list_networks✅ supported · rung 1 · WlanGetAvailableNetworkList🟡 constrained · rung 2 · airport -s / system_profiler via argv runner🟡 constrained · rung 1 · NetworkManager D-Bus (sd-bus)

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

Privileges and prerequisites

OSRuns asExtra grant neededMeasuredIf the read is refused
Windowsagent service account (LocalSystem today, #1442)None: the WLAN API calls need no elevation (docs/agent-privilege-model.md, wifi.* row)2026-09-07, bare-metal, as SYSTEMA WLAN API failure is not privilege-specific. connected reports UNAVAILABLE/PARTIAL with a named wifi:wlan_* token; list_networks writes a wifi|error|Cannot open WLAN handle row (and sets no status). A single interface whose list call fails is skipped silently (caveat 1).
macOSagent LaunchDaemon, root by default (no UserName key; docs/agent-privilege-model.md “macOS is the current exception”); the plugin itself needs no elevationNone for connected (CoreWLAN reads are unprivileged). The system_profiler fallback of list_networks needs a Location Services grant a background daemon cannot obtain.2026-09-07, bare-metal, as an unprivileged interactive user, not the production root LaunchDaemon identitylist_networks: CONSTRAINED/PARTIAL with wifi:macos_scan_requires_location_services and a wifi|info|... sentinel row. connected: SSID and BSSID become <ssid-withheld>/- on an otherwise live connection, never a false “Not connected”.
Linuxagent unprivileged account (yuzu-agent on the packaged unit; docs/agent-privilege-model.md TL;DR)None for the NetworkManager read or the nmcli fallback. The last rung, iwlist <interface> scan, is expected to need root for an active scan (inferred from the tool, not shown by the plugin code), which the agent neither holds nor requests.2026-09-06, container, as root (euid 0), with no wireless tools installedUnder the least-privilege agent iwlist is expected to fail or return nothing (not measured); if no scan exits zero, list_networks reports UNAVAILABLE/PARTIAL with wifi:iwlist_scan_failed and a wifi|error|Wi-Fi scan failed on every discovered interface row rather than an empty airspace.

Binaries and network: Linux opens a connection to the system D-Bus for NetworkManager (local IPC, not a subprocess) and spawns nmcli, iw, iwlist and iwconfig through the bounded argv runner (20 second deadline per call, 5 second budget for the D-Bus calls). macOS spawns airport (absent on macOS 14 and later) and system_profiler the same way. Windows spawns nothing and links wlanapi directly. No leg makes a network call of its own.

Data contract

Inputs

Neither action takes parameters.

Outputs

Rows are pipe-delimited. list_networks emits zero or more wifi|... rows, or a single wifi|info|..., wifi|error|... or wifi|scan_output|... row when the scan could not enumerate real access points; connected emits one connected|... row (Windows emits one per connected adapter). The fourth and fifth list_networks columns mean different things per OS (see the table below), and the wifi|scan_output|<blob> fallback, the raw iwlist text after ESSID, Quality and Encryption filtering, fills none of the declared columns and is one opaque field. Every free-text field (SSID, security, BSSID, raw scan text) is escaped through safe_output_field at the emission site, because wifi is not a key-value plugin on the server and an unescaped | or newline in an attacker-controlled SSID would shift columns or fabricate a row.

device.wifi.connected — ssid|signal|security|bssid|interface_or_channel

FieldTypeValuesAvailableExampleDescription
ssidstring-Windows, Linux, macOSnoneThe connected network’s SSID; sentinel none/unknown when disconnected or undetermined; on macOS when Location Services blocks the read on an otherwise-live association; hex:… on the Linux D-Bus leg for a non-UTF-8 SSID. Values: SSID text, none, unknown, , or hex:….
signalstring-Windows, Linux, macOSNot connectedSignal strength using the same per-OS units as list_networks’ signal field; on a disconnected/undetermined sentinel row this carries the free-text message instead (e.g. “Not connected”). Values: integer, or a free-text sentinel message.
securitystring-Windows, Linux, macOS0Security type of the connected access point; “unknown” on the Linux iwconfig blob fallback; “0” on the disconnected/undetermined sentinel. Values: free text, unknown, or 0.
bssidstring-Windows, Linux, macOSnoneConnected access point’s MAC address; - or none when not associated or unknown. Values: MAC address, -, or none.
interface_or_channelstring-Windows, Linux, macOSnoneWireless interface name on Linux/Windows (e.g. wlan0, an adapter description); 802.11 channel number on macOS; none when undetermined. Values: interface name string, channel number, or none.

device.wifi.list_networks — ssid|signal|security|channel_or_type|bssid_or_connected

FieldTypeValuesAvailableExampleDescription
ssidstring-Windows, Linux, macOSinfoThe network’s SSID, or a sentinel discriminator (info/error/ scan_output) when the row is a status line rather than an access point. Values: SSID text, (empty SSID), hex:… (Linux D-Bus non-UTF-8 SSID), or info/error/scan_output.
signalstring-Windows, Linux, macOSwi-fi scan unavailable; airport removed in macOS 14+ and system_profiler requires Location ServicesSignal strength: 0-100 quality/percent on Windows and Linux, RSSI in dBm on macOS; on a sentinel row this field carries the free-text message instead. Values: integer 0-100 (Windows/Linux), a dBm integer (macOS), or free text on a sentinel row.
securitystring-Windows, Linux, macOS0The access point’s security type in the emitting leg’s own vocabulary (e.g. Open, WPA2-Personal, WPA3, 802.1X); “0” on a sentinel row. Values: free text, or 0 on a sentinel row.
channel_or_typestring-Windows, Linux, macOS0802.11 channel number on Linux/macOS; BSS type (Infrastructure/Ad-hoc/Unknown) on Windows — the two OS families populate genuinely different data in this column. Values: channel number as a string, 0 when unknown, or Infrastructure/Ad-hoc/Unknown.
bssid_or_connectedstring-Windows, Linux, macOSnoneAccess point MAC address on Linux/macOS; the literal true/false for whether this network is the one currently connected on Windows — again a different meaning per OS. Values: MAC address, - when unknown, or true/false.

Result status

StatusCompletenessProvenanceWhen
UNDECLARED (default)UNKNOWN(empty)Windows list_networks (every path, including the error rows), Windows connected when no interface is connected, macOS connected, and every Linux or macOS result that completed without a degrade. The plugin sets no status on these paths.
OKFULLwifi:nm_dbus_fallthroughLinux, both actions: the NetworkManager D-Bus rung did not answer and the action fell through to nmcli. This is provenance, not a failure, because the next rung answers in full.
CONSTRAINEDPARTIALwifi:action_budget_exhaustedLinux list_networks: the 45 second budget ran out between iwlist scans with interfaces still unscanned
CONSTRAINEDPARTIALwifi:macos_scan_requires_location_servicesmacOS list_networks: neither airport nor system_profiler produced rows
UNAVAILABLEPARTIALwifi:iwlist_scan_failedLinux list_networks: interfaces were discovered but no iwlist scan exited zero
UNAVAILABLEPARTIALwifi:no_wireless_toolsLinux list_networks: neither nmcli nor iw answered and no runner-level failure was already forwarded
UNAVAILABLEPARTIALwifi:wlan_open_handle_failed / wifi:wlan_enum_interfaces_failed / wifi:wlan_query_interface_failedWindows connected: the WLAN API call itself failed
UNAVAILABLEPARTIALwifi:nmcli_and_iwconfig_failedLinux connected: neither nmcli nor iwconfig answered and no runner-level failure was already forwarded
UNAVAILABLEPARTIALsubprocess_runner:spawn_errorA tool could not be spawned at all; this is what docs/samples/linux.txt shows for both actions
CONSTRAINEDPARTIALsubprocess_runner:deadlineThe 20 second runner deadline elapsed and the child was killed
CONSTRAINEDPARTIALsubprocess_runner:cancelledThe run was cancelled before it finished
CONSTRAINEDPARTIALsubprocess_runner:signaledThe child was killed by a signal rather than exiting cleanly
OKPARTIALsubprocess_runner:line_limitDefined by the shared runner for a capped line count. This plugin sets no line limit, so it does not occur here.

Where the data goes

Sample output

Windows — captured: windows Microsoft Windows NT 10.0.26200.0 x64 · bare-metal · 2026-09-07 · SYSTEM · leg-hash 08531a51b9d2

== action=list_networks
[result_status] UNDECLARED / UNKNOWN

== action=connected
connected|none|Not connected|0|none|none
[result_status] UNDECLARED / UNKNOWN

macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-09-07 · euid 501 (jsmith) · leg-hash 08531a51b9d2

== action=list_networks
wifi|info|wi-fi scan unavailable; airport removed in macOS 14+ and system_profiler requires Location Services|0|0|none
[result_status] CONSTRAINED / PARTIAL / wifi:macos_scan_requires_location_services

== action=connected
connected|none|Not connected|0|none|none
[result_status] UNDECLARED / UNKNOWN

Linux — captured: linux Debian GNU/Linux 13 (trixie) aarch64 · container · 2026-09-06 · euid 0 · leg-hash 08531a51b9d2

== action=list_networks
wifi|error|No wireless tools available (nmcli/iw)|0|0|none
[result_status] UNAVAILABLE / PARTIAL / subprocess_runner:spawn_error

== action=connected
connected|unknown|Wi-Fi connection state could not be determined (NetworkManager unreachable; nmcli and iwconfig both failed)|0|none|none
[result_status] UNAVAILABLE / PARTIAL / subprocess_runner:spawn_error

Caveats and known gaps

  1. Windows list_networks can return nothing without saying why. A failed WLAN handle and a host with no wireless interface are reported as wifi|error|... rows, but once an interface exists, a failed WlanGetAvailableNetworkList call is skipped silently, and a successful call that returns zero networks writes no row. The Windows capture shows exactly this: an adapter was present and no networks were listed, with no status and no sentinel.
  2. Windows reports a narrower, cached view. The plugin never asks Windows to scan, so list_networks is whatever the WLAN service already knows, with no BSSID or channel. Its security label comes from the authentication algorithm alone (Open, WEP-Shared, WPA-Enterprise, WPA-PSK, WPA-None, WPA2-Enterprise, WPA2-PSK), so WPA3 and Enhanced Open networks read Unknown and a WEP network using open authentication reads Open.
  3. The Linux D-Bus rung has never met a real access point. The signatures were checked against introspection and a running NetworkManager in a container with no radio, so both Linux legs are declared constrained, and no sample on any OS shows a real access-point row. Promoting a leg needs one real-radio run that returns an AP through D-Bus and one that forces the nmcli fallback.
  4. WEP reads as Open on the Linux D-Bus leg. The plugin reads only the WPA and RSN flag words, never the access point’s privacy bit, so a WEP-only network reports Open the same as a genuinely unsecured one.
  5. On macOS 14 and later list_networks is normally the sentinel. Without Location Services the scan returns the wifi|info row with wifi:macos_scan_requires_location_services, and the 2026-09-07 sample shows it. Neither non-Windows sample used the production identity (the macOS capture ran unprivileged and the Linux capture ran as root with no wireless tools), so the least-privilege iwlist degrade is neither captured nor shown by the code (the plugin only reports what the tool’s exit status says).

Source and tests