yuzu

rdp_control

What it doesRemote Desktop control — enable/disable RDP (registry + firewall + TermService)
Version0.1.0
KindAction · mutating · on-demand
PlatformsWindows ✅ · macOS ⛔ unsupported · Linux ⛔ unsupported
Actionsset_state (definition windows.rdp.set_state) · status (definition windows.rdp.status)
Securityset_state: securable Security · operation Write · risk High · dispatch Mutating · approval gate AdminOrApproval; status: securable Security · operation Read · risk Low · dispatch ReadOnly · approval gate None
Rolesexecute: set_state: endpoint-admin; status: endpoint-admin, endpoint-operator · author: content-author

How it works

set_state runs three sequential steps under a per-process mutex (registry fDenyTSConnections, the built-in Remote Desktop firewall rule group via INetFwPolicy2, then TermService) and reports each step’s outcome independently; overall|ok only when every attempted step succeeded, and there is no rollback on partial failure. Disable deliberately leaves TermService running — only the registry and firewall gates are closed. status independently re-reads all three gates and derives a rdp verdict that is unknown (never a false off) whenever any gate could not be read. On macOS and Linux neither action touches the OS: both return the honest rdp_control|unsupported|... sentinel; set_state still reports rc=1 there (a state-changing action that touched nothing must not read as success), while status keeps rc=0 (an unsupported read is not itself a failure). The plugin is deliberately not a rollback/undo mechanism, and it never stops a running TermService — that is the Guardian service-guard’s job.

flowchart LR
  OP["Operator / ITSM change window"] --> SRV["Server - authz: Security.Write set_state, Security.Read status"]
  SRV -->|"gRPC mTLS"| HOST["Agent plugin host"] --> EX["rdp_control.execute"]
  EX --> WIN["Windows leg - registry + INetFwPolicy2 COM + SCM"]
  EX --> MAC["macOS leg - honest UNSUPPORTED sentinel, no mechanism"]
  EX --> LIN["Linux leg - honest UNSUPPORTED sentinel, no mechanism"]
  WIN & MAC & LIN --> ROWS["key|value rows + result status"] --> RS[("ResponseStore")] --> API["REST /api/responses"]

OS capability

ActionWindowsmacOSLinux
set_state✅ supported · rung 1 · Win32 registry + INetFwPolicy2 COM + SCM⛔ unsupported⛔ unsupported
status✅ supported · rung 1 · Win32 registry + INetFwPolicy2 COM + SCM⛔ unsupported⛔ unsupported

Privileges and prerequisites

