yuzu

Agent Plugins

Each plugin runs inside the agent process on managed endpoints and exposes one or more actions that the server can invoke via instructions. This document covers what is common to every plugin — the load/allowlist/signing model, the stub plugins, and the operational recipes. The reference for each plugin lives next to its code in agents/plugins/<name>/README.md: what it does, per-OS capability and mechanism, privileges, the data contract, real captured output on every supported OS, and known gaps.

Plugin index

Each plugin that has adopted the README standard (docs/plugin-readme-standard.md) is listed here with a link to its page beside the code. The table is generated by tools/plugin-doc-gen; do not hand-edit it.

The echo plugin was named tar before 2026-10 (tar.dll, tar.so, tar.dylib); there is no alias, and the agent rejects a leftover tar plugin file. See the Echo section of Upgrading.

71 of 71 plugins document themselves this way; the rest are described in the prose below until their README lands.

PluginPlatformsWhat it doesDocs
agent_actions✅ ✅ ✅Agent runtime actions — set log level, query agent infoREADME
agent_logging✅ ✅ ✅Remote agent log access — retrieve log tail, list key agent filesREADME
antivirus✅ ✅ ✅Antivirus product detection, status, and Defender exclusionsREADME
app_control✅ ⛔ ⛔Read-only WDAC and AppLocker application-control policy posture as configured (Windows-only)README
app_usage✅ 🟡 ✅Read-only machine-scope app usage inventory derived from Echo’s usage fold (no pid, command line, or user names in output)README
asset_tags✅ ✅ ✅Structured asset tag awareness — syncs server-assigned tags locally and detects changesREADME
autoruns✅ ✅ ✅Enumerates persistence sources (what starts automatically) across Windows, Linux and macOSREADME
bitlocker✅ ✅ ✅Disk encryption status — BitLocker, LUKS, FileVaultREADME
browser_inventory🟡 🟡 ✅Chromium-family browser and profile inventoryREADME
browser_policy🟡 🟡 ✅Enterprise-managed Chrome, Chromium and Edge browser policy inventoryREADME
certificates✅ ✅ ✅Certificate inventory and management for system storesREADME
chargen✅ ✅ ✅RFC 864 character generator — streams rotating ASCII linesREADME
content_dist✅ ✅ ✅Content staging — download, verify, execute, and manage staged files (no shell-out)README
device_identity✅ ✅ ✅Reports device hostname, domain membership, and AD organizational unitREADME
diagnostics✅ ✅ ✅Agent diagnostics — log level, certificates, connection infoREADME
discovery✅ ✅ 🟡Network device discovery — ARP scan and ping sweepREADME
disk_actions✅ 🟡 🟡Reports physical drive health and the mapping between drives and the logical volumes they backREADME
disk_space✅ ✅ ✅Reports free / total disk space for a single volumeREADME
echo✅ ✅ ✅Echo activity record — continuous system state change trackingREADME
event_logs✅ ✅ ✅Queries system event logs for errors and filtered eventsREADME
example✅ ✅ ✅Reference example plugin — responds to ‘ping’README
execution_artifacts✅ ⛔ ⛔Windows execution-evidence sources: ShimCache, Amcache InventoryApplicationFile, and Prefetch. Forensics-gated, single-target, default-offREADME
filesystem✅ ✅ ✅Filesystem operations — exists, list_dir, file_hash, search_dir, text ops (admin-only)README
filesystem_posture🟡 ✅ 🟡Reports mounted filesystems, per-mount quota-subsystem state, and snapshot-capable volumesREADME
firewall✅ ✅ ✅Firewall status and rule listingREADME
firmware_posture✅ 🟡 🟡Reports firmware/BIOS vendor, version, release date and update-pending postureREADME
hardware✅ ✅ ✅Reports hardware inventory: manufacturer, model, BIOS, CPU, memory, disks, drivers, chassis, baseboard, swap, machine idREADME
http_client✅ ✅ ✅HTTP client — download files, GET/HEAD requests with hash verification (no shell-out)README
installed_apps✅ ✅ ✅Inventories installed applications and queries by nameREADME
interaction✅ ✅ ✅Desktop user interaction — notifications, message boxes, input dialogs, surveys, DNDREADME
ioc✅ ✅ ✅Indicator of Compromise checking — match IOCs against local endpoint stateREADME
license_scan✅ ✅ 🟡Detects software licences across OS, vendor and per-user surfacesREADME
local_security_policy🟡 🟡 🟡Reports local password, lockout and audit policy posture and sudoers contentREADME
mgmt_posture🟡 ✅ 🟡Management-plane posture: which plane controls the device (facts only)README
msi_packages✅ ✅ ⛔Enumerates installed packages and product codes (Windows MSI / macOS pkgutil)README
netprobe✅ ✅ ✅Active network measurement: ICMP/TCP round-trip time, jitter, loss, and DNS resolution timing to operator-chosen targetsREADME
netstat✅ ✅ ✅Enumerates active network connections and listening socketsREADME
network_actions✅ ✅ ✅Network actions — DNS flush and pingREADME
network_config✅ ✅ ✅Reports network adapter configuration, IP addresses, DNS servers, proxy settings, and the routing tableREADME
network_diag✅ ✅ ✅Network diagnostics — listening ports and established connectionsREADME
os_info✅ ✅ ✅Reports OS name, version, build, architecture, and system uptimeREADME
peripherals✅ ✅ ✅USB, PCI and Thunderbolt/USB4 device inventoryREADME
pkg_inventory🟡 ✅ ✅Machine-scope package-manager inventory (managers, packages and store_packages); per-user package stores are out of scope, deferred to the user-session helperREADME
platform_security✅ ✅ ✅Secure Boot and code-integrity enforcement posture (read-only)README
power_health✅ ✅ 🟡Battery, thermal, and power-plan inventory, plus a gated power-plan switchREADME
printing✅ ✅ ✅Printer/job inventory plus a single narrowly-scoped clear_queue cancellationREADME
privacy_permissions🟡 🟡 🟡Per-app sensitive-permission grants — camera, microphone, location, full-disk-access equivalents (read-only)README
processes✅ ✅ ✅Process listing — enumerate and query running processesREADME
procfetch✅ ✅ ✅Enumerates running processes with SHA-1 hashes of executablesREADME
quarantine✅ ✅ ✅Device network isolation (quarantine) with per-IP whitelistingREADME
rdp_control✅ ⛔ ⛔Remote Desktop control — enable/disable RDP (registry + firewall + TermService)README
registry✅ 🟡 🟡Windows Registry — get, set (six types, verified), delete value, delete key (optionally recursive), enumerate, per-user reads and writes, bulk apply with an undo manifestREADME
remote_access_audit🟡 ✅ 🟡SSH inbound-access audit: sshd_config as written, per-account authorized_keys fingerprints, sshd service state; never key comments, option values or pathsREADME
runtimes🟡 🟡 ✅Installed .NET and JVM runtime inventoryREADME
sccm✅ ⛔ ⛔Reports SCCM/ConfigMgr client status, version, and site assignmentREADME
script_exec✅ ✅ ✅Executes commands and scripts with streaming output (admin-only)README
services✅ ✅ ✅System services — enumerate, query, and configure service startup typesREADME
shares✅ 🟡 ✅Shares this host serves over SMB/NFS: names, paths and a few flags, never ACLsREADME
software_actions✅ ✅ ✅Lists upgradable packages and counts installed softwareREADME
status✅ ✅ ✅Reports agent version, system info, health, modules, connection, switch, and configREADME
storage✅ ✅ ✅Persistent key-value storage — set, get, delete, list, clearREADME
system_hardening✅ ✅ ✅Exploit-mitigation and kernel-hardening posture (read-only)README
tags✅ ✅ ✅Device tagging — set, get, delete, list tags for scope evaluationREADME
update_source_trust🟡 🟡 ✅Package and update-source trust posture (facts only)README
users✅ ✅ ✅Reports logged-on users, sessions, local accounts, admin group members, group membership, primary user, and session historyREADME
vuln_scan✅ ✅ ✅Host vulnerability scanning — CVE matching and configuration compliance checksREADME
wifi✅ 🟡 🟡Scans visible WiFi networks and reports current connection statusREADME
windows_optional_features✅ ⛔ ⛔Windows optional OS feature state (enabled/disabled/pending) via the DISM APIREADME
windows_updates✅ ✅ ✅Updates/packages: installed, available, pending-reboot, patch connectivityREADME
wmi✅ ⛔ ⛔Windows Management Instrumentation — WQL queries and instance enumerationREADME
wol✅ ✅ ✅Sends Wake-on-LAN magic packets and checks host reachabilityREADME

