yuzu

license_scan

What it doesDetects software licences across OS, vendor and per-user surfaces
Version1.0.0
KindCollector · read-only · on-demand
PlatformsWindows ✅ · macOS ✅ · Linux 🟡 constrained
Actionslist · surfaces
Securitysecurable SoftwareLicensing · operation Read · risk Low · dispatch ReadOnly · approval gate None
Rolesexecute: · author: -

How it works

list and surfaces run the same sweep of detection surfaces for the host’s OS; they differ only in what they print. list prints every detected lic| licence record first and then one probe_status|<surface>|ok|<rows> (or |error|<reason>) line per surface it tried. surfaces prints only the probe_status| lines, so it answers “which surfaces could be read, and why not” without returning the licence inventory. Both always return rc 0: success means the sweep finished, not that every surface answered, so the probe_status| lines are the real health signal.

Surfaces run in a fixed order, listed below. The order is not strictly by authority: on Linux the package-metadata surface runs before the authoritative certificate and FlexLM surfaces.

The plugin reads and reports; it changes nothing. It is deliberately not an entitlement tracker: FlexLM seat counts and KMS activation counts are not emitted. It is also not a licence validator: a macOS App Store receipt is detected but not verified, a binary (bplist00) Info.plist is read by a bounded, plugin-local decoder that returns only the display name, name and short version strings. A plist that is missing, unreadable or only partly decodable keeps the fields it did decode; the name falls back to the bundle name, and any version it could not read stays empty. A full licence key is never emitted, only the partial key Windows itself exposes or a short hash prefix.

flowchart TD
  OP["Operator, or the 24 h daily sync"] --> LS["license_scan list or surfaces"]
  LS --> WIN["Windows: slp_wmi, office_c2r, vendor rows, per-user hives and files"]
  LS --> LIN["Linux: pkg_metadata, entitlement_certs, flexlm_lic"]
  LS --> MAC["macOS: mas_receipt, vendor_plists"]
  WIN --> OUT["lic records, then one probe_status line per surface"]
  LIN --> OUT
  MAC --> OUT
  OUT --> ST{"Any probe_status error?"}
  ST -- "yes" --> C["CONSTRAINED, or PERMISSION_DENIED when entitlement_certs is refused"]
  ST -- "no" --> U["Result status UNDECLARED"]
  OUT --> SYNC["Daily sync: software_licensing source"]

What you can decide with it

QuestionWindowsmacOSLinux
Is the operating system or Office activated, and how (KMS, MAK, OEM, retail)?Yes, from slp_wmi: authoritative status, channel, evaluation end, grace expiry and the last five characters of the product key.No.No.
Is a subscription about to lapse?Office subscription type only (from the Click-to-Run SKU, status unknown); the OS reports an evaluation end or grace expiry where it has one.No.Yes for Red Hat subscriptions (entitlement_certs, certificate expiry) and for FlexLM features that carry an expiry date.
Is a particular vendor product installed, and what licence class is it likely to be?Presence of the listed vendor products with a type and confidence. A few interpreters go further: VMware Workstation reports licensed when a Serial registry value exists, and WinRAR reports licensed when a rarreg.key file sits beside the executable and trial when none is found (an absence, not an evaluation marker). These are at probable or heuristic confidence only.Office volume licence plist and Parallels presence; App Store apps with a receipt.Package-declared licence class (open_source, freeware or unknown): every installed package on rpm hosts, and on dpkg hosts only packages with a machine-readable (DEP-5) copyright header.
Is a user holding a personal licence on a shared machine?Visual Studio and JetBrains keys and the Adobe sign-in artefact, tagged user_scope=user.No.No.
Which action?list for the inventory; surfaces to see which sources answered.Same.Same.

What a result does and does not prove:

Diagrams

Which surfaces can block the daily sync

flowchart LR
  P["Plugin list output"] --> G{"probe_status error on an authoritative or primary surface?"}
  G -- "slp_wmi, pkg_metadata, mas_receipt, entitlement_certs or flexlm_lic" --> SK["Skip this cycle, keep last good state"]
  G -- "per_user_hives, per_user_files or any other surface" --> OK["Cycle proceeds, rows kept"]
  G -- "no error" --> OK
  OK --> BL["Skipped at 10,000 or more records or over 1 MiB, otherwise ingested"]

The daily sync reads the probe_status| text, not the typed result status. An error on one of five surfaces makes it skip the cycle rather than replace stored licences with an incomplete picture. A degraded per-user surface, or an error on a vendor presence row, never blocks the sync, and the typed result status of the live command is CONSTRAINED in both cases.

