Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
646 changes: 323 additions & 323 deletions Cargo.lock

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions crates/ironrdp-acceptor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,122 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [[0.11.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.10.0...ironrdp-acceptor-v0.11.0)] - 2026-08-13

### <!-- 0 -->Security

- Validate auto-reconnect cookies ([#1509](https://github.com/Devolutions/IronRDP/issues/1509)) ([44f675e244](https://github.com/Devolutions/IronRDP/commit/44f675e244ee76b5311756668ffbbe28e98c7175))

## Summary
- parse and carry `ARC_CS_PRIVATE_PACKET` data through the acceptor
- validate returning Enhanced RDP Security cookies with HMAC-MD5 before
reconnecting
- rotate reconnect randoms per connection and hourly, with runtime
cookie updates
- restrict cookie authentication to TLS/Hybrid and document the behavior

## Testing
- `cargo test -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server`
- `cargo clippy -p ironrdp-pdu -p ironrdp-acceptor -p ironrdp-server
--all-targets -- -D warnings`

### <!-- 1 -->Features

- Expose client multitransport flags on AcceptorResult ([#1453](https://github.com/Devolutions/IronRDP/issues/1453)) ([f0fc215555](https://github.com/Devolutions/IronRDP/commit/f0fc215555394a89510ff85c7b8a93b20e878074))

## What

The acceptor already parses the client's GCC `MultiTransportChannelData`
block (MS-RDPBCGR Β§2.2.1.3.8) into `ClientGccBlocks` during
`BasicSettingsWaitInitial` and then discards it, keeping only the
early-capability flags, core desktop size, and keyboard layout. This
surfaces the client's multitransport (MS-RDPEMT) capability flags on
`AcceptorResult`.

## Why

A server implementing UDP multitransport needs to know whether the
client advertised support (`SOFT_SYNC_TCP_TO_UDP`,
`TRANSPORT_TYPE_UDP_FEC{R,L}`) before deciding whether to send a Server
Initiate Multitransport Request. Today that information is parsed and
thrown away, so there's no way for a downstream server to see it.

## Shape

Purely additive, mirroring the existing `keyboard_layout` ([#1397](https://github.com/Devolutions/IronRDP/issues/1397)) and
desktop-size ([#1373](https://github.com/Devolutions/IronRDP/issues/1373)) surfacing of GCC client data the acceptor already
parses:

- new private `multitransport_flags: gcc::MultiTransportFlags` field on
`Acceptor`, captured from `gcc_blocks.multi_transport_channel`;
- new `pub multitransport_flags: gcc::MultiTransportFlags` field on
`AcceptorResult`;
- empty when the client sends no multitransport block;
- carried across a deactivation-reactivation like the sibling fields.

No behavior change β€” the acceptor just stops discarding a block it
already decodes.

`cargo clippy -p ironrdp-acceptor --all-targets` and `cargo fmt --check`
are clean.

- [**breaking**] Clamp honored client desktop size to an operator maximum ([#1404](https://github.com/Devolutions/IronRDP/issues/1404)) ([d3747a05b2](https://github.com/Devolutions/IronRDP/commit/d3747a05b202ba2d87ac19698354ae7e487850a2))

Follow-up to #1373 (the resource-hardening angle you flagged in review β€”
thanks for the go-ahead πŸ™‚).

## Problem

`#1373` gated honor-client-desktop-size behind a bare `bool`. With it
on, the acceptor adopts the client-requested desktop size bounded only
by the protocol range `[200, 8192]`. But the desktop size is a
client-controlled `u16`, and the server still builds its
framebuffer/encoder from the negotiated size β€” so a client could request
e.g. `8192x8192` and drive the server's allocation off an untrusted
number (~256 MiB per frame buffer). Mild, and only on an opt-in
default-off path, but it's a resource-exhaustion vector driven purely by
a number the client picks.

Your review comment: *"[200, 8192] is a protocol ceiling, not a resource
guard … tracked the 'clamp/range policy rather than a bare bool' idea as
a future follow-up (an operator-set max size)."* This is that PR.

## Change

Replace the `bool` with `Option<DesktopSize>` carrying an **operator-set
maximum**:

- `None` (default) β€” disabled; always enforce the server-provided size
(unchanged behavior).
- `Some(max)` β€” honor the client's request, **clamped per dimension to
`max`**. The client can ask for a smaller desktop, never a larger one.

The acceptor clamps the requested `width`/`height` to `max` *before* the
existing `validate_desktop_size` protocol-range check, so the negotiated
size can never exceed what the operator is willing to render β€” set `max`
to the host display's native resolution (or whatever ceiling the server
can afford).

- Support runtime-defined static virtual channels ([#1517](https://github.com/Devolutions/IronRDP/issues/1517)) ([8b4c483ba0](https://github.com/Devolutions/IronRDP/commit/8b4c483ba0c900a8de0b2718347754f56dd363ba))

## Summary
- add keyed runtime-defined static-channel registration, lookup, and
negotiated ID attachment
- enforce the static-channel limit and reject malformed SVC fragment
sequences
- wire generic connector, acceptor, and session name-based dispatch
support

## Testing
- `cargo test -p ironrdp-testsuite-core --test integration_tests_core
svc::`
- `cargo clippy -p ironrdp-testsuite-core --test integration_tests_core
-- -D warnings`

---------



## [[0.10.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-acceptor-v0.9.0...ironrdp-acceptor-v0.10.0)] - 2026-07-10

### <!-- 1 -->Features
Expand Down
10 changes: 5 additions & 5 deletions crates/ironrdp-acceptor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ironrdp-acceptor"
version = "0.10.0"
version = "0.11.0"
readme = "README.md"
description = "State machines to drive an RDP connection acceptance sequence"
edition.workspace = true
Expand All @@ -17,11 +17,11 @@ doctest = false
test = false

[dependencies]
ironrdp-core = { path = "../ironrdp-core", version = "0.2", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" } # public
ironrdp-core = { path = "../ironrdp-core", version = "0.3", features = ["alloc"] } # public
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" } # public
ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" } # public
ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" } # public
ironrdp-async = { path = "../ironrdp-async", version = "0.10" } # public
ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" } # public
ironrdp-async = { path = "../ironrdp-async", version = "0.11" } # public
tracing = { version = "0.1", features = ["log"] }

[lints]
Expand Down
12 changes: 6 additions & 6 deletions crates/ironrdp-activex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ doctest = false

[target.'cfg(windows)'.dependencies]
anyhow = "1"
ironrdp-client = { path = "../ironrdp-client", version = "0.1", features = ["clipboard", "dvc-com-plugin", "gateway", "rdpdr", "rustls", "sound"] }
ironrdp-client = { path = "../ironrdp-client", version = "0.2", features = ["clipboard", "dvc-com-plugin", "gateway", "rdpdr", "rustls", "sound"] }
ironrdp-cliprdr = { path = "../ironrdp-cliprdr", version = "0.7" }
ironrdp-cliprdr-native = { path = "../ironrdp-cliprdr-native", version = "0.7" }
ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.1" }
ironrdp-connector = { path = "../ironrdp-connector", version = "0.10" }
ironrdp-core = { path = "../ironrdp-core", version = "0.2" }
ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.2" }
ironrdp-connector = { path = "../ironrdp-connector", version = "0.11" }
ironrdp-core = { path = "../ironrdp-core", version = "0.3" }
ironrdp-daemon = { path = "../ironrdp-daemon", version = "0.1" }
ironrdp-input = { path = "../ironrdp-input", version = "0.7" }
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.9" }
ironrdp-pdu = { path = "../ironrdp-pdu", version = "0.10" }
ironrdp-rdpei = { path = "../ironrdp-rdpei", version = "0.1" }
ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" }
ironrdp-rail = { path = "../ironrdp-rail", version = "0.1" }
ironrdp-rdpdr-native = { path = "../ironrdp-rdpdr-native", version = "0.7" }
ironrdp-rpc = { path = "../ironrdp-rpc", version = "0.1" }
ironrdp-session = { path = "../ironrdp-session", version = "0.11" }
ironrdp-session = { path = "../ironrdp-session", version = "0.12" }
ironrdp-svc = { path = "../ironrdp-svc", version = "0.8" }
ironrdp-tls = { path = "../ironrdp-tls", version = "0.2" }
png = "0.18"
Expand Down
196 changes: 196 additions & 0 deletions crates/ironrdp-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,202 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).


## [[0.2.0](https://github.com/Devolutions/IronRDP/compare/ironrdp-agent-v0.1.0...ironrdp-agent-v0.2.0)] - 2026-08-13

### <!-- 0 -->Security

- Restrict Windows named pipe to the current user ([#1482](https://github.com/Devolutions/IronRDP/issues/1482)) ([26821836b7](https://github.com/Devolutions/IronRDP/commit/26821836b78e0670f4a0a41eb7021f6e1c256be4))

## Summary

The `ironrdp-agent` Windows named-pipe listener inherited the pipe
namespace's default ACL, so **any local user** could connect to a
running daemon and drive the session β€” inject input, capture
screenshots, read logs, and trigger NOW remote execution. The Unix
listener already locks its socket to `0o600` (owner-only); this mirrors
that stance on Windows.

## The fix

Build a **protected DACL** (`D:P(A;;GA;;;{user-sid})`) granting
`GENERIC_ALL` only to the current user's SID, and apply it to **every**
`CreateNamedPipeW` instance (both the first in `bind` and the
replacement minted in `accept`) via tokio's
`create_with_security_attributes_raw`. The descriptor is cached on the
`Listener` and reused across instances (the kernel copies it on each
`CreateNamedPipeW`, so it only needs to outlive each synchronous call).

Implementation follows the established windows-FFI style used by sibling
crates (`ironrdp-cliprdr-native`): the `windows` crate (0.62, matching
siblings), RAII guards (`OwnedTokenHandle`, `OwnedSecurityDescriptor`)
for `CloseHandle`/`LocalFree` cleanup, one unsafe op per block with `//
SAFETY:` comments, `.cast::<>()` + `try_from` instead of `as` casts, and
`tracing::warn!` on cleanup failure.

## Why this matters

On shared Windows hosts the `\\.\pipe\` namespace is reachable by every
local user by default. This is the last line of defense against a
different local user taking over a running agent session. It closes the
asymmetry with the Unix path, which already documents the `/tmp`
fallback as world-writable and explicitly sets `0o600`.

## Test coverage

`transport.rs` previously had **zero tests**. Added a Windows-gated
`#[cfg(test)]` smoke test
(`security_descriptor_for_current_user_is_non_null`) that exercises the
full token β†’ SID β†’ SDDL β†’ security-descriptor pipeline for the current
process and asserts a non-null descriptor is produced β€” the happy path
`Listener::bind` depends on. (Asserting the ACL denies a *different*
user requires a second token context and is out of scope.)

## Verification

All `cargo xtask` CI-equivalent checks pass on a clean tree:

- `cargo xtask check fmt -v` β†’ All good
- `cargo xtask check lints -v` (workspace clippy `--all-targets -D
warnings`) β†’ All good (ironrdp-agent produces zero warnings)
- `cargo test -p ironrdp-agent` β†’ 12 passed; 0 failed (incl. the new
test)
- `cargo xtask check locks -v` β†’ All good

## Files

- `crates/ironrdp-agent/Cargo.toml` β€” add `windows = "0.62"`
(cfg(windows),
Win32_Foundation/Security/Security_Authorization/System_Threading)
- `crates/ironrdp-agent/src/transport.rs` β€”
`OwnedTokenHandle`/`OwnedSecurityDescriptor` RAII guards,
`create_server_instance` helper, `Listener` caches + reuses the SD; new
smoke test
- `Cargo.lock` β€” +1 line registering `windows` as an ironrdp-agent dep
(no new versions; crates already present via siblings)

## Release

This is a `fix(agent):` conventional commit, picked up by release-plz
into the next release cycle (alongside the existing `feat(agent)`
NOW-integration commit) to propose the `0.2.0` bump once merged to
`master`. CHANGELOG is auto-generated β€” no manual edit needed.

- Connect to Windows Sandbox named pipes ([#1580](https://github.com/Devolutions/IronRDP/issues/1580)) ([39b020343d](https://github.com/Devolutions/IronRDP/commit/39b020343d962962bfbefc89939be64d5c716196))

Windows Sandbox's default attach path is a local named pipe carrying
plain TPKT/X.224 with PROTOCOL_RDP and ENCRYPTION_LEVEL_NONE, not
TCP:3389 or VMConnect. Allow the connector and client to complete that
sequence only via an explicit opt-in (`enable_standard_rdp_security`;
NamedPipe enables it), and teach ironrdp-agent to resolve pipe path and
guest credentials from WindowsSandboxServer after `wsb start`.

Adds Transport::NamedPipe, ironrdp_named_pipe/ironrdp_sandbox_id
properties, sandbox list/config/stop CLI helpers via an in-process
h2/gRPC client on the per-user `\\.\pipe\wsandbox\{guid}` pipe (no .NET
helper), and connect --sandbox-id / --sandbox-pipe. Sandbox-derived
properties are the merge base; explicit .rdp/--prop/flags override them
while NamedPipe TLS/CredSSP stay forced off. Local :2179+PCB remains
unsupported.

### <!-- 1 -->Features

- Integrate NOW client ([#1451](https://github.com/Devolutions/IronRDP/issues/1451)) ([405770e2a6](https://github.com/Devolutions/IronRDP/commit/405770e2a6232f506e10f88ab6cb413d6bec7c5b))

## Summary
- Integrate the released registry `now-client = "0.1.0"` into
`ironrdp-agent`; no Git, path, patch, or vendored dependency is used.
- Add a per-session `Devolutions::Now::Agent` DVC endpoint with
30-second initial readiness and 10-second reconnect deadlines.
- Add durable NOW operations, IPC streaming/retention, supported CLI
forms (`run`, `powershell`, `pwsh`, `exec process`, `exec batch`), safe
PowerShell defaults, raw output forwarding, and remote exit propagation.
- Add IPC/retention coverage and an adapter regression proving immediate
Run frames are quarantined before a following Process execution.
- Do not modify Gateway or `ironrdp-dvc-pipe-proxy`.

## Validation
- `cargo test -p ironrdp-agent`
- `cargo test -p ironrdp-testsuite-extra --test integration_tests_extra
agent`
- `cargo clippy -p ironrdp-agent --all-targets -- -D warnings`
- `cargo xtask check fmt -v`
- `cargo xtask check tests -v`
- `cargo xtask check locks -v`

`cargo xtask check lints -v` is blocked by a pre-existing `ironrdp-str`
`manual_is_multiple_of` lint under the available Rust 1.90 toolchain.
`cargo xtask check typos -v` cannot run because `typos-cli` is not
installed. Authorized-VM end-to-end validation remains pending access to
the designated RDP/NOW endpoint.

---------

- Expose generic session configuration and lifecycle APIs ([#1522](https://github.com/Devolutions/IronRDP/issues/1522)) ([57b1366650](https://github.com/Devolutions/IronRDP/commit/57b13666506dc40c15b4c4702d35150beee99133))

## Summary
- expose generic client configuration for connection metadata,
compression, shell/work directory, audio, and runtime static-channel
factories
- add bounded input delivery with independent close cancellation, host
clipboard plumbing, lifecycle events, and Display Control resize
readiness/fallback handling
- update agent, viewer, web, FFI, examples, and tests for the generic
APIs

## Stack dependencies
This PR is stacked on `copilot/tls-validation-policy` (`b2bbcece`),
which already includes the merged runtime static-channel support from
`master`. It intentionally contains no TLS implementation/policy,
ActiveX/COM, SVC implementation, decompression, or bitmap-recovery
changes.

## Validation
- `cargo fmt --check --all`
- `cargo xtask check tests --no-run -v`
- `cargo xtask check lints -v`
- `cargo test -p ironrdp-client --lib --features rustls`
- `cargo check -p ironrdp-agent -p ironrdp-viewer -p ironrdp-web -p ffi`

---------

- Configure static RDPDR drives ([#1617](https://github.com/Devolutions/IronRDP/issues/1617)) ([b06cd71e0f](https://github.com/Devolutions/IronRDP/commit/b06cd71e0f2845893db2123c8280f4a80e1b5a36))

Allow an agent daemon to opt in to fixed Windows filesystem drives.

Validate names, roots, and duplicate definitions before listening, then
attach the native RDPDR backend factory to each client.

- Add authorized RDPDR harness ([#1620](https://github.com/Devolutions/IronRDP/issues/1620)) ([24eb1f62f9](https://github.com/Devolutions/IronRDP/commit/24eb1f62f9aadf36fb6b3b588ecda390fcad2b38))

Add an opt-in Windows harness that verifies \\tsclient direct PowerShell
and Explorer copy paths for an explicitly authorized endpoint.

Keep TLS certificate and hostname validation strict by default, but
permit a daemon-start-only bypass for the authorized test endpoint. Add
bounded all-or-nothing Unicode text input for remote commands without
expanding ActiveX.

- Add RAIL audit commands ([#1646](https://github.com/Devolutions/IronRDP/issues/1646)) ([77759dca03](https://github.com/Devolutions/IronRDP/commit/77759dca032eb829b5be54a3c44d9be92252cf41))

Expose bounded client-validated RAIL events and RemoteApp launch
requests through the daemon IPC so headless agents can verify sessions.

Preserve cursors across resize reconnects, wake waiting readers for
locally queued launches, and redact launch data in logs.

Report terminal local Execute failures without disrupting an otherwise
valid RDP session.

### <!-- 99 -->Please Sort

- Add agentic RDP CLI and localhost CI workflow ([#1289](https://github.com/Devolutions/IronRDP/issues/1289)) ([ffedb69ca8](https://github.com/Devolutions/IronRDP/commit/ffedb69ca8d4f2dec5a0649fa2cc4758ee74d13f))

- Extract reusable RDP daemon support ([#1543](https://github.com/Devolutions/IronRDP/issues/1543)) ([dc4692538e](https://github.com/Devolutions/IronRDP/commit/dc4692538e67ca089969879b7c62b69558e128e6))

- Add ActiveX RPC backend for ironrdp-agent ([#1544](https://github.com/Devolutions/IronRDP/issues/1544)) ([c31e43f755](https://github.com/Devolutions/IronRDP/commit/c31e43f755dbf24a302c86ca1ef8ba186d51216e))



## [[0.1.0](https://github.com/Devolutions/IronRDP/releases/tag/ironrdp-agent-v0.1.0)] - 2026-07-10

Initial release.
4 changes: 2 additions & 2 deletions crates/ironrdp-agent/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ironrdp-agent"
version = "0.1.0"
version = "0.2.0"
readme = "README.md"
description = "CLI-driven, daemon-backed agentic RDP client suitable for LLM consumption"
edition.workspace = true
Expand All @@ -22,7 +22,7 @@ test = false
[dependencies]
# Configuration model and codecs
ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" }
ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.1" }
ironrdp-cfg = { path = "../ironrdp-cfg", version = "0.2" }
ironrdp-rdpfile = { path = "../ironrdp-rdpfile", version = "0.1" }
ironrdp-input = { path = "../ironrdp-input", version = "0.7" }
ironrdp-daemon = { path = "../ironrdp-daemon", version = "0.1" }
Expand Down
Loading