Platform cells are Windows · macOS · Linux (✅ supported · 🟡 constrained or planned · ⛔ unsupported).


Table of Contents

  1. Stub Plugins (Planned)
  2. Plugin Architecture
  3. Plugin Allowlist
  4. Plugin Code Signing

Stub Plugins (Planned)

The following plugins are defined in the roadmap but not yet implemented. They are listed here for planning purposes.

PluginDescriptionTarget Phase
patchPatch deployment agent-side automation (scanning, downloading, installing). Server-side, PatchManager keeps patch and deployment records only: deployment execution was removed (ADR-0062, #3669) and nothing populates patch inventory (#3676), so GET /api/patches is always empty and GET /api/patches/deployments lists only recorded deployments; removal of those routes is tracked by #6326.Phase 7 (7.8)

Plugin Architecture

Overview

All agent plugins share a common architecture based on a stable C ABI defined in sdk/include/yuzu/plugin.h. This ABI is designed to remain binary-compatible across compiler versions and agent upgrades, so plugins can be updated independently of the agent binary. The ABI is currently at version 4 (YUZU_PLUGIN_ABI_VERSION); the agent still loads plugins built against any version back to YUZU_PLUGIN_ABI_VERSION_MIN (1) — a major-version bump does not mean existing plugins need recompiling. See sdk/README.md’s ABI compatibility table for the full version-by-version breakdown.

A C++23 CRTP wrapper (sdk/include/yuzu/plugin.hpp) provides ergonomic C++ authoring on top of the C ABI. The YUZU_PLUGIN_EXPORT macro handles symbol visibility and the export table automatically.

Plugin Lifecycle

  1. The agent scans its plugin directory at startup. If an allowlist is configured, each library’s SHA-256 hash is verified before loading (see Plugin Allowlist below). If a code-signing trust bundle is configured, the library’s CMS detached signature is verified against it as well (see Plugin Code Signing below) — both checks run before dlopen/LoadLibrary so tampered or untrusted binaries never execute code.
  2. Each shared library (.dll on Windows, .so on Linux, .dylib on macOS) is loaded dynamically. Plugin names must be unique within the directory: once a library is loaded and its declared name read, a second file declaring a name an earlier file in the same scan already claimed is unloaded again before its init runs and rejected with a duplicate plugin name reason; the first file the directory walk encountered keeps the name.
  3. The agent calls the plugin’s init function, passing a context with configuration and callbacks.
  4. When the server sends an instruction targeting a plugin action, the agent dispatches it to the correct plugin.
  5. The plugin executes the action and returns results via the output callback.

Plugin Allowlist

The agent can verify plugin shared libraries against a SHA-256 allowlist before loading them. This prevents loading of tampered or unauthorized plugins.

Configuration:

yuzu-agent --plugin-allowlist /etc/yuzu/plugin-allowlist.txt
# or via environment variable:
YUZU_PLUGIN_ALLOWLIST=/etc/yuzu/plugin-allowlist.txt yuzu-agent

Allowlist file format (compatible with sha256sum output):

a1b2c3d4e5f6...  libhardware.so
f6e5d4c3b2a1...  libnetwork_info.so
9876543210ab...  libos_info.so

Generate the allowlist from your verified plugin builds:

sha256sum /opt/yuzu/plugins/*.so > /etc/yuzu/plugin-allowlist.txt

Behavior:

Plugin Code Signing

The agent can verify a CMS detached signature for each plugin shared library and reject any plugin whose signature does not chain to an operator-configured trust anchor. This is the provenance layer that complements the allowlist’s integrity layer: the allowlist binds this filename to this hash on this host, and the signature binds this file content to a CA-issued signing identity. With both enabled, an attacker who drops a malicious .so plus a fresh .sig is rejected because their cert does not chain to the trusted CA.

Trust anchor. A standard PEM file containing one or more X.509 root or intermediate certificates the operator chooses to trust. The bundle is deployment-format-agnostic — it can hold a public CA root, an internal-CA root, or (when shipped) the Yuzu self-managed CA root. The verifier does not care which authority issued the signing cert as long as the chain validates against the bundle.

Signature file convention. For each plugin file <plugin>.so (or .dll / .dylib), the agent looks for a sibling <plugin>.so.sig containing a PEM-armoured CMS detached signature over the plugin file bytes. Inspect a signature with:

openssl cms -inform pem -text -noout -in chargen.so.sig

Configuration. There are two paths — server-managed (recommended for fleets) and agent-local (for one-off hosts or air-gapped environments).

Server-managed (Settings → Plugin Code Signing). An admin operator goes to Settings > Plugins and updates (or the Settings page) and uploads the PEM trust bundle through the Plugin Code Signing card; replacing a loaded bundle asks for confirmation. The recorded “Require signed plugins” flag is served with the bundle; each agent enforces its own --plugin-require-signature. The server validates the PEM (counts certs, computes SHA-256, surfaces parse errors immediately), persists it atomically (temp file + rename) under the server’s cert directory, and exposes it at GET /api/v2/agent/plugin-policy (PluginSigning:Read, Administrator-only and unreachable by any MCP token at any tier — see docs/user-manual/rest-api.md “Settings”; returns JSON containing the bundle PEM + the require flag, nested under a data envelope). The deprecated GET /api/v1/agent/plugin-policy (flat body, require_admin gate) still works during its announced removal window — see server-admin.md’s vNEXT note. The card also has a “Require signed plugins” checkbox that flips the require flag and a “Remove trust bundle” button that disables signing for new agent starts. Every change emits an audit_event with action plugin_signing.bundle.uploaded / plugin_signing.bundle.cleared / plugin_signing.require.changed.

In this mode, agents are configured by curling the policy endpoint (with an admin token) into a local file at startup:

curl -fsSL -H "Authorization: Bearer $YUZU_ADMIN_TOKEN" \
  https://server.example.com:8443/api/v2/agent/plugin-policy \
  | jq -r .data.trust_bundle_pem > /etc/yuzu/plugin-trust-bundle.pem

yuzu-agent --plugin-trust-bundle /etc/yuzu/plugin-trust-bundle.pem \
           --plugin-require-signature

Important — fail-closed when require is set. If you pass --plugin-require-signature without --plugin-trust-bundle (or with an empty path), the agent refuses to start rather than silently fail-open. This is intentional: the prior behaviour would have skipped the entire signing block and quietly loaded unsigned plugins while the operator believed enforcement was active.

Important — Yuzu-shipped plugins are not signed yet. This release ships the verifier; it does not yet sign the in-tree agents/plugins/. If you turn on Require with a trust bundle anchored only at your own CA, every Yuzu-bundled plugin will be rejected at next agent restart. Use the transitional mode (bundle uploaded, Require off) until you have signed every plugin your fleet uses, including the in-tree ones. The Settings card displays this warning inline.

Windows admins. The curl/jq recipe above assumes a Unix host; equivalent PowerShell using Invoke-RestMethod + ConvertFrom-Json works against the same endpoint. Place the resulting PEM at C:\ProgramData\Yuzu\certs\plugin-trust-bundle.pem and pass the path via --plugin-trust-bundle.

(Fully automatic agent-side fetch is a planned follow-up; today’s contract is “the bundle is hosted on the server, the agent is told where the local copy lives via CLI flags”.)

Agent-local CLI flags:

yuzu-agent \
  --plugin-trust-bundle /etc/yuzu/plugin-trust-bundle.pem \
  --plugin-require-signature
FlagEffect
--plugin-trust-bundle <path>Enables signature verification. PEM file with one or more CA certs. Must be a regular file of at most 1 MiB, and on Windows not held open by another process that denies shared reading; otherwise every signed plugin is refused as untrusted (since 0.14.1, #5249). Env: YUZU_PLUGIN_TRUST_BUNDLE.
--plugin-require-signatureWhen set, plugins without a .sig sibling are rejected. When unset (default), unsigned plugins are allowed (transitional mode for ops rolling out signing). Env: YUZU_PLUGIN_REQUIRE_SIGNATURE.
--verify-plugin-signature <path>Packaging-time check, not a runtime setting. Verifies one plugin’s CMS signature sidecar against --plugin-trust-bundle using the agent’s own verifier, then exits without starting the agent: exit 0 if it verifies, non-zero with Plugin signature rejected: <reason> otherwise. Requires --plugin-trust-bundle (exits non-zero if it is missing). Hidden from --help; used by the macOS package build scripts. No environment variable.

Signing a plugin (operator workflow, internal CA — recommended):

The server’s internal CA now issues code-signing leaves directly (gap-matrix #10): the operator still generates their own key and CSR locally — the server never sees the private key (CSR-custody model) — and submits only the CSR to POST /api/v1/ca/issue-code-signing (Security:Write). The server signs it with usage hard-pinned to codeSigning only (never clientAuth/serverAuth), so there is no -extfile/EKU step to get right by hand — the constraint the old self-signed recipe enforced manually is now enforced server-side unconditionally.

# One-time: generate your own signing keypair + CSR. The server never
# sees signer.key.
openssl ecparam -genkey -name prime256v1 -out signer.key
openssl req -new -key signer.key -out signer.csr \
  -subj "/CN=Plugin Signer"

# Submit the CSR; `label` becomes the leaf's CN and must match
# ^[A-Za-z0-9._-]{1,64}$. Returns the signed leaf plus the issuer chain.
curl -s -X POST -H "X-Yuzu-Token: $TOKEN" -H 'Content-Type: application/json' \
  -d "{\"csr_pem\": $(jq -Rs . < signer.csr), \"label\": \"plugin-signer-2026\"}" \
  https://localhost:8443/api/v1/ca/issue-code-signing > signer_issuance.json
jq -r .certificate_pem < signer_issuance.json > signer.pem
jq -r .chain_pem       < signer_issuance.json > signer_chain.pem

# Per-plugin: sign with the leaf, include the issuer chain via -certfile.
openssl cms -sign -binary \
  -signer signer.pem -inkey signer.key -certfile signer_chain.pem \
  -in chargen.so -outform pem -out chargen.so.sig

The agent trust bundle is the already-public CA root — GET /api/v1/ca/root — no separate distribution step for a self-signed CA is needed. Fetch it once (as in the server-managed trust-bundle recipe above) and ship the .sig files alongside each .so.

Bring-your-own-CA (alternative, for operators not using the internal CA): the verifier’s code path is CA-agnostic and accepts any chain that validates against the configured trust bundle, so an existing external PKI (or a throwaway self-signed CA) still works — generate ca.key/ca.pem, issue a signing leaf with extendedKeyUsage=codeSigning (REQUIRED — the agent’s verifier enforces X509_PURPOSE_CODE_SIGN, rejecting a leaf without this EKU even if it chains to the trusted CA), sign with openssl cms -sign as above, and distribute your own ca.pem as the trust bundle instead of the internal CA root.

Behavior:

Self-managed CA — shipped for code signing. The internal CA’s POST /api/v1/ca/issue-code-signing (above) already lets operators sign plugins against the same server-managed CA used for mTLS — the CA root is published at the already-public GET /api/v1/ca/root. The verifier itself did not change to support this: the same code path accepts public-CA, internal-CA, and internal-CA-issued codeSigning certs. A separate server-side key-minting CLI (the server generating the signer’s private key on the operator’s behalf) was deliberately NOT built — CSR-custody keeps the private key off the server end to end.

Not yet supported (follow-up work):

Output Format

Plugins emit results by calling write_output() with pipe-delimited (|) fields. Each call writes one row of output. The server parses these rows according to the result schema declared in the plugin’s InstructionDefinition YAML.

Example:

write_output("hostname|Windows 11 Pro|10.0.26200|x64");

Configuration Access

Plugins can read configuration values set by the server using get_config(key). This allows the server to pass parameters (filter strings, thresholds, target paths) to plugin actions at execution time.

Data Storage

Each plugin can persist state locally. Per-plugin data is stored at:

<agent_data_dir>/<plugin_name>.json

For example, the tags plugin stores its tag set in tags.json within the agent’s data directory. This file survives agent restarts and upgrades.

Writing a New Plugin

Use the example plugin as a starting template. The minimum steps are:

  1. Create a new directory under agents/plugins/<your_plugin>/.
  2. Implement the C ABI entry points (or use the C++ CRTP wrapper).
  3. Add the plugin to meson.build.
  4. Write an InstructionDefinition YAML file in content/definitions/ following the yuzu.io/v1alpha1 DSL spec.
  5. Register plugin actions in the Substrate Primitive Reference table (docs/yaml-dsl-spec.md, section 14).
  6. Document the plugin (docs/plugin-readme-standard.md): write agents/plugins/<your_plugin>/README.md from docs/templates/plugin-README.md; add the optional description, values, example and platforms keys to the definition’s result.columns; on every OS where an action is supported or constrained, capture a sample with the built tools/plugin-capture on that OS (plugin-capture <plugin library> --os <os> --host-class <bare-metal|vm|container> --action <name> … --out agents/plugins/<your_plugin>/docs/samples/<os>.txt; the standard’s “Capturing on each OS” section has the per-host recipes, including the Linux container — a three-OS plugin needs all three before the PR is raised; a leg no capture host can honestly run is recorded [not captured] <class>: <reason>), then python3 tools/plugin-doc-gen/plugin_doc_gen.py --stamp <your_plugin> <os> to write the leg-hash; finally python3 tools/plugin-doc-gen/plugin_doc_gen.py --all (or meson compile -C <builddir> docs-regen) so the generated fences, this index, the site navigation and the plugin’s content/plugin-docs manifest are current, and --check to confirm — it runs every generator-side check; the docs suite adds the ratchet and the manifest-key binding. Lower RATCHET_BASELINE_MISSING in tests/test_plugin_readmes.py by one in the same PR (the gate fails until you do, and tells you the number). The docs test suite and the docs-lint touch rule enforce this.

For the full DSL specification, see docs/yaml-dsl-spec.md. For a walkthrough, see docs/getting-started.md.