Confidence by surface

SurfaceOSConfidenceWhat it can tell you
slp_wmiWindowsauthoritativeOS, Office and Microsoft server licence status and channel
entitlement_certsLinuxauthoritativeRed Hat subscription expiry
flexlm_licLinuxauthoritativeFlexLM feature expiry
office_c2r, vendor registry rowsWindowsprobable or heuristicProduct present, likely licence type
per_user_hives, per_user_filesWindowsheuristic; probable for a JetBrains key fileA per-user key or artefact is present
pkg_metadataLinuxprobable (rpm) or heuristic (dpkg)Declared licence class of an installed package
mas_receipt, vendor_plistsmacOSprobable or heuristicApp Store receipt present, vendor plist present

OS capability

ActionWindowsmacOSLinux
list✅ supported · rung 1 · wmi+win32_registry✅ supported · rung 1 · filesystem_probe(glob+plist, XML and bplist00)🟡 constrained · rung 2 · rpm/dpkg-query via bounded argv runner; in-process libcrypto X.509
surfaces✅ supported · rung 1 · wmi+win32_registry✅ supported · rung 1 · filesystem_probe(glob+plist, XML and bplist00)🟡 constrained · rung 2 · rpm/dpkg-query via bounded argv runner; in-process libcrypto X.509

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. Machine-scope surfaces need only the account’s existing registry-read access. The per-user offline-hive fallback uses SeBackupPrivilege and SeRestorePrivilege, which the agent account already holds (docs/agent-privilege-model.md).Bare-metal Windows (NT 10.0.26200) as SYSTEM, 2026-09-07. That capture shows machine-scope records only (user_scope is machine on every row) and predates the default-on hive-file guard for the offline arm.slp_wmi errors (a wmi_* token or row_cap_exceeded) block the daily sync. per_user_hives degrades to privilege_missing, hive_file_refused, hive_unload_failed or profile_list_truncated without blocking it; hive_file_refused is the aggregate reason when the offline hive-file guard refuses any profile’s hive, and the per-profile SID and the guard’s token go to the agent log at warn, never into a row. An unreadable profile list reports profile_list_unreadable on both per-user surfaces.
macOSAgent LaunchDaemon, which runs as root today (no UserName key, docs/agent-privilege-model.md).None. mas_receipt and vendor_plists are ordinary filesystem globs and plist reads.Bare-metal macOS 26.6.2 as an ordinary user (euid 501), 2026-09-07. The binary-plist decoder was measured as euid 501 on 2026-10-26 and again as root on 2026-10-27 (the production LaunchDaemon identity), identical to plutil both ways, so the plugin is shown to need none of the daemon’s root grant.There is no coded refusal path: an unreadable file or an absent glob match reports a structural zero-row ok, never an error, and an Info.plist that is over 4 MiB or unreadable gives the bundle name with an empty version, while one that decodes only partly keeps the fields it did decode.
LinuxAgent daemon, an unprivileged account by design (docs/agent-privilege-model.md).None for pkg_metadata and FlexLM .lic files (the package databases and the conventional .lic directories are world-readable). The RHEL entitlement certificates under /etc/pki/entitlement are not assumed readable: subscription-manager creates them root-owned, and the unprivileged account’s access is unmeasured on a real RHEL host; the -key.pem files are skipped.Debian 13 container, euid 0, 2026-09-06. Not representative of the unprivileged account, and the entitlement directory was absent there, so the RHEL grant is unmeasured.entitlement_certs reports cert_permission_denied (result status PERMISSION_DENIED) when the directory listing is refused or a certificate cannot be read, never ok|0; cert_parse_failed (no certificate parsed, or the directory cannot be listed for a reason other than a refusal or its absence; an absent directory is ok|0) or cert_parse_partial (some parsed, some did not), including for an unparseable or oversized certificate; and cert_scan_budget_exhausted for a truncated listing, more than 64 certificate files, more than 16 certificates in one file, or the 10 s start gate. pkg_metadata reports dpkg_query_failed or output_truncated.

Subprocesses: Linux only, rpm and dpkg-query through the bounded argv runner (no shell, 20 s per call) for pkg_metadata. The entitlement certificates are read and parsed in-process via libcrypto: no openssl binary and no subprocess, under a 10 s start gate checked before each certificate read (a gate, not a deadline; see Caveats). Windows and macOS spawn nothing. No network use on any OS.

Data contract

Inputs

Neither action takes parameters.

Outputs

