Skip to content

feat: drive redirection for the viewer, .rdp files and Windows hotplug - #2178

Open
Marc-André Moreau (mamoreau-devolutions) wants to merge 9 commits into
masterfrom
claude/viewer-drive-redirection-755e63
Open

Marc-André Moreau (mamoreau-devolutions) wants to merge 9 commits into
masterfrom
claude/viewer-drive-redirection-755e63

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Drive redirection used to be reachable only through ActiveX and the daemon's fixed volume list. This makes it usable from the viewer and from standard .rdp files, and adds automatic drive hotplug on Windows.

Changes

  • ironrdp-cfg: DriveRedirection parses drivestoredirect with mstsc semantics (* = every drive including later ones, DynamicDrives, C:\;E:\). A nonempty drivestoredirect wins; otherwise redirectdrives:i:1 selects every drive, as in FreeRDP. New PropertySetExt accessors for both keys plus drive_redirection().
  • ironrdp-client: RdpdrConfig::drives and ConfigBuilder::with_drive_redirection, filled from the property set. The client only carries the selection; frontends build the native factory.
  • ironrdp-rdpdr-native (Windows):
    • A redirected root may now be a directory on a volume (C:\Users\name), not only C:\. Paths with .., \?\/device/UNC prefixes, : or NUL are rejected, and the root is still opened with OBJ_DONT_REPARSE.
    • WindowsRdpdrBackendFactory::with_logical_drives registers drive letters as device IDs 1–26 and announces the selected letters present when each connection (including reconnects) starts.
    • LogicalDriveWatcher watches WM_DEVICECHANGE on a hidden top-level window, plus a 2 s GetLogicalDrives poll for network drives, which are not broadcast. It reports arrivals and removals; arrivals are added to the shared selection.
  • ironrdp-viewer: --drive NAME,PATH, --drives, --home-drive, --dynamic-drives; honors the .rdp keys; attaches the RDPDR backend whenever any drive, smartcard or printer redirection is requested; forwards watcher changes as AddRdpdrDrive / RemoveRdpdrDrive.
  • ironrdp-daemon: honors drivestoredirect / redirectdrives on connect (an explicit ironrdp_rdpdr:i:0 disables them) and keeps a watcher for the session's lifetime. Configured daemon volumes now use device IDs from 27, so they cannot collide with drive letters.

Testing

  • cargo xtask check fmt -v and cargo xtask check lints -v pass.
  • New tests: .rdp parsing and precedence (testsuite-core/tests/cfg.rs), viewer CLI and .rdp mapping (testsuite-extra/tests/client/config.rs), root path validation, logical-drive factory, daemon factory.
  • Opt-in watcher_reports_subst_drive_arrival_and_removal (--ignored) maps a free letter with subst and passes locally: arrival and removal are both reported.
  • Not done: a live session against a server, and a physical USB stick.
  • e2e::egfx_moves_onto_the_udp_tunnel_with_soft_sync timed out once under a parallel run and passed 3/3 alone; it is unrelated to this change.

Merge note

This overlaps the printer redirection PR #2177 in ironrdp-rdpdr-native (factory.rs, exports, README), ironrdp-client config, and the viewer's cli.rs / main.rs. Whichever lands second needs a rebase. The device ID layout is: 0 smartcard, 1–26 drive letters, 27+ named drives, printers from u32::MAX - 1 down.

🤖 Generated with Claude Code

