diff --git a/Cargo.lock b/Cargo.lock index 89fbea6d..cde538cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" +checksum = "e79b3f8a79cccc2898f31920fc69f304859b3bd567490f75ebf51ae1c792a9ac" dependencies = [ "compression-codecs", "compression-core", @@ -271,9 +271,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -282,9 +282,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -363,9 +363,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -462,7 +462,7 @@ dependencies = [ "ipnetwork", "lazy_static", "libc", - "lru", + "lru 0.17.0", "mockall", "mockito", "nix 0.31.2", @@ -512,7 +512,7 @@ dependencies = [ "aes 0.8.4", "async-trait", "aws-lc-rs", - "bitflags 2.11.0", + "bitflags 2.11.1", "block-padding 0.3.3", "byteorder", "bytes", @@ -526,13 +526,13 @@ dependencies = [ "der 0.8.0", "des", "digest 0.10.7", - "ecdsa 0.17.0-rc.16", + "ecdsa 0.17.0-rc.17", "ed25519-dalek", - "elliptic-curve 0.14.0-rc.30", + "elliptic-curve 0.14.0-rc.31", "enum_dispatch", "flate2", "futures", - "generic-array 1.3.5", + "generic-array 1.4.0", "getrandom 0.2.17", "hex-literal", "hmac 0.12.1", @@ -543,16 +543,16 @@ dependencies = [ "md5", "ml-kem", "module-lattice", - "p256 0.14.0-rc.8", - "p384 0.14.0-rc.8", - "p521 0.14.0-rc.8", + "p256 0.14.0-rc.9", + "p384 0.14.0-rc.9", + "p521 0.14.0-rc.9", "pbkdf2 0.12.2", "pkcs1 0.8.0-rc.4", "pkcs5", "pkcs8 0.11.0-rc.11", "polyval 0.7.1", "rand 0.10.1", - "rand_core 0.10.0", + "rand_core 0.10.1", "ring", "rsa 0.10.0-rc.17", "russh-cryptovec", @@ -631,9 +631,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.60" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -672,7 +672,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -739,9 +739,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -761,9 +761,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -832,9 +832,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +checksum = "ce2548391e9c1929c21bf6aa2680af86fe4c1b33e6cea9ac1cfeec0bd11218cf" dependencies = [ "compression-core", "flate2", @@ -843,9 +843,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" +checksum = "cc14f565cf027a105f7a44ccf9e5b424348421a1d8952a8fc9d499d313107789" [[package]] name = "console" @@ -1015,7 +1015,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crossterm_winapi", "derive_more", "document-features", @@ -1065,7 +1065,7 @@ dependencies = [ "getrandom 0.4.2", "hybrid-array", "num-traits", - "rand_core 0.10.0", + "rand_core 0.10.1", "serdect", "subtle", "zeroize", @@ -1089,7 +1089,7 @@ checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "getrandom 0.4.2", "hybrid-array", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -1100,7 +1100,7 @@ checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" dependencies = [ "crypto-bigint 0.7.3", "libm", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -1215,9 +1215,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "delegate" @@ -1357,7 +1357,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "block2", "libc", "objc2", @@ -1427,13 +1427,13 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.17.0-rc.16" +version = "0.17.0-rc.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829" +checksum = "dc4bf51f0534ed6e59a0f2f26272b64ba55c470133f8424c2adfd1c4d59d9988" dependencies = [ "der 0.8.0", "digest 0.11.2", - "elliptic-curve 0.14.0-rc.30", + "elliptic-curve 0.14.0-rc.31", "rfc6979 0.5.0-rc.5", "signature 3.0.0-rc.10", "spki 0.8.0", @@ -1458,7 +1458,7 @@ checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" dependencies = [ "curve25519-dalek", "ed25519", - "rand_core 0.10.0", + "rand_core 0.10.1", "serde", "sha2 0.11.0", "signature 3.0.0-rc.10", @@ -1493,9 +1493,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.14.0-rc.30" +version = "0.14.0-rc.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d7a0bfd012613a7bcfe02cbfccf2b846e9ef9e1bccb641c48d461253cfb034d" +checksum = "b148a81cede8f4023248f980cffdf7611c46f2add469c6980e815b7c5b764ba5" dependencies = [ "base16ct 1.0.0", "crypto-bigint 0.7.3", @@ -1506,7 +1506,7 @@ dependencies = [ "once_cell", "pem-rfc7468 1.0.0", "pkcs8 0.11.0-rc.11", - "rand_core 0.10.0", + "rand_core 0.10.1", "rustcrypto-ff", "rustcrypto-group", "sec1 0.8.1", @@ -1795,9 +1795,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.3.5" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" +checksum = "e38154b42567e31b925d3859382888aa6f86e446d1f018c89ef337fc1726bcf2" dependencies = [ "generic-array 0.14.7", "rustversion", @@ -1838,7 +1838,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", - "rand_core 0.10.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1934,6 +1934,11 @@ name = "hashbrown" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] [[package]] name = "heck" @@ -2051,9 +2056,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" dependencies = [ "ctutils", "subtle", @@ -2347,15 +2352,15 @@ dependencies = [ "bcrypt-pbkdf", "crypto-bigint 0.7.3", "dsa", - "ecdsa 0.17.0-rc.16", + "ecdsa 0.17.0-rc.17", "ed25519-dalek", "hex", "hmac 0.13.0", "num-bigint-dig", - "p256 0.14.0-rc.8", - "p384 0.14.0-rc.8", - "p521 0.14.0-rc.8", - "rand_core 0.10.0", + "p256 0.14.0-rc.9", + "p384 0.14.0-rc.9", + "p521 0.14.0-rc.9", + "rand_core 0.10.1", "rsa 0.10.0-rc.17", "sec1 0.8.1", "serde", @@ -2377,7 +2382,7 @@ dependencies = [ "num-integer", "num-traits", "rand 0.10.1", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -2482,7 +2487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" dependencies = [ "crypto-common 0.2.1", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -2508,9 +2513,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -2533,7 +2538,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2571,13 +2576,22 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" -version = "0.16.3" +version = "0.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" dependencies = [ "hashbrown 0.16.1", ] +[[package]] +name = "lru" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0b564323a0fb6d54b864f625ae139de9612e27edb944dda37c109f05aac531" +dependencies = [ + "hashbrown 0.17.0", +] + [[package]] name = "mac_address" version = "1.1.8" @@ -2661,7 +2675,7 @@ dependencies = [ "hybrid-array", "kem", "module-lattice", - "rand_core 0.10.0", + "rand_core 0.10.1", "sha3", ] @@ -2708,7 +2722,7 @@ dependencies = [ "hyper-util", "log", "pin-project-lite", - "rand 0.9.3", + "rand 0.9.4", "regex", "serde_json", "serde_urlencoded", @@ -2742,7 +2756,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -2755,7 +2769,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "cfg_aliases", "libc", @@ -2801,7 +2815,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "serde", "smallvec", "zeroize", @@ -2888,7 +2902,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3006,7 +3020,7 @@ dependencies = [ "futures-util", "opentelemetry", "percent-encoding", - "rand 0.9.3", + "rand 0.9.4", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -3047,14 +3061,14 @@ dependencies = [ [[package]] name = "p256" -version = "0.14.0-rc.8" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f0a10fe314869359cb2901342b045f4e5a962ef9febc006f03d2a8c848fe4c" +checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a" dependencies = [ - "ecdsa 0.17.0-rc.16", - "elliptic-curve 0.14.0-rc.30", + "ecdsa 0.17.0-rc.17", + "elliptic-curve 0.14.0-rc.31", "primefield", - "primeorder 0.14.0-rc.8", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] @@ -3072,15 +3086,15 @@ dependencies = [ [[package]] name = "p384" -version = "0.14.0-rc.8" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b079e66810c55ab3d6ba424e056dc4aefcdb8046c8c3f3816142edbdd7af7721" +checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03" dependencies = [ - "ecdsa 0.17.0-rc.16", - "elliptic-curve 0.14.0-rc.30", + "ecdsa 0.17.0-rc.17", + "elliptic-curve 0.14.0-rc.31", "fiat-crypto", "primefield", - "primeorder 0.14.0-rc.8", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] @@ -3100,15 +3114,15 @@ dependencies = [ [[package]] name = "p521" -version = "0.14.0-rc.8" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eecc34c4c6e6596d5271fecf90ac4f16593fa198e77282214d0c22736aa9266" +checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7" dependencies = [ "base16ct 1.0.0", - "ecdsa 0.17.0-rc.16", - "elliptic-curve 0.14.0-rc.30", + "ecdsa 0.17.0-rc.17", + "elliptic-curve 0.14.0-rc.31", "primefield", - "primeorder 0.14.0-rc.8", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] @@ -3168,9 +3182,9 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.13.0-rc.10" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f24f3eb2f4471b1730d59e4b730b747939960a8c7eb0c33c5a9076f2d3dddea" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ "digest 0.11.2", "hmac 0.13.0", @@ -3270,7 +3284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -3352,8 +3366,8 @@ dependencies = [ "aes-gcm 0.11.0-rc.3", "cbc 0.2.0", "der 0.8.0", - "pbkdf2 0.13.0-rc.10", - "rand_core 0.10.0", + "pbkdf2 0.13.0", + "rand_core 0.10.1", "scrypt", "sha2 0.11.0", "spki 0.8.0", @@ -3377,7 +3391,7 @@ checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" dependencies = [ "der 0.8.0", "pkcs5", - "rand_core 0.10.0", + "rand_core 0.10.1", "spki 0.8.0", ] @@ -3511,13 +3525,13 @@ dependencies = [ [[package]] name = "primefield" -version = "0.14.0-rc.8" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6543f5eec854fbf74ba5ef651fbdc9408919b47c3e1526623687135c16d12e9" +checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6" dependencies = [ "crypto-bigint 0.7.3", "crypto-common 0.2.1", - "rand_core 0.10.0", + "rand_core 0.10.1", "rustcrypto-ff", "subtle", "zeroize", @@ -3534,11 +3548,11 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.14.0-rc.8" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "569d9ad6ef822bb0322c7e7d84e5e286244050bd5246cac4c013535ae91c2c90" +checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a" dependencies = [ - "elliptic-curve 0.14.0-rc.30", + "elliptic-curve 0.14.0-rc.31", ] [[package]] @@ -3606,9 +3620,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -3617,9 +3631,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ec095654a25171c2124e9e3393a930bddbffdc939556c914957a4c3e0a87166" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -3633,7 +3647,7 @@ checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20 0.10.0", "getrandom 0.4.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -3676,9 +3690,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "ratatui" @@ -3700,13 +3714,13 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "compact_str", "hashbrown 0.16.1", "indoc", "itertools 0.14.0", "kasuari", - "lru", + "lru 0.16.4", "strum", "thiserror 2.0.18", "unicode-segmentation", @@ -3752,7 +3766,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.16.1", "indoc", "instability", @@ -3767,9 +3781,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -3791,7 +3805,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -3946,7 +3960,7 @@ dependencies = [ "digest 0.11.2", "pkcs1 0.8.0-rc.4", "pkcs8 0.11.0-rc.11", - "rand_core 0.10.0", + "rand_core 0.10.1", "sha2 0.11.0", "signature 3.0.0-rc.10", "spki 0.8.0", @@ -3955,9 +3969,9 @@ dependencies = [ [[package]] name = "rtoolbox" -version = "0.0.4" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327b72899159dfae8060c51a1f6aebe955245bcd9cc4997eed0f623caea022e4" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", "windows-sys 0.59.0", @@ -3981,7 +3995,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bb94393cafad0530145b8f626d8687f1ee1dedb93d7ba7740d6ae81868b13b5" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "chrono", "flurry", @@ -4019,7 +4033,7 @@ version = "0.14.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd2a8adb347447693cd2ba0d218c4b66c62da9b0a5672b17b981e4291ec65ff6" dependencies = [ - "rand_core 0.10.0", + "rand_core 0.10.1", "subtle", ] @@ -4029,7 +4043,7 @@ version = "0.14.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "369f9b61aa45933c062c9f6b5c3c50ab710687eca83dd3802653b140b43f85ed" dependencies = [ - "rand_core 0.10.0", + "rand_core 0.10.1", "rustcrypto-ff", "subtle", ] @@ -4040,7 +4054,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys", @@ -4049,9 +4063,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" dependencies = [ "aws-lc-rs", "log", @@ -4076,18 +4090,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.11" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20a6af516fea4b20eccceaf166e8aa666ac996208e8a644ce3ef5aa783bc7cd4" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -4107,7 +4121,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a990b25f351b25139ddc7f21ee3f6f56f86d6846b74ac8fad3a719a287cd4a0" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "clipboard-win", "home", @@ -4173,12 +4187,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scrypt" -version = "0.12.0-rc.10" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e03ed5b54ed5fcc8e016cd94301416bc2c01c05c87a6742b97468337c8804598" +checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" dependencies = [ "cfg-if", - "pbkdf2 0.13.0-rc.10", + "pbkdf2 0.13.0", "salsa20", "sha2 0.11.0", ] @@ -4233,7 +4247,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation", "core-foundation-sys", "libc", @@ -4499,7 +4513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" dependencies = [ "digest 0.11.2", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] @@ -4762,7 +4776,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "fancy-regex", "filedescriptor", "finl_unicode", @@ -4897,9 +4911,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.51.1" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -5030,7 +5044,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http", @@ -5123,9 +5137,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -5238,9 +5252,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "atomic", "getrandom 0.4.2", @@ -5305,11 +5319,11 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -5318,7 +5332,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -5413,7 +5427,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -5714,6 +5728,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -5763,7 +5783,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", diff --git a/Cargo.toml b/Cargo.toml index 0df30c60..f6f1f0b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,16 +18,16 @@ edition = "2024" [dependencies] bytes = "1.11.1" -tokio = { version = "1.51.1", features = ["full"] } +tokio = { version = "1.52.1", features = ["full"] } # Use our internal russh fork with session loop fixes # - Development: uses local path (crates/bssh-russh) # - Publishing: uses crates.io version (path ignored) russh = { package = "bssh-russh", version = "0.60.1", path = "crates/bssh-russh" } russh-sftp = "2.1.1" -clap = { version = "4.6.0", features = ["derive", "env"] } +clap = { version = "4.6.1", features = ["derive", "env"] } anyhow = "1.0.102" thiserror = "2.0.18" -tracing = "0.1.43" +tracing = "0.1.44" tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } serde = { version = "1.0.228", features = ["derive"] } serde_yaml = "0.9" @@ -57,10 +57,10 @@ nix = { version = "0.31", features = ["fs", "poll", "process", "signal", "term"] atty = "0.2.14" arrayvec = "0.7.6" smallvec = "1.15.1" -lru = "0.16.2" -uuid = { version = "1.23.0", features = ["v4"] } +lru = "0.17.0" +uuid = { version = "1.23.1", features = ["v4"] } fastrand = "2.4.1" -tokio-util = "0.7.17" +tokio-util = "0.7.18" socket2 = "0.6" shell-words = "1.1.1" libc = "0.2" diff --git a/crates/bssh-russh/Cargo.toml b/crates/bssh-russh/Cargo.toml index ab1433fd..35106428 100644 --- a/crates/bssh-russh/Cargo.toml +++ b/crates/bssh-russh/Cargo.toml @@ -26,7 +26,7 @@ serde = ["ssh-key/serde"] [dependencies] aes = "0.8" async-trait = { version = "0.1.50", optional = true } -aws-lc-rs = { version = "1.16.2", optional = true } +aws-lc-rs = { version = "1.16.3", optional = true } bitflags = "2.0" block-padding = { version = "0.3", features = ["std"] } byteorder = "1.4" @@ -41,9 +41,9 @@ delegate = "0.13" digest = "0.10" der = "0.8" des = { version = "0.8.1", optional = true } -ecdsa = "0.17.0-rc.16" +ecdsa = "0.17.0-rc.17" ed25519-dalek = { version = "3.0.0-pre.6", features = ["alloc", "rand_core", "pkcs8"] } -elliptic-curve = { version = "0.14.0-rc.30", features = ["ecdh"] } +elliptic-curve = { version = "0.14.0-rc.31", features = ["ecdh"] } enum_dispatch = "0.3.13" flate2 = { version = "1.0.15", optional = true } futures = "0.3" @@ -59,12 +59,14 @@ module-lattice = "0.2" # num-bigint 0.4.x only supports rand 0.8; upstream russh ships a fork that # adds rand 0.10 support via the `rand_0_10` feature flag. num-bigint = { package = "internal-russh-num-bigint", version = "=0.5.0", features = ["rand_0_10"] } -p256 = { version = "0.14.0-rc.8", features = ["ecdh"] } -p384 = { version = "0.14.0-rc.8", features = ["ecdh"] } -p521 = { version = "0.14.0-rc.8", features = ["ecdh"] } +p256 = { version = "0.14.0-rc.9", features = ["ecdh"] } +p384 = { version = "0.14.0-rc.9", features = ["ecdh"] } +p521 = { version = "0.14.0-rc.9", features = ["ecdh"] } pbkdf2 = "0.12" pkcs1 = { version = "0.8.0-rc.4", optional = true } -pkcs5 = "0.8.0-rc.13" +# Pinned: pkcs8 0.11.0-rc.11 still calls the rc.13-era `Parameters::recommended` API. +# Stable 0.8.0 renamed it to `generate_recommended` and breaks pkcs8. +pkcs5 = "=0.8.0-rc.13" pkcs8 = { version = "0.11.0-rc.11", features = ["encryption", "std"] } polyval = "0.7.1" rand_core = { version = "0.10.0" } @@ -79,7 +81,7 @@ spki = "0.8.0" ssh-encoding = { version = "0.2", features = ["bytes"] } subtle = "2.4" thiserror = "2.0.18" -tokio = { version = "1.51.1", features = ["io-util", "sync", "time", "rt-multi-thread", "net"] } +tokio = { version = "1.52.1", features = ["io-util", "sync", "time", "rt-multi-thread", "net"] } typenum = "1.17" universal-hash = "0.6.1" yasna = { version = "0.5.0", features = ["bit-vec", "num-bigint"], optional = true } diff --git a/crates/bssh-russh/patches/channel-write-ordering.patch b/crates/bssh-russh/patches/channel-write-ordering.patch new file mode 100644 index 00000000..12185700 --- /dev/null +++ b/crates/bssh-russh/patches/channel-write-ordering.patch @@ -0,0 +1,88 @@ +--- a/src/session.rs ++++ b/src/session.rs +@@ -449,7 +449,7 @@ + let buf0 = buf0.into(); + if let Some(channel) = self.channels.get_mut(&channel) { + assert!(channel.confirmed); +- if !channel.pending_data.is_empty() && is_rekeying { ++ if !channel.pending_data.is_empty() || is_rekeying { + channel.pending_data.push_back((buf0, None, 0)); + return Ok(()); + } +@@ -473,7 +473,7 @@ + let buf0 = buf0.into(); + if let Some(channel) = self.channels.get_mut(&channel) { + assert!(channel.confirmed); +- if !channel.pending_data.is_empty() && is_rekeying { ++ if !channel.pending_data.is_empty() || is_rekeying { + channel.pending_data.push_back((buf0, Some(ext), 0)); + return Ok(()); + } +@@ -836,4 +836,67 @@ + 1 + ); + } ++ ++ #[test] ++ fn data_queues_behind_existing_pending_data_when_not_rekeying() { ++ let channel_id = ChannelId(5); ++ let mut encrypted = test_encrypted(); ++ encrypted ++ .channels ++ .insert(channel_id, test_channel(channel_id, 42, false, false)); ++ ++ let channel = &encrypted.channels[&channel_id]; ++ let initial_pending = channel.pending_data.len(); ++ assert!(initial_pending > 0); ++ let initial_front = channel.pending_data.front().unwrap(); ++ let initial_front_data = initial_front.0.to_vec(); ++ let initial_front_ext = initial_front.1; ++ ++ encrypted ++ .data(channel_id, Bytes::from_static(b"new"), false) ++ .unwrap(); ++ ++ let channel = &encrypted.channels[&channel_id]; ++ assert_eq!(channel.pending_data.len(), initial_pending + 1); ++ assert_eq!( ++ channel.pending_data.front().unwrap().0.as_ref(), ++ initial_front_data.as_slice() ++ ); ++ assert_eq!(channel.pending_data.front().unwrap().1, initial_front_ext); ++ assert_eq!(channel.pending_data.back().unwrap().0.as_ref(), b"new"); ++ assert_eq!(channel.pending_data.back().unwrap().1, None); ++ assert!(encrypted.write.is_empty()); ++ } ++ ++ #[test] ++ fn extended_data_queues_behind_existing_pending_data_when_not_rekeying() { ++ let channel_id = ChannelId(6); ++ let ext = 1; ++ let mut encrypted = test_encrypted(); ++ encrypted ++ .channels ++ .insert(channel_id, test_channel(channel_id, 42, false, false)); ++ ++ let channel = &encrypted.channels[&channel_id]; ++ let initial_pending = channel.pending_data.len(); ++ assert!(initial_pending > 0); ++ let initial_front = channel.pending_data.front().unwrap(); ++ let initial_front_data = initial_front.0.to_vec(); ++ let initial_front_ext = initial_front.1; ++ ++ encrypted ++ .extended_data(channel_id, ext, Bytes::from_static(b"new"), false) ++ .unwrap(); ++ ++ let channel = &encrypted.channels[&channel_id]; ++ assert_eq!(channel.pending_data.len(), initial_pending + 1); ++ assert_eq!( ++ channel.pending_data.front().unwrap().0.as_ref(), ++ initial_front_data.as_slice() ++ ); ++ assert_eq!(channel.pending_data.front().unwrap().1, initial_front_ext); ++ assert_eq!(channel.pending_data.back().unwrap().0.as_ref(), b"new"); ++ assert_eq!(channel.pending_data.back().unwrap().1, Some(ext)); ++ assert!(encrypted.write.is_empty()); ++ } + } diff --git a/crates/bssh-russh/patches/handle-data-fix.patch b/crates/bssh-russh/patches/handle-data-fix.patch index d93bd8cd..49a28976 100644 --- a/crates/bssh-russh/patches/handle-data-fix.patch +++ b/crates/bssh-russh/patches/handle-data-fix.patch @@ -1,5 +1,5 @@ ---- /tmp/russh-upstream-compare/russh/src/server/session.rs 2026-04-03 13:17:42 -+++ /Users/inureyes/Development/backend.ai/bssh/crates/bssh-russh/src/server/session.rs 2026-04-03 13:20:54 +--- a/src/server/session.rs ++++ b/src/server/session.rs @@ -7,7 +7,7 @@ use log::debug; use negotiation::parse_kex_algo_list; @@ -146,3 +146,193 @@ tokio::select! { r = &mut reading => { let (stream_read, mut buffer, mut opening_cipher) = match r { +@@ -659,22 +789,20 @@ + // data from it. + self.common.alive_timeouts = 0; + } +- if self.common.received_data || sent_keepalive { +- if let (futures::future::Either::Right(ref mut sleep), Some(d)) = ( ++ if (self.common.received_data || sent_keepalive) ++ && let (futures::future::Either::Right(ref mut sleep), Some(d)) = ( + keepalive_timer.as_mut().as_pin_mut(), + self.common.config.keepalive_interval, + ) { + sleep.as_mut().reset(tokio::time::Instant::now() + d); + } +- } +- if !sent_keepalive { +- if let (futures::future::Either::Right(ref mut sleep), Some(d)) = ( ++ if !sent_keepalive ++ && let (futures::future::Either::Right(ref mut sleep), Some(d)) = ( + inactivity_timer.as_mut().as_pin_mut(), + self.common.config.inactivity_timeout, + ) { + sleep.as_mut().reset(tokio::time::Instant::now() + d); + } +- } + } + debug!("disconnected"); + // Shutdown +@@ -703,38 +831,35 @@ + } + + pub fn writable_packet_size(&self, channel: &ChannelId) -> u32 { +- if let Some(ref enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get(channel) { ++ if let Some(ref enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get(channel) { + return channel + .sender_window_size + .min(channel.sender_maximum_packet_size); + } +- } + 0 + } + + pub fn window_size(&self, channel: &ChannelId) -> u32 { +- if let Some(ref enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get(channel) { ++ if let Some(ref enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get(channel) { + return channel.sender_window_size; + } +- } + 0 + } + + pub fn max_packet_size(&self, channel: &ChannelId) -> u32 { +- if let Some(ref enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get(channel) { ++ if let Some(ref enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get(channel) { + return channel.sender_maximum_packet_size; + } +- } + 0 + } + + /// Flush the session, i.e. encrypt the pending buffer. + pub fn flush(&mut self) -> Result<(), Error> { +- if let Some(ref mut enc) = self.common.encrypted { +- if enc.flush( ++ if let Some(ref mut enc) = self.common.encrypted ++ && enc.flush( + &self.common.config.as_ref().limits, + &mut self.common.packet_writer, + )? && self.kex == SessionKexState::Idle +@@ -744,7 +869,6 @@ + self.begin_rekey()?; + } + } +- } + Ok(()) + } + +@@ -819,12 +943,11 @@ + /// cancelling). Always call this function if the request was + /// successful (it checks whether the client expects an answer). + pub fn request_success(&mut self) { +- if self.common.wants_reply { +- if let Some(ref mut enc) = self.common.encrypted { ++ if self.common.wants_reply ++ && let Some(ref mut enc) = self.common.encrypted { + self.common.wants_reply = false; + push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS)) + } +- } + } + + /// Send a "failure" reply to a global request. +@@ -839,8 +962,8 @@ + /// function if the request was successful (it checks whether the + /// client expects an answer). + pub fn channel_success(&mut self, channel: ChannelId) -> Result<(), crate::Error> { +- if let Some(ref mut enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get_mut(&channel) { ++ if let Some(ref mut enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get_mut(&channel) { + assert!(channel.confirmed); + if channel.wants_reply { + channel.wants_reply = false; +@@ -851,14 +974,13 @@ + }) + } + } +- } + Ok(()) + } + + /// Send a "failure" reply to a global request. + pub fn channel_failure(&mut self, channel: ChannelId) -> Result<(), crate::Error> { +- if let Some(ref mut enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get_mut(&channel) { ++ if let Some(ref mut enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get_mut(&channel) { + assert!(channel.confirmed); + if channel.wants_reply { + channel.wants_reply = false; +@@ -868,7 +990,6 @@ + }) + } + } +- } + Ok(()) + } + +@@ -951,8 +1072,8 @@ + channel: ChannelId, + client_can_do: bool, + ) -> Result<(), Error> { +- if let Some(ref mut enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get(&channel) { ++ if let Some(ref mut enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get(&channel) { + assert!(channel.confirmed); + push_packet!(enc.write, { + msg::CHANNEL_REQUEST.encode(&mut enc.write)?; +@@ -963,7 +1084,6 @@ + (client_can_do as u8).encode(&mut enc.write)?; + }) + } +- } + Ok(()) + } + +@@ -1003,8 +1123,8 @@ + channel: ChannelId, + exit_status: u32, + ) -> Result<(), Error> { +- if let Some(ref mut enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get(&channel) { ++ if let Some(ref mut enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get(&channel) { + assert!(channel.confirmed); + push_packet!(enc.write, { + msg::CHANNEL_REQUEST.encode(&mut enc.write)?; +@@ -1015,7 +1135,6 @@ + exit_status.encode(&mut enc.write)?; + }) + } +- } + Ok(()) + } + +@@ -1028,8 +1147,8 @@ + error_message: &str, + language_tag: &str, + ) -> Result<(), Error> { +- if let Some(ref mut enc) = self.common.encrypted { +- if let Some(channel) = enc.channels.get(&channel) { ++ if let Some(ref mut enc) = self.common.encrypted ++ && let Some(channel) = enc.channels.get(&channel) { + assert!(channel.confirmed); + push_packet!(enc.write, { + msg::CHANNEL_REQUEST.encode(&mut enc.write)?; +@@ -1043,7 +1162,6 @@ + language_tag.encode(&mut enc.write)?; + }) + } +- } + Ok(()) + } + diff --git a/crates/bssh-russh/patches/sha1-mac-exclude.patch b/crates/bssh-russh/patches/sha1-mac-exclude.patch new file mode 100644 index 00000000..a2bb1532 --- /dev/null +++ b/crates/bssh-russh/patches/sha1-mac-exclude.patch @@ -0,0 +1,36 @@ +--- a/src/negotiation.rs ++++ b/src/negotiation.rs +@@ -131,13 +131,12 @@ + cipher::AES_128_CTR, + ]; + +-const HMAC_ORDER: &[mac::Name] = &[ ++// SHA-1 MAC variants are excluded from defaults. ++const SAFE_HMAC_ORDER: &[mac::Name] = &[ + mac::HMAC_SHA512_ETM, + mac::HMAC_SHA256_ETM, + mac::HMAC_SHA512, + mac::HMAC_SHA256, +- mac::HMAC_SHA1_ETM, +- mac::HMAC_SHA1, + ]; + + const COMPRESSION_ORDER: &[compression::Name] = &[ +@@ -171,7 +170,7 @@ + Algorithm::Rsa { hash: None }, + ]), + cipher: Cow::Borrowed(CIPHER_ORDER), +- mac: Cow::Borrowed(HMAC_ORDER), ++ mac: Cow::Borrowed(SAFE_HMAC_ORDER), + compression: Cow::Borrowed(COMPRESSION_ORDER), + }; + +@@ -179,7 +178,7 @@ + kex: Cow::Borrowed(SAFE_KEX_ORDER), + key: Preferred::DEFAULT.key, + cipher: Cow::Borrowed(CIPHER_ORDER), +- mac: Cow::Borrowed(HMAC_ORDER), ++ mac: Cow::Borrowed(SAFE_HMAC_ORDER), + compression: Cow::Borrowed(COMPRESSION_ORDER), + }; + } diff --git a/crates/bssh-russh/src/negotiation.rs b/crates/bssh-russh/src/negotiation.rs index 6ef741ff..c1d97105 100644 --- a/crates/bssh-russh/src/negotiation.rs +++ b/crates/bssh-russh/src/negotiation.rs @@ -131,13 +131,12 @@ const CIPHER_ORDER: &[cipher::Name] = &[ cipher::AES_128_CTR, ]; -const HMAC_ORDER: &[mac::Name] = &[ +// SHA-1 MAC variants are excluded from defaults. +const SAFE_HMAC_ORDER: &[mac::Name] = &[ mac::HMAC_SHA512_ETM, mac::HMAC_SHA256_ETM, mac::HMAC_SHA512, mac::HMAC_SHA256, - mac::HMAC_SHA1_ETM, - mac::HMAC_SHA1, ]; const COMPRESSION_ORDER: &[compression::Name] = &[ @@ -171,7 +170,7 @@ impl Preferred { Algorithm::Rsa { hash: None }, ]), cipher: Cow::Borrowed(CIPHER_ORDER), - mac: Cow::Borrowed(HMAC_ORDER), + mac: Cow::Borrowed(SAFE_HMAC_ORDER), compression: Cow::Borrowed(COMPRESSION_ORDER), }; @@ -179,7 +178,7 @@ impl Preferred { kex: Cow::Borrowed(SAFE_KEX_ORDER), key: Preferred::DEFAULT.key, cipher: Cow::Borrowed(CIPHER_ORDER), - mac: Cow::Borrowed(HMAC_ORDER), + mac: Cow::Borrowed(SAFE_HMAC_ORDER), compression: Cow::Borrowed(COMPRESSION_ORDER), }; } diff --git a/crates/bssh-russh/src/session.rs b/crates/bssh-russh/src/session.rs index 50a82bdb..d9cf65ff 100644 --- a/crates/bssh-russh/src/session.rs +++ b/crates/bssh-russh/src/session.rs @@ -449,7 +449,7 @@ impl Encrypted { let buf0 = buf0.into(); if let Some(channel) = self.channels.get_mut(&channel) { assert!(channel.confirmed); - if !channel.pending_data.is_empty() && is_rekeying { + if !channel.pending_data.is_empty() || is_rekeying { channel.pending_data.push_back((buf0, None, 0)); return Ok(()); } @@ -473,7 +473,7 @@ impl Encrypted { let buf0 = buf0.into(); if let Some(channel) = self.channels.get_mut(&channel) { assert!(channel.confirmed); - if !channel.pending_data.is_empty() && is_rekeying { + if !channel.pending_data.is_empty() || is_rekeying { channel.pending_data.push_back((buf0, Some(ext), 0)); return Ok(()); } @@ -836,4 +836,67 @@ mod tests { 1 ); } + + #[test] + fn data_queues_behind_existing_pending_data_when_not_rekeying() { + let channel_id = ChannelId(5); + let mut encrypted = test_encrypted(); + encrypted + .channels + .insert(channel_id, test_channel(channel_id, 42, false, false)); + + let channel = &encrypted.channels[&channel_id]; + let initial_pending = channel.pending_data.len(); + assert!(initial_pending > 0); + let initial_front = channel.pending_data.front().unwrap(); + let initial_front_data = initial_front.0.to_vec(); + let initial_front_ext = initial_front.1; + + encrypted + .data(channel_id, Bytes::from_static(b"new"), false) + .unwrap(); + + let channel = &encrypted.channels[&channel_id]; + assert_eq!(channel.pending_data.len(), initial_pending + 1); + assert_eq!( + channel.pending_data.front().unwrap().0.as_ref(), + initial_front_data.as_slice() + ); + assert_eq!(channel.pending_data.front().unwrap().1, initial_front_ext); + assert_eq!(channel.pending_data.back().unwrap().0.as_ref(), b"new"); + assert_eq!(channel.pending_data.back().unwrap().1, None); + assert!(encrypted.write.is_empty()); + } + + #[test] + fn extended_data_queues_behind_existing_pending_data_when_not_rekeying() { + let channel_id = ChannelId(6); + let ext = 1; + let mut encrypted = test_encrypted(); + encrypted + .channels + .insert(channel_id, test_channel(channel_id, 42, false, false)); + + let channel = &encrypted.channels[&channel_id]; + let initial_pending = channel.pending_data.len(); + assert!(initial_pending > 0); + let initial_front = channel.pending_data.front().unwrap(); + let initial_front_data = initial_front.0.to_vec(); + let initial_front_ext = initial_front.1; + + encrypted + .extended_data(channel_id, ext, Bytes::from_static(b"new"), false) + .unwrap(); + + let channel = &encrypted.channels[&channel_id]; + assert_eq!(channel.pending_data.len(), initial_pending + 1); + assert_eq!( + channel.pending_data.front().unwrap().0.as_ref(), + initial_front_data.as_slice() + ); + assert_eq!(channel.pending_data.front().unwrap().1, initial_front_ext); + assert_eq!(channel.pending_data.back().unwrap().0.as_ref(), b"new"); + assert_eq!(channel.pending_data.back().unwrap().1, Some(ext)); + assert!(encrypted.write.is_empty()); + } } diff --git a/crates/bssh-russh/sync-upstream.sh b/crates/bssh-russh/sync-upstream.sh index fdaa28e4..9b798a7e 100755 --- a/crates/bssh-russh/sync-upstream.sh +++ b/crates/bssh-russh/sync-upstream.sh @@ -10,7 +10,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" UPSTREAM_URL="https://github.com/warp-tech/russh.git" TEMP_DIR="/tmp/russh-sync-$$" -PATCH_FILE="$SCRIPT_DIR/patches/handle-data-fix.patch" +PATCH_DIR="$SCRIPT_DIR/patches" # Colors for output RED='\033[0;31m' @@ -82,27 +82,50 @@ if [ "$VERSION" != "main" ]; then fi # Apply our patches +# +# Each *.patch file under patches/ is a forward-port of a fix that is either +# unique to this fork (e.g. handle-data-fix.patch) or a cherry-pick of an +# unreleased upstream commit. For cherry-picks, once upstream releases a +# version that includes the change, the next sync will automatically detect +# it (reverse-apply succeeds) and skip the patch — at which point the patch +# file should be deleted. log_info "Applying patches..." -if [ -f "$PATCH_FILE" ]; then - if patch -p1 --dry-run < "$PATCH_FILE" > /dev/null 2>&1; then - patch -p1 < "$PATCH_FILE" - log_info "Applied handle-data-fix.patch" +shopt -s nullglob +PATCH_FILES=("$PATCH_DIR"/*.patch) +shopt -u nullglob + +if [ ${#PATCH_FILES[@]} -eq 0 ]; then + log_warn "No patch files found in $PATCH_DIR/" +fi + +OBSOLETE_PATCHES=() + +for PATCH_FILE in "${PATCH_FILES[@]}"; do + PATCH_NAME=$(basename "$PATCH_FILE") + + # If reverse-apply succeeds, the change is already in upstream — skip and + # mark the patch as obsolete so the maintainer can delete it. + if patch -p1 -R --dry-run --silent < "$PATCH_FILE" > /dev/null 2>&1; then + log_info "Skipping $PATCH_NAME — already present in upstream (consider deleting this patch file)" + OBSOLETE_PATCHES+=("$PATCH_NAME") + continue + fi + + if patch -p1 --dry-run --silent < "$PATCH_FILE" > /dev/null 2>&1; then + patch -p1 --silent < "$PATCH_FILE" + log_info "Applied $PATCH_NAME" else - log_warn "Patch may not apply cleanly, attempting with fuzz..." + log_warn "$PATCH_NAME may not apply cleanly, attempting with fuzz..." if patch -p1 --fuzz=3 < "$PATCH_FILE"; then - log_warn "Patch applied with fuzz - please verify manually" + log_warn "$PATCH_NAME applied with fuzz - please verify manually" else - log_error "Failed to apply patch. Manual intervention required." + log_error "Failed to apply $PATCH_NAME. Manual intervention required." log_error "Patch file: $PATCH_FILE" exit 1 fi fi -else - log_error "Patch file not found: $PATCH_FILE" - log_error "Please create the patch file first using: ./create-patch.sh" - exit 1 -fi +done # Verify build log_info "Verifying build..." @@ -116,6 +139,16 @@ fi log_info "Sync complete!" log_info "Upstream version: $VERSION ($COMMIT_HASH)" + +if [ ${#OBSOLETE_PATCHES[@]} -gt 0 ]; then + log_info "" + log_warn "The following patches are now obsolete (already in upstream $VERSION):" + for p in "${OBSOLETE_PATCHES[@]}"; do + log_warn " - $p" + done + log_warn "Delete these patch files: rm $PATCH_DIR/{$(IFS=,; echo "${OBSOLETE_PATCHES[*]}")}" +fi + log_info "" log_info "Next steps:" log_info " 1. Review changes: git diff crates/bssh-russh/" diff --git a/src/hostlist/expander.rs b/src/hostlist/expander.rs index 46022d98..c310c838 100644 --- a/src/hostlist/expander.rs +++ b/src/hostlist/expander.rs @@ -223,10 +223,8 @@ fn parse_port_suffix(spec: &str) -> Result<(&str, Option), HostlistError for (i, ch) in spec.char_indices() { match ch { '[' => bracket_depth += 1, - ']' => { - if bracket_depth > 0 { - bracket_depth -= 1; - } + ']' if bracket_depth > 0 => { + bracket_depth -= 1; } ':' if bracket_depth == 0 => { last_colon_outside = Some(i); diff --git a/src/server/sftp.rs b/src/server/sftp.rs index ea80e278..3820acdd 100644 --- a/src/server/sftp.rs +++ b/src/server/sftp.rs @@ -1191,18 +1191,18 @@ impl russh_sftp::server::Handler for SftpHandler { match component { Component::Normal(c) => resolved.push(c), Component::CurDir => {} - Component::ParentDir => { - if !resolved.pop() || !resolved.starts_with(&root_dir) { - tracing::warn!( - user = %user, - link = %link_path.display(), - target = %targetpath, - "Relative symlink target escapes root" - ); - return Err(SftpError::permission_denied( - "Symlink target must be within root directory", - )); - } + Component::ParentDir + if (!resolved.pop() || !resolved.starts_with(&root_dir)) => + { + tracing::warn!( + user = %user, + link = %link_path.display(), + target = %targetpath, + "Relative symlink target escapes root" + ); + return Err(SftpError::permission_denied( + "Symlink target must be within root directory", + )); } _ => {} } diff --git a/src/ssh/ssh_config/path.rs b/src/ssh/ssh_config/path.rs index b0597802..61a036b5 100644 --- a/src/ssh/ssh_config/path.rs +++ b/src/ssh/ssh_config/path.rs @@ -370,24 +370,22 @@ fn sanitize_environment_value(value: &str, var_name: &str) -> Result { // Additional validation for specific variable types match var_name { - "SSH_AUTH_SOCK" => { + "SSH_AUTH_SOCK" // Should be a socket path, typically in /tmp or similar - if !value.starts_with('/') && !value.starts_with("./") { + if !value.starts_with('/') && !value.starts_with("./") => { tracing::warn!( "SSH_AUTH_SOCK '{}' does not look like a typical socket path", value ); } - } - "HOME" => { + "HOME" // Should be an absolute path to a directory - if !value.starts_with('/') && !value.contains(":\\") { + if !value.starts_with('/') && !value.contains(":\\") => { tracing::warn!( "HOME '{}' does not look like a typical home directory path", value ); } - } _ => {} } diff --git a/src/ssh/tokio_client/channel_manager.rs b/src/ssh/tokio_client/channel_manager.rs index cad2e98a..90480c4d 100644 --- a/src/ssh/tokio_client/channel_manager.rs +++ b/src/ssh/tokio_client/channel_manager.rs @@ -266,26 +266,24 @@ impl Client { } } } - russh::ChannelMsg::ExtendedData { ref data, ext } => { - if ext == 1 { - // Handle backpressure for stderr as well - match sender.try_send(CommandOutput::StdErr(data.clone())) { - Ok(_) => {} - Err(tokio::sync::mpsc::error::TrySendError::Full(output)) => { - // Channel is full - apply backpressure by waiting - tracing::trace!("Channel full, applying backpressure for stderr"); - if sender.send(output).await.is_err() { - // Receiver dropped - stop processing - tracing::debug!("Receiver dropped, stopping stderr processing"); - break; - } - } - Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => { + russh::ChannelMsg::ExtendedData { ref data, ext } if ext == 1 => { + // Handle backpressure for stderr as well + match sender.try_send(CommandOutput::StdErr(data.clone())) { + Ok(_) => {} + Err(tokio::sync::mpsc::error::TrySendError::Full(output)) => { + // Channel is full - apply backpressure by waiting + tracing::trace!("Channel full, applying backpressure for stderr"); + if sender.send(output).await.is_err() { // Receiver dropped - stop processing - tracing::debug!("Channel closed, stopping stderr processing"); + tracing::debug!("Receiver dropped, stopping stderr processing"); break; } } + Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => { + // Receiver dropped - stop processing + tracing::debug!("Channel closed, stopping stderr processing"); + break; + } } } @@ -448,80 +446,77 @@ impl Client { return Ok(1); } } - russh::ChannelMsg::ExtendedData { ref data, ext } => { - if ext == 1 { - // Stderr - also check for sudo prompts - let text = String::from_utf8_lossy(data); - accumulated_output.push_str(&text); - - // Enforce buffer size limit to prevent unbounded memory growth - if accumulated_output.len() > MAX_SUDO_PROMPT_BUFFER_SIZE { - // Keep only the last MAX_SUDO_PROMPT_BUFFER_SIZE bytes - let truncate_at = - accumulated_output.len() - MAX_SUDO_PROMPT_BUFFER_SIZE; - accumulated_output = accumulated_output[truncate_at..].to_string(); - tracing::debug!( - "Sudo prompt buffer exceeded limit (stderr), truncated to {} bytes", - MAX_SUDO_PROMPT_BUFFER_SIZE - ); - } + russh::ChannelMsg::ExtendedData { ref data, ext } if ext == 1 => { + // Stderr - also check for sudo prompts + let text = String::from_utf8_lossy(data); + accumulated_output.push_str(&text); - match sender.try_send(CommandOutput::StdErr(data.clone())) { - Ok(_) => {} - Err(tokio::sync::mpsc::error::TrySendError::Full(output)) => { - tracing::trace!("Channel full, applying backpressure for stderr"); - if sender.send(output).await.is_err() { - tracing::debug!("Receiver dropped, stopping stderr processing"); - break; - } - } - Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => { - tracing::debug!("Channel closed, stopping stderr processing"); + // Enforce buffer size limit to prevent unbounded memory growth + if accumulated_output.len() > MAX_SUDO_PROMPT_BUFFER_SIZE { + // Keep only the last MAX_SUDO_PROMPT_BUFFER_SIZE bytes + let truncate_at = accumulated_output.len() - MAX_SUDO_PROMPT_BUFFER_SIZE; + accumulated_output = accumulated_output[truncate_at..].to_string(); + tracing::debug!( + "Sudo prompt buffer exceeded limit (stderr), truncated to {} bytes", + MAX_SUDO_PROMPT_BUFFER_SIZE + ); + } + + match sender.try_send(CommandOutput::StdErr(data.clone())) { + Ok(_) => {} + Err(tokio::sync::mpsc::error::TrySendError::Full(output)) => { + tracing::trace!("Channel full, applying backpressure for stderr"); + if sender.send(output).await.is_err() { + tracing::debug!("Receiver dropped, stopping stderr processing"); break; } } + Err(tokio::sync::mpsc::error::TrySendError::Closed(_)) => { + tracing::debug!("Channel closed, stopping stderr processing"); + break; + } + } - // Check if we need to send the password (sudo can prompt on stderr) - if password_send_count < MAX_SUDO_PASSWORD_SENDS - && contains_sudo_prompt(&accumulated_output) - { - password_send_count += 1; - tracing::debug!( - "Sudo prompt detected on stderr, sending password (attempt {}/{})", - password_send_count, - MAX_SUDO_PASSWORD_SENDS - ); - let password_data = sudo_password.with_newline(); - if let Err(e) = channel.data(&password_data[..]).await { - tracing::error!("Failed to send sudo password: {}", e); - return Err(super::Error::SshError(e)); - } - accumulated_output.clear(); + // Check if we need to send the password (sudo can prompt on stderr) + if password_send_count < MAX_SUDO_PASSWORD_SENDS + && contains_sudo_prompt(&accumulated_output) + { + password_send_count += 1; + tracing::debug!( + "Sudo prompt detected on stderr, sending password (attempt {}/{})", + password_send_count, + MAX_SUDO_PASSWORD_SENDS + ); + let password_data = sudo_password.with_newline(); + if let Err(e) = channel.data(&password_data[..]).await { + tracing::error!("Failed to send sudo password: {}", e); + return Err(super::Error::SshError(e)); } + accumulated_output.clear(); + } - // Check for sudo failure - if password_send_count > 0 && contains_sudo_failure(&accumulated_output) { - tracing::debug!( - "Sudo authentication failed on stderr after {} attempt(s), closing channel", - password_send_count - ); - // Send error message to stderr so user can see why it failed - let error_msg = format!( - "\n[bssh] Sudo authentication failed after {} attempt(s). \ + // Check for sudo failure + if password_send_count > 0 && contains_sudo_failure(&accumulated_output) { + tracing::debug!( + "Sudo authentication failed on stderr after {} attempt(s), closing channel", + password_send_count + ); + // Send error message to stderr so user can see why it failed + let error_msg = format!( + "\n[bssh] Sudo authentication failed after {} attempt(s). \ Please verify your sudo password is correct.\n", - password_send_count - ); - let _ = sender - .send(CommandOutput::StdErr(Bytes::from(error_msg.into_bytes()))) - .await; - // Send exit code 1 to indicate failure to the stream - let _ = sender.send(CommandOutput::ExitCode(1)).await; - // Close the channel and return failure exit code - let _ = channel.eof().await; - let _ = channel.close().await; - drop(sender); - return Ok(1); - } + password_send_count + ); + let _ = sender + .send(CommandOutput::StdErr(Bytes::from(error_msg.into_bytes()))) + .await; + // Send exit code 1 to indicate failure to the stream + let _ = sender.send(CommandOutput::ExitCode(1)).await; + // Close the channel and return failure exit code + let _ = channel.eof().await; + let _ = channel.close().await; + drop(sender); + return Ok(1); } } russh::ChannelMsg::ExitStatus { exit_status } => result = Some(exit_status), diff --git a/src/ui/tui/event.rs b/src/ui/tui/event.rs index 19f391f0..c9c16a36 100644 --- a/src/ui/tui/event.rs +++ b/src/ui/tui/event.rs @@ -113,19 +113,15 @@ fn handle_summary_keys(app: &mut TuiApp, key: KeyEvent, num_nodes: usize) { } } // 's' for split view - KeyCode::Char('s') => { - if num_nodes >= 2 { - // Default to first 4 nodes - let indices: Vec = (0..num_nodes.min(4)).collect(); - app.show_split(indices, num_nodes); - } + KeyCode::Char('s') if num_nodes >= 2 => { + // Default to first 4 nodes + let indices: Vec = (0..num_nodes.min(4)).collect(); + app.show_split(indices, num_nodes); } // 'd' for diff view - KeyCode::Char('d') => { - if num_nodes >= 2 { - // Default to first 2 nodes - app.show_diff(0, 1, num_nodes); - } + KeyCode::Char('d') if num_nodes >= 2 => { + // Default to first 2 nodes + app.show_diff(0, 1, num_nodes); } _ => {} }