No definition YAML ships for this plugin, so these tables are derived from the plugin source and the sample captures rather than spec.result.columns. list emits two row shapes on one stream: lic| licence records followed by probe_status| diagnostics (one per surface attempted); surfaces emits only the diagnostics. A lic row has 14 pipe-separated fields, in this order: lic, product, vendor, version, license_type, channel, status, expires_at, source, confidence, key_hint, exe_hints, user_scope, user_ref. An empty field is empty, never a placeholder. Closed vocabularies: license_type is perpetual, subscription, trial, volume, oem, retail, open_source, freeware or unknown; channel is kms, mak, oem, retail or empty; status is licensed, subscription_active, trial, grace, expired, unlicensed or unknown; source is os_licensing_api, entitlement_cert, registry_probe, license_file, package_metadata, app_receipt or heuristic; confidence is authoritative, probable or heuristic; user_scope is machine or user. expires_at is a UTC epoch in seconds truncated to midnight, or empty for none or unknown, and a grace countdown is converted to an absolute date so the row does not change from day to day. key_hint is the OS-provided partial key or a 12-hex SHA-256 prefix, never a full key. exe_hints is a comma-separated list of executable names. user_ref is a local profile name or empty, never a SID. A probe_status row is probe_status|<surface>|ok|<rows> or probe_status|<surface>|error|<reason>. Every field has |, CR, LF and NUL removed and is cut at 1024 bytes.

No definition declares result columns.

Result status

StatusCompletenessProvenanceWhen
UNDECLARED (never explicitly set)UNKNOWN-every surface reported ok, as in all three sample captures
PERMISSION_DENIEDPARTIALlicense_scan:permission_deniedthe entitlement_certs outcome is cert_permission_denied (the agent may not list /etc/pki/entitlement or read a certificate in it). It takes precedence over CONSTRAINED, is set identically after both list and surfaces, and keeps the rows already collected.
CONSTRAINEDPARTIALlicense_scan:surface_errorat least one probe_status line is an error and none is a permission refusal, for either action. The return code stays 0. Any surface counts here, including the per-user ones, so this status is wider than the set of errors that block the daily sync.

The reason tokens that appear in a probe_status|<surface>|error|<reason> line are the useful detail:

Where the data goes

Sample output

Windows — captured: windows Windows 10.0.26200 x86_64 · bare-metal · 2026-10-26 · LocalSystem (elevated) · leg-hash 0c437dc4008f

== action=list
lic|Windows(R), Professional edition|Microsoft||oem|oem|licensed||os_licensing_api|authoritative|2R4J2||machine|
lic|Microsoft Visual Studio|Microsoft||unknown||unknown||registry_probe|heuristic||devenv.exe|machine|
lic|7-Zip|Igor Pavlov||open_source||unknown||registry_probe|heuristic||7zfm.exe|machine|
lic|Git for Windows|The Git Development Community||open_source||unknown||registry_probe|heuristic||git.exe|machine|
probe_status|slp_wmi|ok|1
probe_status|office_c2r|ok|0
probe_status|sql_server|ok|0
probe_status|exchange|ok|0
probe_status|visual_studio|ok|1
probe_status|autodesk_adsklicensing|ok|0
probe_status|veeam|ok|0
probe_status|acronis|ok|0
… 12 of 18 rows shown
[result_status] UNDECLARED / UNKNOWN

== action=surfaces
probe_status|slp_wmi|ok|1
probe_status|office_c2r|ok|0
probe_status|sql_server|ok|0
probe_status|exchange|ok|0
probe_status|visual_studio|ok|1
probe_status|autodesk_adsklicensing|ok|0
probe_status|veeam|ok|0
probe_status|acronis|ok|0
probe_status|av_suites|ok|0
probe_status|vmware_workstation|ok|0
probe_status|winrar|ok|0
probe_status|open_source_classification|ok|2
… 12 of 14 rows shown
[result_status] UNDECLARED / UNKNOWN

macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-10-26 · euid 501 · leg-hash 0c437dc4008f