Parse the mstsc drive selection (`*`, `DynamicDrives`, drive letters separated
by `;`) into `DriveRedirection`, with a nonempty `drivestoredirect` taking
precedence over the legacy `redirectdrives:i:1` as in FreeRDP.
`ConfigBuilder` exposes the selection on `RdpdrConfig::drives` for frontends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Allow redirected roots below a volume root (`C:\Users\name`), still opened
without following reparse points. `with_logical_drives` registers device IDs
1-26 for drive letters and announces the selected letters present at each
connection. `LogicalDriveWatcher` reports volumes mounted and removed from
`WM_DEVICECHANGE` broadcasts and a `GetLogicalDrives` poll for network drives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `--drive NAME,PATH`, `--drives`, `--home-drive` and `--dynamic-drives`,
honor the `.rdp` drive properties, attach the Windows RDPDR backend whenever a
drive is requested, and forward hotplugged drives to the session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the selected drive letters to the per-connect RDPDR factory and watch for
dynamic drives for the session's lifetime. Configured volumes now use device
IDs from 27 so they cannot collide with drive letters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 00:41
@github-actions github-actions Bot added breaking-change Includes a breaking change, and requires special scrutiny at the boundaries kind/protocol Affects RDP or related protocol behavior risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/cross-cutting Spans multiple architectural boundaries size/XXL Size: 1300 or more counted lines or 50 or more files labels Oct 21, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Root-path and gateway-policy bypasses remain, alongside hotplug delivery and readiness issues.

5 open issues
What changed in this PR

Adds drive redirection to the viewer and .rdp configuration, backed by Windows drive discovery and hotplug support.

Changes:

  • Parses drive selections and exposes viewer CLI options.
  • Supports directory roots, stable drive-letter IDs, and Windows hotplug notifications.
  • Integrates daemon sessions and adds tests and documentation.
File Description
crates/​ironrdp-viewer/​src/​main.rs Attaches drive backend and retains watcher.
crates/​ironrdp-viewer/​src/​lib.rs Exports Windows drive support.
crates/​ironrdp-viewer/​src/​drives.rs Builds factories and forwards hotplug events.
crates/​ironrdp-viewer/​src/​cli.rs Adds drive selection options.
crates/​ironrdp-viewer/​README.md Documents drive options and properties.
crates/​ironrdp-testsuite-extra/​tests/​client/​config.rs Tests viewer drive configuration.
crates/​ironrdp-testsuite-core/​tests/​cfg.rs Tests parsing, precedence, and serialization.
crates/​ironrdp-rdpdr-native/​src/​windows/​mod.rs Exports drive APIs.
crates/​ironrdp-rdpdr-native/​src/​windows/​logical_drives.rs Implements discovery, selection, and hotplug watching.
crates/​ironrdp-rdpdr-native/​src/​windows/​handles.rs Accepts directory roots and validates paths.
crates/​ironrdp-rdpdr-native/​src/​windows/​factory.rs Registers and selects logical drives.
crates/​ironrdp-rdpdr-native/​src/​lib.rs Exposes Windows drive APIs publicly.
crates/​ironrdp-rdpdr-native/​README.md Documents roots and hotplug integration.
crates/​ironrdp-rdpdr-native/​Cargo.toml Enables required Windows APIs.
crates/​ironrdp-daemon/​src/​daemon.rs Integrates selections, watchers, and reserved IDs.
crates/​ironrdp-daemon/​README.md Documents connect-time drive selection.
crates/​ironrdp-client/​src/​rdp.rs Updates test configuration literals.
crates/​ironrdp-client/​src/​config.rs Carries drive selection through configuration.
crates/​ironrdp-cfg/​src/​lib.rs Adds drive parsing and property accessors.

Review effort: Balanced

Comment thread crates/ironrdp-rdpdr-native/src/windows/handles.rs
Comment thread crates/ironrdp-viewer/src/drives.rs Outdated
Comment thread crates/ironrdp-rdpdr-native/src/windows/logical_drives.rs Outdated
Comment thread crates/ironrdp-rdpdr-native/src/windows/logical_drives.rs Outdated
Comment thread crates/ironrdp-viewer/src/drives.rs Outdated
- Keep OBJ_DONT_REPARSE on redirected-root opens even when the server asks
  for FILE_OPEN_REPARSE_POINT, so a junction above a directory root is not
  followed.