OSRuns asExtra grant neededMeasuredIf the read is refused
Windowsagent service account with an elevated token (LocalSystem today, #1442)Administrators group membership (LocalSystem already has it). The intended least-privilege NT SERVICE\YuzuAgent account is not granted this by scripts/install-agent-user.ps1; must be added out of band (GPO Restricted Groups / Intune / Add-LocalGroupMember).2026-09-07, bare metal, as SYSTEMset_state: per-step error:<code> rows and overall|error; status: per-gate error:<code> rows and rdp|unknown
macOSn/a — leg not implemented; the honest-sentinel path needs no privilegen/a2026-09-07, bare metal, at euid 501 (jsmith)always rdp_control|unsupported|..., rc=1 (set_state) / rc=0 (status)
Linuxn/a — leg not implemented; the honest-sentinel path needs no privilegen/a2026-09-06, container, at euid 0always rdp_control|unsupported|..., rc=1 (set_state) / rc=0 (status)

None. The Windows leg calls Win32 registry, COM, and Service Control Manager APIs in-process (RegSetValueExW/RegGetValueW, INetFwPolicy2 COM, StartServiceW/QueryServiceStatusEx) — no subprocess, no network.

Data contract

Inputs

DefinitionParameterTypeRequiredDefaultConstraintsDescription
windows.rdp.set_statestatestringyes-enum: enable, disableenable allows Remote Desktop connections; disable blocks them (e.g. state: enable).

Outputs

Each action’s result is a set of independent key|value lines — one per gate or step, in emission order — not a single delimited row; the bold lines below list the field order. There is no - placeholder convention here: a step or gate that could not be read reports its own error:<code> (or error:com_init / error:group_not_found for the firewall step) instead.

windows.rdp.set_state — reg_status|firewall_status|service_status|overall

FieldTypeValuesAvailableExampleDescription
reg_statusstring-Windowsnot observed in any captureOutcome of writing HKLM fDenyTSConnections; ok or a Win32 error code. Values: ok, error: (e.g. error:5 = ERROR_ACCESS_DENIED).
firewall_statusstring-Windowsnot observed in any captureOutcome of toggling the Remote Desktop firewall rule group via INetFwPolicy2. Values: ok, error:com_init, error:group_not_found, error:0x.
service_statusstring-Windowsnot observed in any captureTermService outcome — running once start is confirmed by polling (enable), untouched (disable), or a Win32 error. Values: running, untouched, error:.
overallstring-Windowsnot observed in any captureTerminal verdict for the whole set_state call; ok only when every step above succeeded. Values: ok, error.

windows.rdp.status — deny_ts_connections|firewall_group|term_service|rdp

FieldTypeValuesAvailableExampleDescription
deny_ts_connectionsstring-Windows0Raw fDenyTSConnections registry value (0 = RDP allowed, 1 = RDP denied), or a Win32 error if the read failed. Values: 0, 1, error:.
firewall_groupstring-WindowsenabledWhether the Remote Desktop firewall rule group is enabled, or why it could not be read. Values: enabled, disabled, group_not_found, error:com_init, error:0x.
term_servicestring-Windowsnot observed in any captureCurrent SCM state name for TermService, or a Win32 error if the query failed. Values: running, start_pending, stopped, stop_pending, paused, pause_pending, continue_pending, unknown, error:.
rdpstring-WindowsonDerived RDP posture — on only when all three gates are readable and open, unknown when any gate could not be read, off otherwise. Values: on, off, unknown.

Result status

This plugin does not set a typed result status; the agent records UNDECLARED and the sample shows UNDECLARED / UNKNOWN /.

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 e17062cd8474

== action=set_state
error|invalid state (use enable or disable)
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=status
deny_ts_connections|0
firewall_group|enabled
term_service|running
rdp|on
[result_status] UNDECLARED / UNKNOWN

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

== action=set_state
rdp_control|unsupported|Windows Remote Desktop has no macOS equivalent; use Screen Sharing / com.apple.screensharing
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=status
rdp_control|unsupported|Windows Remote Desktop has no macOS equivalent; use Screen Sharing / com.apple.screensharing
[result_status] UNDECLARED / UNKNOWN

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

== action=set_state
rdp_control|unsupported|Windows Remote Desktop is not available on this platform
[result_status] UNDECLARED / UNKNOWN
[rc] 1

== action=status
rdp_control|unsupported|Windows Remote Desktop is not available on this platform
[result_status] UNDECLARED / UNKNOWN

Caveats and known gaps

  1. No rollback on partial failure. set_state’s three steps each report their own status; overall|ok only when all succeeded, and a partial failure leaves whichever gates were flipped in place with no automatic revert.
  2. Disable does not stop TermService. Only the registry and firewall gates close on disable; the service is deliberately left running, which is why the capability catalogue classifies set_state as Irreversible rather than claiming a complete undo.
  3. S_FALSE from the firewall API is not success. INetFwPolicy2::EnableRuleGroup/IsRuleGroupEnabled return S_FALSE (which passes SUCCEEDED()) when the built-in Remote Desktop rule group is absent (e.g. a hardened image); the plugin explicitly classifies that as group_not_found/an unreadable gate, never a confirmed enable, disable, or “off”.
  4. status never reports “off” from an unreadable gate. If any of the three gates cannot be read, the derived verdict is unknown, distinguishing “confirmed closed” from “couldn’t tell” — the fail-safe direction for a remote-access posture check.
  5. No typed result status. The plugin never calls a result-status setter; every sample on every OS shows UNDECLARED / UNKNOWN, so a caller must key off overall/rdp and the process exit code, not a typed status field.

Source and tests