== action=list
lic|Pages Creator Studio||15.1.1|retail||licensed||app_receipt|probable|||machine|
lic|OneDrive||26.168.0830|retail||licensed||app_receipt|probable|||machine|
lic|iMovie||10.4.4|retail||licensed||app_receipt|probable|||machine|
lic|Microsoft Word||16.114.1|retail||licensed||app_receipt|probable|||machine|
lic|Numbers Creator Studio||15.1|retail||licensed||app_receipt|probable|||machine|
lic|Microsoft Excel||16.114.1|retail||licensed||app_receipt|probable|||machine|
lic|Microsoft Outlook||16.113.2|retail||licensed||app_receipt|probable|||machine|
lic|Keynote Creator Studio||15.1.1|retail||licensed||app_receipt|probable|||machine|
lic|uBlock Origin Lite||2026.1018.2229|retail||licensed||app_receipt|probable|||machine|
lic|GarageBand||10.4.13|retail||licensed||app_receipt|probable|||machine|
lic|Microsoft PowerPoint||16.114.1|retail||licensed||app_receipt|probable|||machine|
lic|Hush||1.0.19|retail||licensed||app_receipt|probable|||machine|
… 12 of 14 rows shown
[result_status] UNDECLARED / UNKNOWN

== action=surfaces
probe_status|mas_receipt|ok|12
probe_status|vendor_plists|ok|0
[result_status] UNDECLARED / UNKNOWN

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

== action=list
lic|apt||3.0.3|open_source||unknown||package_metadata|heuristic|||machine|
lic|autoconf||2.72-3.1|open_source||unknown||package_metadata|heuristic|||machine|
lic|automake||1:1.17-4|open_source||unknown||package_metadata|heuristic|||machine|
lic|base-files||13.8+deb13u6|open_source||unknown||package_metadata|heuristic|||machine|
lic|base-passwd||3.6.7|open_source||unknown||package_metadata|heuristic|||machine|
lic|bash||5.2.37-2+b9|open_source||unknown||package_metadata|heuristic|||machine|
lic|bison||2:3.8.2+dfsg-1+b2|open_source||unknown||package_metadata|heuristic|||machine|
lic|bsdutils||1:2.41.5-0+deb13u1|open_source||unknown||package_metadata|heuristic|||machine|
lic|ca-certificates||20250419|open_source||unknown||package_metadata|heuristic|||machine|
lic|cmake||3.31.6-2|open_source||unknown||package_metadata|heuristic|||machine|
lic|cmake-data||3.31.6-2|open_source||unknown||package_metadata|heuristic|||machine|
lic|coreutils||9.7-3|open_source||unknown||package_metadata|heuristic|||machine|
… 12 of 149 rows shown
[result_status] UNDECLARED / UNKNOWN

== action=surfaces
probe_status|pkg_metadata|ok|146
probe_status|entitlement_certs|ok|0
probe_status|flexlm_lic|ok|0
[result_status] UNDECLARED / UNKNOWN

Caveats and known gaps

  1. A refused entitlement directory pauses the daily sync, and Linux is constrained by design. On Linux pkg_metadata is a declared-licence classification only (no lapse detection) and the agent account’s access to /etc/pki/entitlement on a real RHEL host is unmeasured. A refused directory or certificate reports cert_permission_denied, and any listing failure other than a refusal or an absent directory (which is ok|0) reports cert_parse_failed, so the daily sync skips the cycle and keeps the last good state in both cases, never an ok|0 that would replace the stored entitlement records with nothing.
  2. Per-user Windows probes are never a primary surface. A missing SeBackup or SeRestore privilege, a hive file refused by the default offline guard (aggregate reason hive_file_refused, details in the agent log), an unreadable profile list or a hive-unload failure degrades per_user_hives honestly, turns the live command status to CONSTRAINED, and never blocks list or the daily sync. Per-user licence state is additive, not authoritative.
  3. macOS yields bundle identity, not licence state, and seat counts are out of scope. The App Store receipt is not validated, vendor_plists rows are presence-only, and an Info.plist that is over 4 MiB or unreadable gives the bundle name with an empty version, while one that decodes only partly (XML or binary) keeps the fields it did decode. FlexLM seat counts and KMS activation counts (ent| records) are not emitted, so the plugin cannot say how many seats are used or whether a volume agreement is compliant.
  4. A failing rpm falls through to dpkg-query. A leftover rpm binary with an uninitialised database fails every query on a Debian-family host, so an rpm execution failure (not only its absence) tries dpkg-query next. A truncated rpm listing is the exception: it is reported as output_truncated, never merged with a second tool’s partial result.
  5. The entitlement scan’s 10 s budget is a start gate, not a deadline. It is checked before each certificate read; the directory listing comes before it and a read has no deadline of its own, so a certificate reached through a followed link onto a dead network mount can hold the worker for that mount’s timeout. There is no mount-table guard because /etc/pki/entitlement is a fixed root-owned path on the root filesystem by subscription-manager’s contract (the same shape as runtimes, #4875).

Source and tests