- Ignore dynamic drive additions on a channel built without drive capability,
  so a queued event cannot bypass gateway drive policy.
- Probe only the selected letters when a connection starts, and re-probe
  mapped but unready letters periodically so reconnected network shares appear.
- Let the watcher callback report undelivered changes, which are retried on
  the next poll instead of being lost when the input queue is full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added automation-failed Exact-head automated classification or review failed or was unavailable risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny and removed risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny labels Oct 21, 2026
- Signal watcher readiness before probing volumes, so starting a session with
  dynamic drives does not wait on unreachable network shares.
- Treat a letter mapped before the session as connected later only when it is
  selected, unmapped and remapped, or announced by a volume arrival broadcast,
  so a preexisting share that becomes reachable is not redirected or reprobed.
- Reject invalid drive roots in RedirectedDrive::new instead of failing at
  announcement time.
- Canonicalize viewer named drive paths, which resolves junctions in the user
  profile path, and reject network paths, duplicate names or roots, and names
  reserved for drive letters.
- Drop .rdp drive letters in the viewer when ironrdp_rdpdr:i:0 disables device
  redirection, and warn on other platforms for any drive request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny and removed risk/unknown Risk could not be determined automatically; needs maintainer-level scrutiny automation-failed Exact-head automated classification or review failed or was unavailable labels Oct 21, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR makes drive redirection configurable (drivestoredirect/redirectdrives) for the viewer and daemon, adds Windows logical-drive enumeration and hotplug watching, and renumbers daemon volumes to device IDs 27+. Verification confirms the two Windows frontends carry near-verbatim copies of the selection-to-factory wiring and the try_send forwarding callback (with a subtle Full/Closed retry contract), define the reserved named-drive device-ID base 27 twice while the complementary 1-26 letter layout lives in ironrdp-rdpdr-native, and diverge on display-name policy: the viewer rejects drive-letter names while the daemon does not. The daemon also ships a public DriveRedirection::contains_letter helper with no production caller. The claimed watcher startup arrival gap does not hold: in both frontends the watcher's initial snapshot precedes connection setup, so any selected volume present at snapshot time is announced by the factory's initial_drives() (selection intersected with currently …

Push a commit after addressing these findings. If no code change is needed, you may resolve inline threads and comment @github-actions review-ready to request human review.

Comment thread crates/ironrdp-daemon/src/daemon.rs Outdated
Comment thread crates/ironrdp-daemon/src/daemon.rs Outdated
Comment thread crates/ironrdp-daemon/src/daemon.rs Outdated
Comment thread crates/ironrdp-cfg/src/lib.rs Outdated
@github-actions github-actions Bot added ai-reviewed/1 One automated review completed needs-author-action The pull request author is the current next actor labels Oct 21, 2026
- Move the drive-selection-to-factory wiring and the hotplug forwarding
  callback into ironrdp_daemon::drives, used by both the daemon and the viewer.
- Export FIRST_NAMED_DRIVE_DEVICE_ID and LogicalDriveSelection::from_letters
  from ironrdp-rdpdr-native, which owns the drive-letter device ID layout.
- Reject configured drives with drive-letter names in with_logical_drives, so
  both frontends refuse a name like C: only when drive letters are redirected.
- Remove the unused DriveRedirection::contains_letter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the needs-author-action The pull request author is the current next actor label Oct 21, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-redirection PR adding mstsc-syntax drivestoredirect parsing, viewer/daemon wiring, and a Windows hotplug watcher. The core design is sound: device-ID layout avoids collisions, hotplug additions are gated on drive capability, and the earlier duplication was consolidated in response to review. All six candidates were independently verified against pr-head and are published. Two are questions kept open: a nonempty drivestoredirect unconditionally overrides an explicit redirectdrives:i:0 opt-out (a deliberate, tested but protocol-semantics-sensitive precedence), and the watcher's live behavior against real broadcasts and hardware is unproven. The remaining candidates are a silent drop of unrecognized drivestoredirect entries and three low-severity simplifications (duplicated ineligible computation, single-use helper, fallible usize conversions).

Push a commit after addressing these findings. If no code change is needed, you may resolve inline threads and comment @github-actions review-ready to request human review.

Comment thread crates/ironrdp-cfg/src/lib.rs
Comment thread crates/ironrdp-cfg/src/lib.rs
Comment on lines +333 to +421
fn run_watcher(
selection: LogicalDriveSelection,
mut on_change: ChangeCallback,
ready: mpsc::SyncSender<io::Result<u32>>,
) {
let window = match create_watcher_window() {
Ok(window) => window,
Err(error) => {
let _ = ready.send(Err(error));
return;
}
};
// SAFETY: `window` is a live window owned by this thread, and no timer callback is used.
if unsafe { SetTimer(Some(window), 1, LOGICAL_DRIVE_POLL_INTERVAL_MS, None) } == 0 {
warn!(error = %io::Error::last_os_error(), "Unable to start logical drive polling");
}

// SAFETY: `GetLogicalDrives` takes no arguments.
let mut letters = unsafe { GetLogicalDrives() };
// Letters mapped before the session are not "connected later" until they are unmapped or
// announced by a volume arrival broadcast.
let mut preexisting = LogicalDriveSet::from_mask(letters);
// Report readiness before probing volumes, which can block on unreachable network shares.
// SAFETY: `GetCurrentThreadId` takes no arguments. The thread has a message queue because it
// owns `window`, so `WM_QUIT` posted to this ID reaches the loop below.
let _ = ready.send(Ok(unsafe { GetCurrentThreadId() }));
let mut present = LogicalDriveSet::present();
debug!(drives = ?present.iter().map(LogicalDrive::letter).collect::<String>(), "Watching logical drives");

let mut polls = 0u32;
let mut undelivered = false;
let mut message = MSG::default();
loop {
// SAFETY: `message` is a valid writable `MSG`.
let result = unsafe { GetMessageW(&mut message, None, 0, 0) };
if result.0 == 0 {
break;
}
if result.0 == -1 {
warn!(error = %io::Error::last_os_error(), "Logical drive watcher message loop failed");
break;
}

let current = match message.message {
WM_RESCAN_LOGICAL_DRIVES => {
// SAFETY: `GetLogicalDrives` takes no arguments.
letters = unsafe { GetLogicalDrives() };
let arrived = LogicalDriveSet::from_mask(u32::try_from(message.wParam.0).unwrap_or(0));
preexisting = preexisting
.intersection(LogicalDriveSet::from_mask(letters))
.difference(arrived);
Some(LogicalDriveSet::present())
}
WM_TIMER => {
// Probe every volume only when the letter mask changes or a change is undelivered:
// the probe can block on slow network drives, and media changes are broadcast.
// SAFETY: `GetLogicalDrives` takes no arguments.
let current = unsafe { GetLogicalDrives() };
preexisting = preexisting.intersection(LogicalDriveSet::from_mask(current));
polls = polls.wrapping_add(1);
if core::mem::replace(&mut letters, current) != current || undelivered {
Some(LogicalDriveSet::present())
} else if polls.is_multiple_of(UNREADY_RETRY_POLLS) {
let ineligible = preexisting.difference(selection.get());
let unready = LogicalDriveSet::from_mask(current)
.difference(present)
.difference(ineligible);
(!unready.is_empty()).then(|| present.union(unready.present_subset()))
} else {
None
}
}
_ => {
// SAFETY: `message` was filled by `GetMessageW`.
unsafe { DispatchMessageW(&message) };
None
}
};
if let Some(current) = current {
let ineligible = preexisting.difference(selection.get());
(present, undelivered) = report_changes(present, current, ineligible, &selection, &mut on_change);
}
}

// SAFETY: `window` is owned by this thread and has not been destroyed.
if let Err(error) = unsafe { DestroyWindow(window) } {
debug!(%error, "Unable to destroy the logical drive watcher window");
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[skeptical] Core hotplug behavior of the new Win32 watcher is unproven against live sessions and hardware — medium 🟠 ❓ — run_watcher combines WM_DEVICECHANGE broadcasts (queued via PostMessageW), a 2 s GetLogicalDrives poll, an every-8th-poll unready-volume retry, and preexisting-letter tracking feeding report_changes. Unit tests cover the pure state machine and one opt-in subst test covers arrival/removal locally, but the PR states no live session against a server and no physical USB stick was tested. Failure modes that only manifest live (broadcast timing, GetVolumeInformationW blocking on unreachable shares during retries, reconnect announcement ordering) are exactly what the changed lines implement; the interaction with real Windows behavior is asserted by documentation comments rather than evidence.

Comment thread crates/ironrdp-rdpdr-native/src/windows/logical_drives.rs Outdated
Comment thread crates/ironrdp-cfg/src/lib.rs Outdated
Comment on lines +505 to +543
let arrived = if usize::try_from(DBT_DEVICEARRIVAL).is_ok_and(|event| wparam.0 == event) {
// SAFETY: for `DBT_DEVICEARRIVAL`, `lparam` is null or points to a broadcast header that
// stays valid while this message is processed.
unsafe { arrived_volume_mask(lparam) }
} else {
0
};
// Broadcasts are sent, not posted, so queue the rescan for the message loop.
// SAFETY: `window` is the live window receiving this message.
if let Err(error) = unsafe {
PostMessageW(
Some(window),
WM_RESCAN_LOGICAL_DRIVES,
WPARAM(usize::try_from(arrived).unwrap_or(0)),
LPARAM(0),
)
} {
warn!(%error, "Unable to queue a logical drive rescan");
}
return LRESULT(1);
}
// SAFETY: forwards the unmodified message parameters to the default window procedure.
unsafe { DefWindowProcW(window, message, wparam, lparam) }
}

/// Returns the drive letters announced by a `DBT_DEVICEARRIVAL` volume broadcast.
///
/// # Safety
///
/// `lparam` must be null or point to a valid `DEV_BROADCAST_HDR` followed by `dbch_size` bytes.
unsafe fn arrived_volume_mask(lparam: LPARAM) -> u32 {
let header = core::ptr::with_exposed_provenance::<DEV_BROADCAST_HDR>(usize::from_ne_bytes(lparam.0.to_ne_bytes()));
if header.is_null() {
return 0;
}
// SAFETY: the caller guarantees that a non-null `header` points to a broadcast header.
let header_value = unsafe { header.read_unaligned() };
let is_volume = header_value.dbch_devicetype == DBT_DEVTYP_VOLUME
&& usize::try_from(header_value.dbch_size).is_ok_and(|size| size_of::<DEV_BROADCAST_VOLUME>() <= size);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[code-compressor] usize::try_from(u32) with unwrap_or/is_ok_and where usize::from is lossless — low 🟡 — Line 505 (usize::try_from(DBT_DEVICEARRIVAL).is_ok_and(...)), line 518 (usize::try_from(arrived).unwrap_or(0)), and line 543 (usize::try_from(header_value.dbch_size).is_ok_and(...)) convert u32 to usize. This crate compiles only for Windows, where usize is at least 32 bits, so usize::from is infallible and behavior-preserving; the current form adds fallible plumbing whose failure arm (0) is unreachable and, for dbch_size, would silently skip the malformed-header check. Replacing the three sites with usize::from changes no reachable behavior.

@github-actions github-actions Bot added ai-reviewed/2 Two automated reviews completed needs-author-action The pull request author is the current next actor and removed ai-reviewed/1 One automated review completed labels Oct 21, 2026
- An explicit redirectdrives:i:0 now disables drive redirection even when
  drivestoredirect still lists drives, so a stale list cannot be exposed.
- Add DriveRedirection::unrecognized_entries; the viewer and daemon warn about
  drivestoredirect entries they ignore, such as FreeRDP folder paths.
- Compute the watcher's ineligible drive set once per message and inline a
  single-use letter helper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the needs-author-action The pull request author is the current next actor label Oct 21, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #2178 adds drive redirection for the viewer, .rdp properties and Windows hotplug. The core design is sound: DriveRedirection parsing follows mstsc semantics with documented precedence, the path hardening keeps OBJ_DONT_REPARSE and rejects reparse/UNC/stream forms, device IDs 1-26 for letters and 27+ for named drives are collision-checked, and the drive_hotplug_available gate prevents queued events from adding capability after negotiation. All six validated specialist candidates were independently verified in the head revision and are published: the connect path synchronously probes every selected volume with no timeout, the shared drive wiring lives in the daemon crate's public API with client-session types, --rpc silently drops the new drive flags, a hotplug-watcher startup failure aborts the daemon connect, and two small duplication spots remain in ironrdp-cfg and the daemon's error responses.

Push a commit after addressing these findings. If no code change is needed, you may resolve inline threads and comment @github-actions review-ready to request human review.

Comment thread crates/ironrdp-rdpdr-native/src/windows/factory.rs Outdated
Comment on lines +7 to +12
use ironrdp_client::rdp::{RdpInputEvent, RdpInputSender};
use ironrdp_rdpdr_native::{
LogicalDriveChange, LogicalDriveSelection, LogicalDriveWatcher, WindowsRdpdrBackendFactory,
};
use tokio::sync::mpsc::error::TrySendError;
use tracing::warn;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[skeptical] Frontend-shared drive wiring is exported from the daemon crate and depends on client-session types — low 🟡 — logical_drive_factory and watch_logical_drives are generic viewer/daemon helpers but live as public API in ironrdp-daemon and import ironrdp_client::rdp::{RdpInputEvent, RdpInputSender}; the viewer consumes them through its pre-existing daemon dependency. This inverts ownership: the daemon crate's public API carries client input-event types and serves viewer needs, so client-side changes to RdpInputSender ripple through it. No new crate edges and the alternatives (a new crate for ~100 lines, or client-session types in ironrdp-rdpdr-native) are worse, so this is a low-severity architectural note rather than a defect.

Comment thread crates/ironrdp-viewer/src/cli.rs
Comment thread crates/ironrdp-daemon/src/daemon.rs Outdated
Comment thread crates/ironrdp-cfg/src/lib.rs
Comment thread crates/ironrdp-daemon/src/daemon.rs
@github-actions github-actions Bot added ai-reviewed/3 Final automated review completed needs-author-action The pull request author is the current next actor and removed ai-reviewed/2 Two automated reviews completed labels Oct 21, 2026
- Announce selected drive letters from the logical-drive mask at connect,
  without probing volumes, so an unreachable share cannot stall session setup.
- Reject viewer drive options with --rpc, where connect properties select drives.
- Fall back to the drives present at connect when the hotplug watcher cannot
  start, instead of failing the session.
- Share one drivestoredirect entry classifier between parse and
  unrecognized_entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added needs-review A human reviewer is the current next actor and removed needs-author-action The pull request author is the current next actor labels Oct 21, 2026

This branch was successfully deployed

1 active deployment
llm-providers — a20153bf Deployed Oct 21, 2026 by mamoreau-devolutions via Classify pull request #4929
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed/3 Final automated review completed breaking-change Includes a breaking change, and requires special scrutiny at the boundaries kind/protocol Affects RDP or related protocol behavior needs-review A human reviewer is the current next actor risk/high Substantial core public API impact, or fail-closed triage; needs maintainer-level scrutiny scope/cross-cutting Spans multiple architectural boundaries size/XXL Size: 1300 or more counted lines or 50 or more files

Development

Successfully merging this pull request may close these issues.

2 participants