From b5fc922150d5d5144206871e2a5184b295d42c74 Mon Sep 17 00:00:00 2001 From: devolutionsbot <31221910+devolutionsbot@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:33:51 -0400 Subject: [PATCH] chore(release): prepare for publishing --- Cargo.lock | 174 +++++++++++----------- Cargo.toml | 2 +- protocols/rust/now-client/CHANGELOG.md | 170 +++++++++++++++++++++ protocols/rust/now-client/Cargo.toml | 2 +- protocols/rust/now-proto-pdu/CHANGELOG.md | 127 ++++++++++++++++ protocols/rust/now-proto-pdu/Cargo.toml | 2 +- 6 files changed, 391 insertions(+), 86 deletions(-) create mode 100644 protocols/rust/now-client/CHANGELOG.md diff --git a/Cargo.lock b/Cargo.lock index 385102d..d5b7b6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -129,9 +129,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.4.7" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" dependencies = [ "find-msvc-tools", "shlex", @@ -203,7 +203,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -220,9 +220,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "expect-test" @@ -236,9 +236,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" [[package]] name = "form_urlencoded" @@ -305,7 +305,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -420,9 +420,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" dependencies = [ "displaydoc", "potential_utf", @@ -434,9 +434,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" dependencies = [ "displaydoc", "litemap", @@ -447,9 +447,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -461,15 +461,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" [[package]] name = "icu_properties" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" dependencies = [ "displaydoc", "icu_collections", @@ -482,15 +482,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" [[package]] name = "icu_provider" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" dependencies = [ "displaydoc", "icu_locale_core", @@ -545,9 +545,9 @@ dependencies = [ [[package]] name = "ironrdp-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd344ce9518ab83f6f7568ca4f1bc6dc8c55bd2da04cb5ee7b3e8740d5041734" +checksum = "97c5b444931aeea5d2967c94d493bcc809de63b491ec96389e643c224ba60849" [[package]] name = "itoa" @@ -557,9 +557,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.105" +version = "0.3.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +checksum = "8dc09d2154098bbf4ef54eab6b117a3c24cbbffe94046b16ee6d6d4510584d3a" dependencies = [ "cfg-if", "futures-util", @@ -568,9 +568,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.189" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "litemap" @@ -604,7 +604,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "now-client" -version = "0.1.0" +version = "0.1.1" dependencies = [ "now-proto-pdu", "thiserror", @@ -662,7 +662,7 @@ version = "0.0.0" [[package]] name = "now-proto-pdu" -version = "0.4.4" +version = "0.5.0" dependencies = [ "bitflags", "ironrdp-core", @@ -765,7 +765,7 @@ checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -913,7 +913,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -929,9 +929,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -991,9 +991,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.16.1" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" [[package]] name = "stable_deref_trait" @@ -1035,9 +1035,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -1058,34 +1058,34 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "thiserror" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.20" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "tinystr" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" dependencies = [ "displaydoc", "zerovec", @@ -1093,9 +1093,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.53.1" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "pin-project-lite", @@ -1110,23 +1110,23 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "toml_datetime" -version = "1.1.1+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.25.15+spec-1.1.0" +version = "0.25.17+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" dependencies = [ "indexmap", "toml_datetime", @@ -1136,9 +1136,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.1.3+spec-1.1.0" +version = "1.1.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" dependencies = [ "winnow", ] @@ -1202,9 +1202,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-segmentation" @@ -1238,24 +1238,32 @@ dependencies = [ [[package]] name = "utf8_iter" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +checksum = "af3bd9d3e571c26fcce9d033b87ea5ab061b8d2ffe9b7a5675d810d68f03edf0" +dependencies = [ + "utf_types", +] + +[[package]] +name = "utf_types" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6" [[package]] name = "uuid" -version = "1.26.1" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" [[package]] name = "wasm-bindgen" -version = "0.2.128" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +checksum = "a0181c14ddd6c7b8b0b0132a5f1ea2f32be0d893cd3ea97b6e3b28886b8a3d1c" dependencies = [ "cfg-if", - "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", @@ -1263,9 +1271,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.128" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +checksum = "6e931a02a39abdbf66966fc35902b5ed58344750a70885bed4ebb3d8eaf28ed9" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1273,22 +1281,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.128" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +checksum = "2ca4b54280e455bd831e33a6b8e734baf432fe63fbc35c42ac14ce67a043a53a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.128" +version = "0.2.130" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +checksum = "3b1f9c3798725016c6ee5f2f57869ec0831e8bbc0ab0310a86e787e97f41389e" dependencies = [ "unicode-ident", ] @@ -1393,9 +1401,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -1404,13 +1412,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] @@ -1431,7 +1439,7 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] @@ -1448,9 +1456,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" dependencies = [ "yoke", "zerofrom", @@ -1459,13 +1467,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4078ba9..69945a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ keywords = ["rdp", "remote-desktop", "network", "client", "protocol"] categories = ["network-programming"] [workspace.dependencies] -now-proto-pdu = { version = "0.4", path = "protocols/rust/now-proto-pdu" } +now-proto-pdu = { version = "0.5", path = "protocols/rust/now-proto-pdu" } now-proto-fuzzing = { version = "0.1", path = "protocols/rust/now-proto-fuzzing" } [profile.test.package.proptest] diff --git a/protocols/rust/now-client/CHANGELOG.md b/protocols/rust/now-client/CHANGELOG.md new file mode 100644 index 0000000..a287217 --- /dev/null +++ b/protocols/rust/now-client/CHANGELOG.md @@ -0,0 +1,170 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +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.1.1](https://github.com/Devolutions/now-libraries/compare/now-client-v0.1.0...now-client-v0.1.1)] - 2026-10-30 + +### Features + +- Add elevated execution support to exec messages ([#103](https://github.com/Devolutions/now-libraries/issues/103)) ([4a24d1e50d](https://github.com/Devolutions/now-libraries/commit/4a24d1e50d1b952ff42525c9f34478abbc82746c)) + + ## Summary + + Updates NOW-PROTO to **v1.7**, adding the ability for a client to + request that an exec session run with elevated privileges. + + RDM has had a "Run as administrator" checkbox in *Execute script via + Devolutions Agent* since the legacy RDM Jump days, where it worked — the + receiving RDM ran `ShellExecute` with the `runas` verb. The jump + transport was removed in RDMW-21914 and the NOW exec path has no way to + carry the intent, so the checkbox has silently done nothing since. This + PR adds the protocol half and both client libraries. + + ## What this adds + + - **`NOW_EXEC_FLAG_*_ELEVATED`** on the run, process, shell, batch, + winps and pwsh exec messages. + - **`NOW_CAP_EXEC_ELEVATION`** (0x0080) — advertised by servers that can + run elevated exec sessions. The baseline semantics are the most + restrictive a platform offers: it may prompt the interactive user for + consent, and stdio is not redirected. + - **`NOW_CODE_ELEVATION_DENIED`** (0x0009) and + **`NOW_CODE_ELEVATION_UNAVAILABLE`** (0x000A) — NOW-level status codes + so a client never has to sniff Win32 numbers to learn why elevation + failed. + - **`NOW_EXEC_FLAG_BATCH_NO_EXIT`** (0x0010) — `cmd /K` rather than + `/C`. Under elevation there is no stdio, so a console left open is the + only way the user sees output. winps/pwsh already had `NO_EXIT`; batch + did not. Must be ignored when the session redirects stdio, where the + hidden interpreter would never exit. + - A new normative **"Elevated Execution"** spec section. + - **Client-side refusal** in both `Devolutions.NowClient` and the Rust + `now-client`: an elevated request is rejected unless the negotiated + capset advertises elevation. The .NET client also refuses batch `NoExit` + to a pre-1.7 peer. + + ## Design: intent, not mechanism + + The part worth reviewing. Three rules, all aimed at letting a better + elevation mechanism land later without a breaking change: + + 1. **The message expresses intent only.** The flag is `ELEVATED`, never + `RUNAS`. A client cannot select a mechanism, so a server is free to + change how it elevates without any client change. + 2. **The server advertises what elevation costs** in `execCapset`, the + same way `UNICODE_CONSOLE` and `IO_REDIRECTION` already do. A client + learns what to expect *before* sending. + 3. **The capability is named by property, not mechanism.** An earlier + revision called it `ELEVATE_SHELL`. Clients only ever need to answer two + questions — *will it prompt?* and *will I get stdio?* — and a + mechanism-named bit makes them keep a table of mechanisms as more arrive + (a PEDM token path with no prompt and stdio; macOS `osascript … with + administrator privileges` with a prompt *and* stdio). + `NOW_CAP_EXEC_ELEVATION` is the conservative baseline; a future bit + relaxes exactly one property and says which. That matches the existing + capset, where every bit names what the host supports rather than how. + + Two scoping statements in the spec keep future work unblocked: + + - `ELEVATED` raises the privileges of the identity that would otherwise + run the command. It selects no identity and carries no credentials, so + "run as another user" has clean ground for a separate field. The spec is + honest that credential-prompt elevation (a standard user typing an + admin's password) runs the command as *that* account, because the + identity sentence is what "run as" will later be built on. + - An elevation capability means the server implements elevation, **not** + that every advertised exec style can be elevated. A server that cannot + elevate a style fails with `ELEVATION_UNAVAILABLE`; a client must handle + it per style. + + Three things the section now pins down because they otherwise become + support tickets: + + - `NOW_EXEC_STARTED_MSG` is sent **after** elevation is granted. The UAC + consent wait (up to 120 s) happens before it, so clients should start + execution timers at `STARTED`, not at submission. + - An elevated session sends **no `EXEC_DATA` at all**, and a server must + ignore `IO_REDIRECTION` set alongside `ELEVATED`, so no client waits on + a stream EOF that never comes. + - Abort and cancel **may fail** (the child runs at a higher integrity + level) and cannot dismiss a pending prompt. + + ## Why the client refuses locally + + The spec requires a server to fail rather than execute unelevated — but + that only binds servers at 1.7 or later. A **pre-1.7 server does not + know the flag**: it ignores the unknown bit and runs the command with + normal privileges, with nothing on the wire to distinguish that from + success. The server-side MUST cannot protect that case, so both clients + refuse the request when no elevation capability was advertised. Silent + downgrade is the exact defect this PR exists to remove; it would be + perverse to reintroduce it in the client. + + The Rust `now-client` additionally rejects an elevated request carrying + inline stdin or a timeout: there is no stdio to write to, and a timeout + is enforced by cancellation, which may fail. + + ## Bit allocation + + Each exec message has its own flag namespace; values avoid every bit + already in use. + + | Message | Flag | Value | + |---|---|---| + | `NOW_CHANNEL_CAPSET_MSG` (`execCapset`) | `NOW_CAP_EXEC_ELEVATION` | + `0x0080` | + | `NOW_EXEC_RUN_MSG` | `NOW_EXEC_FLAG_RUN_ELEVATED` | `0x0002` | + | `NOW_EXEC_PROCESS_MSG` | `NOW_EXEC_FLAG_PROCESS_ELEVATED` | `0x0008` | + | `NOW_EXEC_SHELL_MSG` | `NOW_EXEC_FLAG_SHELL_ELEVATED` | `0x0008` | + | `NOW_EXEC_BATCH_MSG` | `NOW_EXEC_FLAG_BATCH_ELEVATED` | `0x0008` | + | `NOW_EXEC_BATCH_MSG` | `NOW_EXEC_FLAG_BATCH_NO_EXIT` | `0x0010` | + | `NOW_EXEC_WINPS_MSG` / `NOW_EXEC_PWSH_MSG` | + `NOW_EXEC_FLAG_PS_ELEVATED` | `0x0800` | + | `NOW_STATUS` (`NOW_STATUS_ERROR_KIND_NOW`) | + `NOW_CODE_ELEVATION_DENIED` | `0x0009` | + | `NOW_STATUS` (`NOW_STATUS_ERROR_KIND_NOW`) | + `NOW_CODE_ELEVATION_UNAVAILABLE` | `0x000A` | + + ## Included bug fix (separate commit) + +- Add session lock and logoff requests ([#123](https://github.com/Devolutions/now-libraries/issues/123)) ([41ef2574b3](https://github.com/Devolutions/now-libraries/commit/41ef2574b34560aa2115e0f2eea0c40d4f328739)) + + Adds `NowClientHandle::session_lock` and + `NowClientHandle::session_logoff`, which send `NOW_SESSION_LOCK_MSG` and + `NOW_SESSION_LOGOFF_MSG`. These let an RDP client lock or log off the + interactive user session that hosts the NOW agent. RDP itself can only + disconnect, so a client-initiated logoff has to go through NOW. + + - **Response:** neither message has a response, so both methods complete + once the request is written and flushed. A successful logoff ends the + session that carries the channel, and the handle then returns + `WorkerClosed`. + - **Capability check:** each request requires the matching negotiated + session capability (`NowCapabilities::supports_session_lock` / + `supports_session_logoff`). + - The worker checks the capability when it writes the request, so a + capset refresh from the peer is honored. + - A request that isn't negotiated fails with + `NowClientError::UnsupportedCapability` and writes nothing. This matches + how the exec styles and the .NET client behave. + - **Default config:** the default `NowClientConfig` capset now + advertises `LOCK | LOGOFF`. Callers that build their own `client_capset` + must add these flags themselves. + + Tests cover: + - the exact header-only frames on the wire; + - the default capset advertisement; + - a peer omitting a capability; + - a peer capset refresh withdrawing lock; + - negotiation requiring both sides; + - the handle closing after the channel ends. + + 🤖 Generated with [Claude Code](https://claude.com/claude-code) + + --------- + + diff --git a/protocols/rust/now-client/Cargo.toml b/protocols/rust/now-client/Cargo.toml index 93e141b..5f40236 100644 --- a/protocols/rust/now-client/Cargo.toml +++ b/protocols/rust/now-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "now-client" -version = "0.1.0" +version = "0.1.1" readme = "README.md" description = "High-level Tokio client for NOW execution channels" edition.workspace = true diff --git a/protocols/rust/now-proto-pdu/CHANGELOG.md b/protocols/rust/now-proto-pdu/CHANGELOG.md index 141ec6e..8447f9b 100644 --- a/protocols/rust/now-proto-pdu/CHANGELOG.md +++ b/protocols/rust/now-proto-pdu/CHANGELOG.md @@ -6,6 +6,133 @@ 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.5.0](https://github.com/Devolutions/now-libraries/compare/now-proto-pdu-v0.4.4...now-proto-pdu-v0.5.0)] - 2026-10-30 + +### Features + +- Add elevated execution support to exec messages ([#103](https://github.com/Devolutions/now-libraries/issues/103)) ([4a24d1e50d](https://github.com/Devolutions/now-libraries/commit/4a24d1e50d1b952ff42525c9f34478abbc82746c)) + + ## Summary + + Updates NOW-PROTO to **v1.7**, adding the ability for a client to + request that an exec session run with elevated privileges. + + RDM has had a "Run as administrator" checkbox in *Execute script via + Devolutions Agent* since the legacy RDM Jump days, where it worked — the + receiving RDM ran `ShellExecute` with the `runas` verb. The jump + transport was removed in RDMW-21914 and the NOW exec path has no way to + carry the intent, so the checkbox has silently done nothing since. This + PR adds the protocol half and both client libraries. + + ## What this adds + + - **`NOW_EXEC_FLAG_*_ELEVATED`** on the run, process, shell, batch, + winps and pwsh exec messages. + - **`NOW_CAP_EXEC_ELEVATION`** (0x0080) — advertised by servers that can + run elevated exec sessions. The baseline semantics are the most + restrictive a platform offers: it may prompt the interactive user for + consent, and stdio is not redirected. + - **`NOW_CODE_ELEVATION_DENIED`** (0x0009) and + **`NOW_CODE_ELEVATION_UNAVAILABLE`** (0x000A) — NOW-level status codes + so a client never has to sniff Win32 numbers to learn why elevation + failed. + - **`NOW_EXEC_FLAG_BATCH_NO_EXIT`** (0x0010) — `cmd /K` rather than + `/C`. Under elevation there is no stdio, so a console left open is the + only way the user sees output. winps/pwsh already had `NO_EXIT`; batch + did not. Must be ignored when the session redirects stdio, where the + hidden interpreter would never exit. + - A new normative **"Elevated Execution"** spec section. + - **Client-side refusal** in both `Devolutions.NowClient` and the Rust + `now-client`: an elevated request is rejected unless the negotiated + capset advertises elevation. The .NET client also refuses batch `NoExit` + to a pre-1.7 peer. + + ## Design: intent, not mechanism + + The part worth reviewing. Three rules, all aimed at letting a better + elevation mechanism land later without a breaking change: + + 1. **The message expresses intent only.** The flag is `ELEVATED`, never + `RUNAS`. A client cannot select a mechanism, so a server is free to + change how it elevates without any client change. + 2. **The server advertises what elevation costs** in `execCapset`, the + same way `UNICODE_CONSOLE` and `IO_REDIRECTION` already do. A client + learns what to expect *before* sending. + 3. **The capability is named by property, not mechanism.** An earlier + revision called it `ELEVATE_SHELL`. Clients only ever need to answer two + questions — *will it prompt?* and *will I get stdio?* — and a + mechanism-named bit makes them keep a table of mechanisms as more arrive + (a PEDM token path with no prompt and stdio; macOS `osascript … with + administrator privileges` with a prompt *and* stdio). + `NOW_CAP_EXEC_ELEVATION` is the conservative baseline; a future bit + relaxes exactly one property and says which. That matches the existing + capset, where every bit names what the host supports rather than how. + + Two scoping statements in the spec keep future work unblocked: + + - `ELEVATED` raises the privileges of the identity that would otherwise + run the command. It selects no identity and carries no credentials, so + "run as another user" has clean ground for a separate field. The spec is + honest that credential-prompt elevation (a standard user typing an + admin's password) runs the command as *that* account, because the + identity sentence is what "run as" will later be built on. + - An elevation capability means the server implements elevation, **not** + that every advertised exec style can be elevated. A server that cannot + elevate a style fails with `ELEVATION_UNAVAILABLE`; a client must handle + it per style. + + Three things the section now pins down because they otherwise become + support tickets: + + - `NOW_EXEC_STARTED_MSG` is sent **after** elevation is granted. The UAC + consent wait (up to 120 s) happens before it, so clients should start + execution timers at `STARTED`, not at submission. + - An elevated session sends **no `EXEC_DATA` at all**, and a server must + ignore `IO_REDIRECTION` set alongside `ELEVATED`, so no client waits on + a stream EOF that never comes. + - Abort and cancel **may fail** (the child runs at a higher integrity + level) and cannot dismiss a pending prompt. + + ## Why the client refuses locally + + The spec requires a server to fail rather than execute unelevated — but + that only binds servers at 1.7 or later. A **pre-1.7 server does not + know the flag**: it ignores the unknown bit and runs the command with + normal privileges, with nothing on the wire to distinguish that from + success. The server-side MUST cannot protect that case, so both clients + refuse the request when no elevation capability was advertised. Silent + downgrade is the exact defect this PR exists to remove; it would be + perverse to reintroduce it in the client. + + The Rust `now-client` additionally rejects an elevated request carrying + inline stdin or a timeout: there is no stdio to write to, and a timeout + is enforced by cancellation, which may fail. + + ## Bit allocation + + Each exec message has its own flag namespace; values avoid every bit + already in use. + + | Message | Flag | Value | + |---|---|---| + | `NOW_CHANNEL_CAPSET_MSG` (`execCapset`) | `NOW_CAP_EXEC_ELEVATION` | + `0x0080` | + | `NOW_EXEC_RUN_MSG` | `NOW_EXEC_FLAG_RUN_ELEVATED` | `0x0002` | + | `NOW_EXEC_PROCESS_MSG` | `NOW_EXEC_FLAG_PROCESS_ELEVATED` | `0x0008` | + | `NOW_EXEC_SHELL_MSG` | `NOW_EXEC_FLAG_SHELL_ELEVATED` | `0x0008` | + | `NOW_EXEC_BATCH_MSG` | `NOW_EXEC_FLAG_BATCH_ELEVATED` | `0x0008` | + | `NOW_EXEC_BATCH_MSG` | `NOW_EXEC_FLAG_BATCH_NO_EXIT` | `0x0010` | + | `NOW_EXEC_WINPS_MSG` / `NOW_EXEC_PWSH_MSG` | + `NOW_EXEC_FLAG_PS_ELEVATED` | `0x0800` | + | `NOW_STATUS` (`NOW_STATUS_ERROR_KIND_NOW`) | + `NOW_CODE_ELEVATION_DENIED` | `0x0009` | + | `NOW_STATUS` (`NOW_STATUS_ERROR_KIND_NOW`) | + `NOW_CODE_ELEVATION_UNAVAILABLE` | `0x000A` | + + ## Included bug fix (separate commit) + + + ## [[0.4.4](https://github.com/Devolutions/now-libraries/compare/now-proto-pdu-v0.4.3...now-proto-pdu-v0.4.4)] - 2026-07-27 ### Documentation diff --git a/protocols/rust/now-proto-pdu/Cargo.toml b/protocols/rust/now-proto-pdu/Cargo.toml index e26b892..719921e 100644 --- a/protocols/rust/now-proto-pdu/Cargo.toml +++ b/protocols/rust/now-proto-pdu/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "now-proto-pdu" -version = "0.4.4" +version = "0.5.0" readme = "README.md" description = "NOW protocol PDU encoding and decoding" edition.workspace = true