From 272febf209f9a082558fe74a55fc2f7376e56d0a Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 22:03:47 +0200 Subject: [PATCH 01/19] chore(deps): update semver-compatible dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo update — patch/minor bumps within existing ranges: tokio 1.52.3, russh 0.60.3, russh-sftp 2.3.0, reqwest 0.13.4, tower-http 0.6.11, aws-config 1.8.17, aws-sdk-ssm 1.111, clap_complete 4.6.5, const-hex 1.19.1, mimalloc 0.1.52, serde_json 1.0.150, tokio-socks 0.5.3, uuid 1.23.2, filetime 0.2.29 (dev). --- Cargo.lock | 910 ++++++++++++++++++++++++----------------------------- 1 file changed, 419 insertions(+), 491 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a7eecb..01b87e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ version = "0.6.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b657e772794c6b04730ea897b66a058ccd866c16d1967da05eeeecec39043fe" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout 0.2.2", ] @@ -41,11 +41,11 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", "cpubits", "cpufeatures 0.3.0", ] @@ -71,9 +71,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22c0c90bbe8d4f77c3ca9ddabe41a1f8382d6fc1f7cea89459d0f320371f972" dependencies = [ "aead 0.6.0-rc.10", - "aes 0.9.0", - "cipher 0.5.1", - "ctr 0.10.0", + "aes 0.9.1", + "cipher 0.5.2", + "ctr 0.10.1", "ghash 0.6.0", "subtle", ] @@ -85,7 +85,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "const-random", "getrandom 0.3.4", "once_cell", "version_check", @@ -127,9 +126,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.12.15" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92570a3f9c98e7e84df84b71d0965ac99b1871fcd75a3773a3bd1bad13f64cf7" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" dependencies = [ "anstyle", "memchr", @@ -192,6 +191,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "argon2" version = "0.5.3" @@ -229,15 +237,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-config" -version = "1.8.16" +version = "1.8.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f156acdd2cf55f5aa53ee416c4ac851cf1222694506c0b1f78c85695e9ca9d" +checksum = "517aa062d8bd9015ee23d6daa5e1c1372328412fdae4e6c4c1be9b69c6ad37a2" dependencies = [ "aws-credential-types", "aws-runtime", @@ -249,12 +257,13 @@ dependencies = [ "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", "fastrand", "hex", - "http 1.4.0", + "http 1.4.1", "sha1 0.10.6", "time", "tokio", @@ -277,9 +286,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -288,9 +297,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -300,9 +309,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.7.3" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcd93c82209ac7413532388067dce79be5a8780c1786e5fae3df22e4dee2864" +checksum = "77ed8e8c52d2dc2390ad9f15647fe663f71e9780b4262c190fbb823a32721566" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -315,7 +324,7 @@ dependencies = [ "bytes", "bytes-utils", "fastrand", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "percent-encoding", "pin-project-lite", @@ -325,9 +334,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssm" -version = "1.109.0" +version = "1.111.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f4bdbeea2c7d18632093cd158644902f1e91ae025a3f68afaa449f620ae658" +checksum = "b90a76d2607bd7fdb9a131362fe4410bd676528ef0bc41a8fe2c6429c8ad1b87" dependencies = [ "aws-credential-types", "aws-runtime", @@ -342,16 +351,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sso" -version = "1.98.0" +version = "1.100.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69c77aafa20460c68b6b3213c84f6423b6e76dbf89accd3e1789a686ffd9489" +checksum = "bee2719d4a5e5e147bb9e9b77490df6ece750df1094968aa857b09b618a1881a" dependencies = [ "aws-credential-types", "aws-runtime", @@ -366,17 +375,18 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-ssooidc" -version = "1.100.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7e7b09346d5ca22a2a08267555843a6a0127fb20d8964cb6ecfb8fdb190225" +checksum = "b30d254992d56ef19f430396e5765b11e0f5bd21a7a557cb12fca1c8c18b9636" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -390,16 +400,16 @@ dependencies = [ "bytes", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "regex-lite", "tracing", ] [[package]] name = "aws-sdk-sts" -version = "1.103.0" +version = "1.105.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2249b81a2e73a8027c41c378463a81ec39b8510f184f2caab87de912af0f49b" +checksum = "59f4f8065fe615dbed9096458ba98dda6d641553ffd5aedd27e37e65211aca9f" dependencies = [ "aws-credential-types", "aws-runtime", @@ -415,16 +425,16 @@ dependencies = [ "aws-types", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "regex-lite", "tracing", ] [[package]] name = "aws-sigv4" -version = "1.4.3" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68dc0b907359b120170613b5c09ccc61304eac3998ff6274b97d93ee6490115a" +checksum = "b7083fb918b38474ac65ffbf8a69fc8792d36879f4ac5f1667b43aec61efe9a5" dependencies = [ "aws-credential-types", "aws-smithy-http", @@ -435,7 +445,7 @@ dependencies = [ "hex", "hmac 0.13.0", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "percent-encoding", "sha2 0.11.0", "time", @@ -465,7 +475,7 @@ dependencies = [ "bytes-utils", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", "percent-encoding", @@ -484,18 +494,18 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "h2 0.3.27", - "h2 0.4.13", + "h2 0.4.14", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "http-body 0.4.6", "hyper 0.14.32", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-rustls 0.24.2", "hyper-rustls 0.27.9", "hyper-util", "pin-project-lite", "rustls 0.21.12", - "rustls 0.23.39", + "rustls 0.23.40", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -506,10 +516,12 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.5" +version = "0.62.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" +checksum = "517089205f18ab4adc5a3e02888cb139bbbbb2e168eac9f396216925d1fbeaf5" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", ] @@ -534,20 +546,21 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0504b1ab12debb5959e5165ee5fe97dd387e7aa7ea6a477bfd7635dfe769a4f5" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-http-client", "aws-smithy-observability", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "bytes", "fastrand", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -559,16 +572,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" +checksum = "dc117c179ecf39a62a0a3f49f600e9ac26a7ad7dd172177999f83933af776c32" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", "aws-smithy-types", "bytes", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "pin-project-lite", "tokio", "tracing", @@ -586,18 +599,29 @@ dependencies = [ "syn", ] +[[package]] +name = "aws-smithy-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.1", +] + [[package]] name = "aws-smithy-types" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" +checksum = "056b66dbce2f81cc0c1e2b05bb402eb58f8a3530479d650efadd5bbae9a4050b" dependencies = [ "base64-simd", "bytes", "bytes-utils", "futures-core", "http 0.2.12", - "http 1.4.0", + "http 1.4.1", "http-body 0.4.6", "http-body 1.0.1", "http-body-util", @@ -623,13 +647,14 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.15" +version = "1.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" +checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" dependencies = [ "aws-credential-types", "aws-smithy-async", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "rustc_version", "tracing", @@ -645,10 +670,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-util", "itoa", "matchit", @@ -676,7 +701,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", "mime", @@ -824,9 +849,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -867,18 +892,18 @@ dependencies = [ [[package]] name = "cbc" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98db6aeaef0eeef2c1e3ce9a27b739218825dae116076352ac3777076aa22225" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] name = "cc" -version = "1.2.61" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -886,12 +911,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -979,12 +998,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.0", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout 0.2.2", ] @@ -1012,9 +1031,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.2" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" dependencies = [ "clap", ] @@ -1048,9 +1067,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f88a43d011fc4a6876cb7344703e297c71dda42494fee094d5f7c76bf13f746" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "colorchoice" @@ -1081,9 +1100,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.18.1" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" dependencies = [ "cfg-if", "cpufeatures 0.2.17", @@ -1103,26 +1122,6 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom 0.2.17", - "once_cell", - "tiny-keccak", -] - [[package]] name = "core-foundation" version = "0.10.1" @@ -1141,9 +1140,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpubits" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" @@ -1240,9 +1239,9 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" -version = "0.7.3" +version = "0.7.0-rc.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" +checksum = "96dacf199529fb801ae62a9aafdc01b189e9504c0d1ee1512a4c16bcd8666a93" dependencies = [ "cpubits", "ctutils", @@ -1267,9 +1266,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "getrandom 0.4.2", "hybrid-array", @@ -1278,9 +1277,9 @@ dependencies = [ [[package]] name = "crypto-primes" -version = "0.7.0" +version = "0.7.0-pre.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" +checksum = "6081ce8b60c0e533e2bba42771b94eb6149052115f4179744d5779883dc98583" dependencies = [ "crypto-bigint", "libm", @@ -1298,11 +1297,11 @@ dependencies = [ [[package]] name = "ctr" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17469f8eb9bdbfad10f71f4cfddfd38b01143520c0e717d8796ccb4d44d44e42" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] @@ -1324,7 +1323,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.11.2", + "digest 0.11.3", "fiat-crypto", "rustc_version", "subtle", @@ -1342,6 +1341,20 @@ dependencies = [ "syn", ] +[[package]] +name = "dashmap" +version = "6.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.11.0" @@ -1424,13 +1437,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] @@ -1457,9 +1470,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1480,16 +1493,16 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecdsa" -version = "0.17.0-rc.17" +version = "0.17.0-rc.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4bf51f0534ed6e59a0f2f26272b64ba55c470133f8424c2adfd1c4d59d9988" +checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829" dependencies = [ "der 0.8.0", - "digest 0.11.2", + "digest 0.11.3", "elliptic-curve", "rfc6979", - "signature 3.0.0-rc.10", - "spki 0.8.0", + "signature 3.0.0", + "spki 0.8.0-rc.4", "zeroize", ] @@ -1500,7 +1513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" dependencies = [ "pkcs8 0.11.0-rc.11", - "signature 3.0.0-rc.10", + "signature 3.0.0", ] [[package]] @@ -1514,27 +1527,27 @@ dependencies = [ "rand_core 0.10.1", "serde", "sha2 0.11.0", - "signature 3.0.0-rc.10", + "signature 3.0.0", "subtle", "zeroize", ] [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" -version = "0.14.0-rc.31" +version = "0.14.0-rc.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b148a81cede8f4023248f980cffdf7611c46f2add469c6980e815b7c5b764ba5" +checksum = "bde7860544606d222fd6bd6d9f9a0773321bf78072a637e1d560a058c0031978" dependencies = [ "base16ct", "crypto-bigint", - "crypto-common 0.2.1", - "digest 0.11.2", + "crypto-common 0.2.2", + "digest 0.11.3", "hkdf", "hybrid-array", "once_cell", @@ -1614,13 +1627,12 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -1639,18 +1651,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flurry" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf5efcf77a4da27927d3ab0509dec5b0954bb3bc59da5a1de9e52642ebd4cdf9" -dependencies = [ - "ahash", - "num_cpus", - "parking_lot", - "seize", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1787,9 +1787,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "1.3.5" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" +checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351" dependencies = [ "generic-array 0.14.7", "rustversion", @@ -1856,6 +1856,18 @@ dependencies = [ "polyval 0.7.1", ] +[[package]] +name = "gloo-timers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.3.27" @@ -1877,16 +1889,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.4.1", "indexmap", "slab", "tokio", @@ -1905,6 +1917,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.5" @@ -1916,9 +1934,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1926,12 +1944,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -1974,7 +1986,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -1990,9 +2002,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -2016,7 +2028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.1", ] [[package]] @@ -2027,7 +2039,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "pin-project-lite", ] @@ -2046,9 +2058,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "ctutils", "subtle", @@ -2082,16 +2094,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2 0.4.13", - "http 1.4.0", + "h2 0.4.14", + "http 1.4.1", "http-body 1.0.1", "httparse", "httpdate", @@ -2123,11 +2135,11 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", - "hyper 1.9.0", + "http 1.4.1", + "hyper 1.10.1", "hyper-util", "log", - "rustls 0.23.39", + "rustls 0.23.40", "rustls-native-certs", "tokio", "tokio-rustls 0.26.4", @@ -2140,7 +2152,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.9.0", + "hyper 1.10.1", "hyper-util", "pin-project-lite", "tokio", @@ -2157,14 +2169,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", - "hyper 1.9.0", + "hyper 1.10.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.6.4", "tokio", "tower-service", "tracing", @@ -2295,9 +2307,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -2310,7 +2322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -2386,11 +2398,11 @@ dependencies = [ "p384", "p521", "rand_core 0.10.1", - "rsa 0.10.0-rc.17", + "rsa 0.10.0-rc.16", "sec1", "sha1 0.11.0", "sha2 0.11.0", - "signature 3.0.0-rc.10", + "signature 3.0.0", "ssh-cipher", "ssh-encoding", "subtle", @@ -2434,16 +2446,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -2524,9 +2526,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -2534,14 +2536,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", ] [[package]] name = "jiff-static" -version = "0.2.24" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -2565,27 +2567,32 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys 0.3.1", + "jni-macros", + "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror 2.0.18", "walkdir", - "windows-sys 0.45.0", + "windows-link", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "jni-sys 0.4.1", + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] @@ -2619,9 +2626,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -2685,7 +2692,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "rand_core 0.10.1", ] @@ -2701,11 +2708,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.1", "libc", ] @@ -2730,10 +2737,10 @@ dependencies = [ "bytes", "either", "futures", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-rustls 0.27.9", "hyper-timeout", "hyper-util", @@ -2742,7 +2749,7 @@ dependencies = [ "k8s-openapi", "kube-core", "pem", - "rustls 0.23.39", + "rustls 0.23.40", "secrecy", "serde", "serde_json", @@ -2764,7 +2771,7 @@ checksum = "f126d2db7a8b532ec1d839ece2a71e2485dc3bbca6cc3c3f929becaa810e719e" dependencies = [ "derive_more", "form_urlencoded", - "http 1.4.0", + "http 1.4.1", "jiff", "k8s-openapi", "serde", @@ -2802,23 +2809,20 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.47" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" dependencies = [ "cc", ] [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.11.1", "libc", - "plain", - "redox_syscall 0.7.4", ] [[package]] @@ -2844,9 +2848,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "lru-slab" @@ -2915,7 +2919,7 @@ dependencies = [ "psrp-rs", "rayon", "regex", - "reqwest 0.13.1", + "reqwest 0.13.4", "russh", "russh-sftp", "serde", @@ -2957,7 +2961,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2966,7 +2970,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd76fb0fd6b2e4be62a73f8e0858ca97f81babcb1af322dcaca196f735f17f80" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2977,15 +2981,15 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mimalloc" -version = "0.1.50" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" dependencies = [ "libmimalloc-sys", ] @@ -3008,9 +3012,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -3033,9 +3037,9 @@ dependencies = [ [[package]] name = "ml-kem" -version = "0.3.0-rc.2" +version = "0.3.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04437cb1a66c0b78740927b76cc61f218344b9f6ef3dd430e283274a718ef0e9" +checksum = "8198b5db27ac9773534c371751a59dc18aec8b80aa141e69abfdd1dec2e3f78c" dependencies = [ "hybrid-array", "kem", @@ -3046,9 +3050,9 @@ dependencies = [ [[package]] name = "module-lattice" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164eb3faeaecbd14b0b2a917c1b4d0c035097a9c559b0bed85c2cdd032bc8faa" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" dependencies = [ "ctutils", "hybrid-array", @@ -3080,9 +3084,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags 2.11.1", "cfg-if", @@ -3161,9 +3165,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -3195,16 +3199,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "once_cell" version = "1.21.4" @@ -3257,7 +3251,7 @@ checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" dependencies = [ "async-trait", "bytes", - "http 1.4.0", + "http 1.4.1", "opentelemetry", "reqwest 0.12.28", ] @@ -3268,7 +3262,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" dependencies = [ - "http 1.4.0", + "http 1.4.1", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -3334,9 +3328,9 @@ checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "p256" -version = "0.14.0-rc.9" +version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a" +checksum = "018bfbb86e05fd70a83e985921241035ee09fcd369c4a2c3680b389a01d2ad28" dependencies = [ "ecdsa", "elliptic-curve", @@ -3347,9 +3341,9 @@ dependencies = [ [[package]] name = "p384" -version = "0.14.0-rc.9" +version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03" +checksum = "8c91df688211f5957dbe2ab599dcbcaade8d6d3cdc15c5b350d350d7d07ce423" dependencies = [ "ecdsa", "elliptic-curve", @@ -3361,9 +3355,9 @@ dependencies = [ [[package]] name = "p521" -version = "0.14.0-rc.9" +version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7" +checksum = "de6cd9451de522549d36cc78a1b45a699a3d55a872e8ea0c8f0318e502d99e2c" dependencies = [ "base16ct", "ecdsa", @@ -3385,33 +3379,24 @@ dependencies = [ [[package]] name = "pageant" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b537f975f6d8dcf48db368d7ec209d583b015713b5df0f5d92d2631e4ff5595" +checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5" dependencies = [ + "base16ct", "byteorder", "bytes", "delegate", "futures", "log", - "rand 0.8.6", - "sha2 0.10.9", - "thiserror 1.0.69", + "rand 0.10.1", + "sha2 0.11.0", + "thiserror 2.0.18", "tokio", "windows", "windows-strings", ] -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - [[package]] name = "parking_lot_core" version = "0.9.12" @@ -3420,7 +3405,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -3452,7 +3437,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", "hmac 0.13.0", ] @@ -3535,18 +3520,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -3583,7 +3568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ "der 0.8.0", - "spki 0.8.0", + "spki 0.8.0-rc.4", ] [[package]] @@ -3592,15 +3577,15 @@ version = "0.8.0-rc.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5a777c6e26664bc9504b3ce3f6133f8f20d9071f130a4f9fcbd3186959d8dd6" dependencies = [ - "aes 0.9.0", + "aes 0.9.1", "aes-gcm 0.11.0-rc.3", - "cbc 0.2.0", + "cbc 0.2.1", "der 0.8.0", "pbkdf2 0.13.0", "rand_core 0.10.1", "scrypt", "sha2 0.11.0", - "spki 0.8.0", + "spki 0.8.0-rc.4", ] [[package]] @@ -3622,15 +3607,9 @@ dependencies = [ "der 0.8.0", "pkcs5", "rand_core 0.10.1", - "spki 0.8.0", + "spki 0.8.0-rc.4", ] -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "plotters" version = "0.3.7" @@ -3744,12 +3723,12 @@ dependencies = [ [[package]] name = "primefield" -version = "0.14.0-rc.9" +version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6" +checksum = "93401c13cc7ff24684571cfca9d3cf9ebabfaf3d4b7b9963ade41ec54da196b5" dependencies = [ "crypto-bigint", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "rand_core 0.10.1", "rustcrypto-ff", "subtle", @@ -3758,9 +3737,9 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.14.0-rc.9" +version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a" +checksum = "a0c5c8a39bcd764bfedf456e8d55e115fe86dda3e0f555371849f2a41cbc9706" dependencies = [ "elliptic-curve", ] @@ -3864,8 +3843,8 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.39", - "socket2 0.6.3", + "rustls 0.23.40", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -3885,7 +3864,7 @@ dependencies = [ "rand 0.9.4", "ring", "rustc-hash", - "rustls 0.23.39", + "rustls 0.23.40", "rustls-pki-types", "slab", "thiserror 2.0.18", @@ -3903,7 +3882,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -4043,15 +4022,6 @@ dependencies = [ "bitflags 2.11.1", ] -[[package]] -name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" -dependencies = [ - "bitflags 2.11.1", -] - [[package]] name = "redox_users" version = "0.5.2" @@ -4115,10 +4085,10 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-util", "js-sys", "log", @@ -4140,17 +4110,17 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-rustls 0.27.9", "hyper-util", "js-sys", @@ -4158,7 +4128,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.39", + "rustls 0.23.40", "rustls-pki-types", "rustls-platform-verifier", "serde", @@ -4173,14 +4143,13 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", ] [[package]] name = "rfc6979" -version = "0.5.0-rc.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a3127ee32baec36af75b4107082d9bd823501ec14a4e016be4b6b37faa74ae" +checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" dependencies = [ "hmac 0.13.0", "subtle", @@ -4223,38 +4192,43 @@ dependencies = [ [[package]] name = "rsa" -version = "0.10.0-rc.17" +version = "0.10.0-rc.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ed3e93fc7e473e464b9726f4759659e72bc8665e4b8ea227547024f416d905" +checksum = "6fb9fd8c1edd9e6a2693623baf0fe77ff05ce022a5d7746900ffc38a15c233de" dependencies = [ "const-oid 0.10.2", "crypto-bigint", "crypto-primes", - "digest 0.11.2", + "digest 0.11.3", "pkcs1 0.8.0-rc.4", "pkcs8 0.11.0-rc.11", "rand_core 0.10.1", "sha2 0.11.0", - "signature 3.0.0-rc.10", - "spki 0.8.0", + "signature 3.0.0", + "spki 0.8.0-rc.4", "zeroize", ] [[package]] name = "russh" -version = "0.60.1" +version = "0.60.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d937f3f4a79bffd67fc12fd437785effdfc8b94edc89ab90392f9ac9e11cc9fc" +checksum = "324b92f459d3e42da294e14e8eb150d2215fcfb7c966838bc1127cd68bc05a0d" dependencies = [ + "aead 0.6.0-rc.10", "aes 0.8.4", + "aes 0.9.1", + "aes-gcm 0.11.0-rc.3", "aws-lc-rs", "bitflags 2.11.1", "block-padding 0.3.3", "byteorder", "bytes", "cbc 0.1.2", - "cipher 0.5.1", + "cbc 0.2.1", + "cipher 0.5.2", "crypto-bigint", + "ctr 0.10.1", "ctr 0.9.2", "curve25519-dalek", "data-encoding", @@ -4267,36 +4241,47 @@ dependencies = [ "enum_dispatch", "flate2", "futures", - "generic-array 1.3.5", + "generic-array 1.4.3", "getrandom 0.2.17", + "ghash 0.6.0", "hex-literal", + "hkdf", "hmac 0.12.1", + "hmac 0.13.0", "inout 0.1.4", "internal-russh-forked-ssh-key", "internal-russh-num-bigint", + "keccak", "log", "md5", "ml-kem", "module-lattice", + "num-bigint", "p256", "p384", "p521", "pageant", "pbkdf2 0.12.2", + "pbkdf2 0.13.0", "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.1", - "rsa 0.10.0-rc.17", + "rsa 0.10.0-rc.16", "russh-cryptovec", "russh-util", + "salsa20", + "scrypt", "sec1", "sha1 0.10.6", + "sha1 0.11.0", "sha2 0.10.9", - "signature 3.0.0-rc.10", - "spki 0.8.0", + "sha2 0.11.0", + "sha3", + "signature 3.0.0", + "spki 0.8.0-rc.4", "ssh-encoding", "subtle", "thiserror 2.0.18", @@ -4308,31 +4293,34 @@ dependencies = [ [[package]] name = "russh-cryptovec" -version = "0.59.0" +version = "0.60.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36140e8a20297bc2e8338807c3d9ca911f7fa49d7539cbcd6d48d3befd70efd8" +checksum = "37cb4d0360bdd8935392a306d8b5edb539cc455b30e8bf13dd213a0cf7879b40" dependencies = [ "log", - "nix 0.31.2", + "nix 0.31.3", "ssh-encoding", "windows-sys 0.61.2", ] [[package]] name = "russh-sftp" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb94393cafad0530145b8f626d8687f1ee1dedb93d7ba7740d6ae81868b13b5" +checksum = "9ed8949eca4163c18a8f59ff96d32cf61e9c13b9735e21ef32b3907f4aafa1a9" dependencies = [ "bitflags 2.11.1", "bytes", "chrono", - "flurry", + "dashmap", + "gloo-timers", "log", "serde", + "serde_bytes", "thiserror 2.0.18", "tokio", "tokio-util", + "wasm-bindgen-futures", ] [[package]] @@ -4410,9 +4398,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.39" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -4448,16 +4436,16 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation", "core-foundation-sys", "jni", "log", "once_cell", - "rustls 0.23.39", + "rustls 0.23.40", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.13", @@ -4526,7 +4514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c" dependencies = [ "cfg-if", - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] @@ -4633,12 +4621,6 @@ dependencies = [ "libc", ] -[[package]] -name = "seize" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "689224d06523904ebcc9b482c6a3f4f7fb396096645c4cd10c0d2ff7371a34d3" - [[package]] name = "self_cell" version = "1.2.2" @@ -4691,6 +4673,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -4713,9 +4705,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -4762,9 +4754,9 @@ dependencies = [ [[package]] name = "serdect" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ "base16ct", "serde", @@ -4807,7 +4799,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -4829,7 +4821,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -4838,7 +4830,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", "keccak", ] @@ -4853,9 +4845,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -4879,11 +4871,11 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.10" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", "rand_core 0.10.1", ] @@ -4893,6 +4885,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "similar" version = "2.7.0" @@ -4935,9 +4943,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -4961,9 +4969,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.8.0" +version = "0.8.0-rc.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" dependencies = [ "base64ct", "der 0.8.0", @@ -5140,15 +5148,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinystr" version = "0.8.3" @@ -5186,16 +5185,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] @@ -5227,7 +5226,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.39", + "rustls 0.23.40", "tokio", ] @@ -5247,9 +5246,9 @@ dependencies = [ [[package]] name = "tokio-socks" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" +checksum = "a7e2948f60dbe26b35f2c7fb74ac2854c1fddded0fe9d7548fcc674a246f7615" dependencies = [ "either", "futures-util", @@ -5296,17 +5295,17 @@ dependencies = [ [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "base64", "bytes", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "hyper 1.9.0", + "hyper 1.10.1", "hyper-timeout", "hyper-util", "percent-encoding", @@ -5322,9 +5321,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", @@ -5352,18 +5351,17 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "base64", "bitflags 2.11.1", "bytes", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", - "iri-string", "mime", "pin-project-lite", "tokio", @@ -5371,6 +5369,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "url", "uuid", ] @@ -5499,7 +5498,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http 1.4.1", "httparse", "log", "rand 0.9.4", @@ -5516,9 +5515,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -5575,7 +5574,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] @@ -5635,9 +5634,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -5716,9 +5715,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -5729,9 +5728,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -5739,9 +5738,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5749,9 +5748,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -5762,9 +5761,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -5805,9 +5804,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -5973,15 +5972,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -6009,21 +5999,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -6066,12 +6041,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6084,12 +6053,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -6102,12 +6065,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6132,12 +6089,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6150,12 +6101,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6168,12 +6113,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6186,12 +6125,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6206,19 +6139,19 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winrm-rs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99d0b3bbecaddf971ea5c2739b801b5a347c186a2b0996deb543f8a9f13cf5" +version = "1.1.0" +source = "git+https://github.com/muchiny/winrm-rs.git?rev=573dadf5abcaed681f65999f216164c9f33a6250#573dadf5abcaed681f65999f216164c9f33a6250" dependencies = [ "base64", "hmac 0.13.0", "md-5", "md4", "rand 0.10.1", - "reqwest 0.13.1", - "rustls 0.23.39", + "reqwest 0.13.4", + "rustls 0.23.40", "secrecy", "sha2 0.11.0", + "subtle", "thiserror 2.0.18", "tokio", "tokio-util", @@ -6359,18 +6292,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", @@ -6379,9 +6312,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] @@ -6445,8 +6378,3 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[patch.unused]] -name = "winrm-rs" -version = "1.1.0" -source = "git+https://github.com/muchiny/winrm-rs.git?rev=573dadf5abcaed681f65999f216164c9f33a6250#573dadf5abcaed681f65999f216164c9f33a6250" From 7a8b55463180a935560a7fe96e9617cc9c411e22 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 22:11:56 +0200 Subject: [PATCH 02/19] chore(deps): bump winrm-rs 1.0 -> 1.1.2, drop git-fork patch (FIND-018) crates.io 1.1.2 ships the reqwest feature fix the local fork carried; [patch.crates-io] override removed. Resolves cargo-outdated 'Removed' status. Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 23 ++++++++++++++++++----- Cargo.toml | 10 +--------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01b87e1..e7d31f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2176,7 +2176,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -3844,7 +3844,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.6.4", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -3882,7 +3882,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.4", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -6139,8 +6139,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winrm-rs" -version = "1.1.0" -source = "git+https://github.com/muchiny/winrm-rs.git?rev=573dadf5abcaed681f65999f216164c9f33a6250#573dadf5abcaed681f65999f216164c9f33a6250" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6296df157160d4d327779aad2c45ed189979955282cc52c323fc8a93c0efefb9" dependencies = [ "base64", "hmac 0.13.0", @@ -6338,6 +6339,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "serde", + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 27858cf..0fb74a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ tokio-stream = { version = "0.1", optional = true } mimalloc = { version = "0.1", default-features = false, optional = true } # WinRM adapter (optional) — wraps winrm-rs for WS-Man/SOAP over HTTPS -winrm-rs = { version = "1.0", optional = true } +winrm-rs = { version = "1.1", optional = true } # PSRP adapter (optional) — PowerShell Remoting Protocol via psrp-rs psrp-rs = { version = "1.0", optional = true, default-features = false } @@ -226,11 +226,3 @@ missing_panics_doc = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" -# ============================================================================= -# Patch overrides (FIND-018 / audit 2026-05-09) -# ============================================================================= -# winrm-rs 1.0 on crates.io declares an obsolete reqwest feature -# (`webpki-roots`) that blocks `cargo outdated` resolution. Local fork -# carries the fix; pin to it until winrm-rs > 1.0 is published. -[patch.crates-io] -winrm-rs = { git = "https://github.com/muchiny/winrm-rs.git", rev = "573dadf5abcaed681f65999f216164c9f33a6250" } From e2cbbf6d23af7834859b22bf3e9a8b23d79b1442 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 22:18:12 +0200 Subject: [PATCH 03/19] chore(deps): bump opentelemetry stack 0.31 -> 0.32 opentelemetry/_sdk/-otlp 0.32, tracing-opentelemetry 0.33 (must match). otel feature only; trace/metric/log APIs stable since 0.30. Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 97 ++++++++++++++++++++++++------------------------------ Cargo.toml | 8 ++--- 2 files changed, 47 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7d31f6..8264c47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2176,7 +2176,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.4", "tokio", "tower-service", "tracing", @@ -2919,7 +2919,7 @@ dependencies = [ "psrp-rs", "rayon", "regex", - "reqwest 0.13.4", + "reqwest", "russh", "russh-sftp", "serde", @@ -3231,9 +3231,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "opentelemetry" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ "futures-core", "futures-sink", @@ -3245,22 +3245,22 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" dependencies = [ "async-trait", "bytes", "http 1.4.1", "opentelemetry", - "reqwest 0.12.28", + "reqwest", ] [[package]] name = "opentelemetry-otlp" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ "http 1.4.1", "opentelemetry", @@ -3268,18 +3268,18 @@ dependencies = [ "opentelemetry-proto", "opentelemetry_sdk", "prost", - "reqwest 0.12.28", + "reqwest", "thiserror 2.0.18", "tokio", "tonic", - "tracing", + "tonic-types", ] [[package]] name = "opentelemetry-proto" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -3290,15 +3290,16 @@ dependencies = [ [[package]] name = "opentelemetry_sdk" -version = "0.31.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" dependencies = [ "futures-channel", "futures-executor", "futures-util", "opentelemetry", "percent-encoding", + "portable-atomic", "rand 0.9.4", "thiserror 2.0.18", "tokio", @@ -3795,6 +3796,15 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "psrp-rs" version = "1.0.0" @@ -3844,7 +3854,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.5.10", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -3882,7 +3892,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -4074,40 +4084,6 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "http 1.4.1", - "http-body 1.0.1", - "http-body-util", - "hyper 1.10.1", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "reqwest" version = "0.13.4" @@ -4116,7 +4092,9 @@ checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", + "futures-channel", "futures-core", + "futures-util", "http 1.4.1", "http-body 1.0.1", "http-body-util", @@ -5330,6 +5308,17 @@ dependencies = [ "tonic", ] +[[package]] +name = "tonic-types" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -5431,9 +5420,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" dependencies = [ "js-sys", "opentelemetry", @@ -6148,7 +6137,7 @@ dependencies = [ "md-5", "md4", "rand 0.10.1", - "reqwest 0.13.4", + "reqwest", "rustls 0.23.40", "secrecy", "sha2 0.11.0", diff --git a/Cargo.toml b/Cargo.toml index 0fb74a6..195104e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -141,10 +141,10 @@ jaq-std = { version = "3", optional = true } jaq-json = { version = "2", features = ["serde"], optional = true } # OpenTelemetry tracing/metrics (optional, feature = "otel") -opentelemetry = { version = "0.31", optional = true } -opentelemetry_sdk = { version = "0.31", features = ["rt-tokio"], optional = true } -opentelemetry-otlp = { version = "0.31", features = ["grpc-tonic"], optional = true } -tracing-opentelemetry = { version = "0.32", optional = true } +opentelemetry = { version = "0.32", optional = true } +opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"], optional = true } +opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic"], optional = true } +tracing-opentelemetry = { version = "0.33", optional = true } similar = "2.6" inventory = "0.3" mcp-ssh-bridge-macros = { version = "0.1.0", path = "crates/mcp-ssh-bridge-macros" } From caaf3a7017b51298435ed08c1a2872462ea1db66 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 22:22:55 +0200 Subject: [PATCH 04/19] chore(deps): bump similar 2 -> 3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No source changes needed — TextDiff::from_lines / ChangeTag API is stable across the major; the break was MSRV-only. Resolves to similar v3.1.1. --- Cargo.lock | 13 +++++++++++-- Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8264c47..fbcedc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2376,7 +2376,7 @@ dependencies = [ "console", "once_cell", "serde", - "similar", + "similar 2.7.0", "tempfile", ] @@ -2926,7 +2926,7 @@ dependencies = [ "serde-saphyr", "serde_json", "sha2 0.10.9", - "similar", + "similar 3.1.1", "tempfile", "thiserror 2.0.18", "tokio", @@ -4885,6 +4885,15 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "similar" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16" +dependencies = [ + "bstr", +] + [[package]] name = "simple_asn1" version = "0.6.4" diff --git a/Cargo.toml b/Cargo.toml index 195104e..a18c66f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,7 +145,7 @@ opentelemetry = { version = "0.32", optional = true } opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"], optional = true } opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic"], optional = true } tracing-opentelemetry = { version = "0.33", optional = true } -similar = "2.6" +similar = "3" inventory = "0.3" mcp-ssh-bridge-macros = { version = "0.1.0", path = "crates/mcp-ssh-bridge-macros" } jsonwebtoken = "9" From c1fd16ccbb521e846c1c64a2f5b02ce3f1f13170 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 22:29:00 +0200 Subject: [PATCH 05/19] chore(deps): bump sha2 0.10 -> 0.11 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direct API (Sha256::new/update/finalize/Digest) unchanged; 0.11 moves to digest 0.11 / hybrid-array output. sftp checksum + audit-recording hash tests confirm byte-identical output. recording.rs: replace format!("{:x}", h.finalize()) with const_hex::encode(h.finalize().as_slice()) — LowerHex no longer implemented for hybrid_array::Array in digest 0.11. Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/security/recording.rs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fbcedc2..f055534 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2925,7 +2925,7 @@ dependencies = [ "serde", "serde-saphyr", "serde_json", - "sha2 0.10.9", + "sha2 0.11.0", "similar 3.1.1", "tempfile", "thiserror 2.0.18", diff --git a/Cargo.toml b/Cargo.toml index a18c66f..849ba55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,7 +95,7 @@ chrono = { version = "0.4", default-features = false, features = ["clock", "serd dirs = "6" tokio-socks = "0.5" async-trait = "0.1" -sha2 = "0.10" +sha2 = "0.11" const-hex = "1" zeroize = { version = "1", features = ["serde"] } uuid = { version = "1", features = ["v4"] } diff --git a/src/security/recording.rs b/src/security/recording.rs index 6b4cd0b..cc1c9c4 100644 --- a/src/security/recording.rs +++ b/src/security/recording.rs @@ -12,6 +12,7 @@ use std::time::Instant; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use const_hex::encode as hex_encode; use sha2::{Digest, Sha256}; use tracing::{error, info, warn}; @@ -400,7 +401,7 @@ impl SessionRecorder { hasher.update(key); hasher.update(previous_hash.as_bytes()); hasher.update(data.as_bytes()); - format!("{:x}", hasher.finalize()) + hex_encode(hasher.finalize().as_slice()) } /// Read recording info from a .cast file header From a9cf331a7c7fac113bb5246d59aa1030174a2de6 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 22:37:33 +0200 Subject: [PATCH 06/19] chore(deps): bump jsonwebtoken 9 -> 10 OAuth/JWT validator (http feature). v10 introduces a pluggable CryptoProvider trait system as a breaking change; enable the rust_crypto + use_pem features to satisfy it with pure-Rust backends. All Validation/DecodingKey call-sites in oauth.rs are API-compatible (identical signatures in v10). No changes to oauth.rs required. explicit set_required_spec_claims + set_issuer/set_audience preserved (FIND-007). Full oauth negative-case suite verified: 20/20 tests pass, all rejection cases still rejecting. Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 287 ++++++++++++++++++++++++++++++++++++++++++++--------- Cargo.toml | 2 +- 2 files changed, 243 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f055534..37b6bae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -712,6 +712,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base16ct" version = "1.0.0" @@ -1237,6 +1243,18 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.7.0-rc.28" @@ -1281,7 +1299,7 @@ version = "0.7.0-pre.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6081ce8b60c0e533e2bba42771b94eb6149052115f4179744d5779883dc98583" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.7.0-rc.28", "libm", "rand_core 0.10.1", ] @@ -1314,6 +1332,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "5.0.0-pre.6" @@ -1324,7 +1358,7 @@ dependencies = [ "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.11.3", - "fiat-crypto", + "fiat-crypto 0.3.0", "rustc_version", "subtle", "zeroize", @@ -1379,6 +1413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -1491,6 +1526,20 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.10", + "digest 0.10.7", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.3", +] + [[package]] name = "ecdsa" version = "0.17.0-rc.16" @@ -1499,13 +1548,23 @@ checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829" dependencies = [ "der 0.8.0", "digest 0.11.3", - "elliptic-curve", - "rfc6979", + "elliptic-curve 0.14.0-rc.28", + "rfc6979 0.5.0", "signature 3.0.0", "spki 0.8.0-rc.4", "zeroize", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + [[package]] name = "ed25519" version = "3.0.0-rc.4" @@ -1516,14 +1575,28 @@ dependencies = [ "signature 3.0.0", ] +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519 2.2.3", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "ed25519-dalek" version = "3.0.0-pre.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" dependencies = [ - "curve25519-dalek", - "ed25519", + "curve25519-dalek 5.0.0-pre.6", + "ed25519 3.0.0-rc.4", "rand_core 0.10.1", "serde", "sha2 0.11.0", @@ -1538,17 +1611,38 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest 0.10.7", + "ff", + "generic-array 0.14.7", + "group", + "hkdf 0.12.4", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1 0.7.3", + "subtle", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.14.0-rc.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bde7860544606d222fd6bd6d9f9a0773321bf78072a637e1d560a058c0031978" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 1.0.0", + "crypto-bigint 0.7.0-rc.28", "crypto-common 0.2.2", "digest 0.11.3", - "hkdf", + "hkdf 0.13.0", "hybrid-array", "once_cell", "pem-rfc7468 1.0.0", @@ -1556,7 +1650,7 @@ dependencies = [ "rand_core 0.10.1", "rustcrypto-ff", "rustcrypto-group", - "sec1", + "sec1 0.8.1", "subtle", "zeroize", ] @@ -1619,6 +1713,22 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "fiat-crypto" version = "0.3.0" @@ -1783,6 +1893,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1868,6 +1979,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.3.27" @@ -1962,6 +2084,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "242402749acf71e6f32f5857598b7002c4058a4e3c3b22b4c7d51cab9aea754e" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hkdf" version = "0.13.0" @@ -2388,18 +2519,18 @@ checksum = "25f8a978272e3cbdf4768f7363eb1c8e1e6ba63c52a3ed05e29e222da4aec7cb" dependencies = [ "argon2", "bcrypt-pbkdf", - "crypto-bigint", - "ecdsa", - "ed25519-dalek", + "crypto-bigint 0.7.0-rc.28", + "ecdsa 0.17.0-rc.16", + "ed25519-dalek 3.0.0-pre.6", "hex", "hmac 0.13.0", "num-bigint-dig", - "p256", - "p384", + "p256 0.14.0-rc.7", + "p384 0.14.0-rc.7", "p521", "rand_core 0.10.1", "rsa 0.10.0-rc.16", - "sec1", + "sec1 0.8.1", "sha1 0.11.0", "sha2 0.11.0", "signature 3.0.0", @@ -2651,17 +2782,26 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "9.3.1" +version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" dependencies = [ "base64", + "ed25519-dalek 2.2.0", + "getrandom 0.2.17", + "hmac 0.12.1", "js-sys", + "p256 0.13.2", + "p384 0.13.1", "pem", - "ring", + "rand 0.8.6", + "rsa 0.9.10", "serde", "serde_json", + "sha2 0.10.9", + "signature 2.2.0", "simple_asn1", + "zeroize", ] [[package]] @@ -3327,30 +3467,54 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] + [[package]] name = "p256" version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "018bfbb86e05fd70a83e985921241035ee09fcd369c4a2c3680b389a01d2ad28" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.17.0-rc.16", + "elliptic-curve 0.14.0-rc.28", "primefield", - "primeorder", + "primeorder 0.14.0-rc.7", "sha2 0.11.0", ] +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "primeorder 0.13.6", + "sha2 0.10.9", +] + [[package]] name = "p384" version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c91df688211f5957dbe2ab599dcbcaade8d6d3cdc15c5b350d350d7d07ce423" dependencies = [ - "ecdsa", - "elliptic-curve", - "fiat-crypto", + "ecdsa 0.17.0-rc.16", + "elliptic-curve 0.14.0-rc.28", + "fiat-crypto 0.3.0", "primefield", - "primeorder", + "primeorder 0.14.0-rc.7", "sha2 0.11.0", ] @@ -3360,11 +3524,11 @@ version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de6cd9451de522549d36cc78a1b45a699a3d55a872e8ea0c8f0318e502d99e2c" dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", + "base16ct 1.0.0", + "ecdsa 0.17.0-rc.16", + "elliptic-curve 0.14.0-rc.28", "primefield", - "primeorder", + "primeorder 0.14.0-rc.7", "sha2 0.11.0", ] @@ -3384,7 +3548,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5" dependencies = [ - "base16ct", + "base16ct 1.0.0", "byteorder", "bytes", "delegate", @@ -3728,7 +3892,7 @@ version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93401c13cc7ff24684571cfca9d3cf9ebabfaf3d4b7b9963ade41ec54da196b5" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.7.0-rc.28", "crypto-common 0.2.2", "rand_core 0.10.1", "rustcrypto-ff", @@ -3736,13 +3900,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve 0.13.8", +] + [[package]] name = "primeorder" version = "0.14.0-rc.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c5c8a39bcd764bfedf456e8d55e115fe86dda3e0f555371849f2a41cbc9706" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.14.0-rc.28", ] [[package]] @@ -4123,6 +4296,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + [[package]] name = "rfc6979" version = "0.5.0" @@ -4175,7 +4358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb9fd8c1edd9e6a2693623baf0fe77ff05ce022a5d7746900ffc38a15c233de" dependencies = [ "const-oid 0.10.2", - "crypto-bigint", + "crypto-bigint 0.7.0-rc.28", "crypto-primes", "digest 0.11.3", "pkcs1 0.8.0-rc.4", @@ -4205,17 +4388,17 @@ dependencies = [ "cbc 0.1.2", "cbc 0.2.1", "cipher 0.5.2", - "crypto-bigint", + "crypto-bigint 0.7.0-rc.28", "ctr 0.10.1", "ctr 0.9.2", - "curve25519-dalek", + "curve25519-dalek 5.0.0-pre.6", "data-encoding", "delegate", "der 0.8.0", "digest 0.10.7", - "ecdsa", - "ed25519-dalek", - "elliptic-curve", + "ecdsa 0.17.0-rc.16", + "ed25519-dalek 3.0.0-pre.6", + "elliptic-curve 0.14.0-rc.28", "enum_dispatch", "flate2", "futures", @@ -4223,7 +4406,7 @@ dependencies = [ "getrandom 0.2.17", "ghash 0.6.0", "hex-literal", - "hkdf", + "hkdf 0.13.0", "hmac 0.12.1", "hmac 0.13.0", "inout 0.1.4", @@ -4235,8 +4418,8 @@ dependencies = [ "ml-kem", "module-lattice", "num-bigint", - "p256", - "p384", + "p256 0.14.0-rc.7", + "p384 0.14.0-rc.7", "p521", "pageant", "pbkdf2 0.12.2", @@ -4252,7 +4435,7 @@ dependencies = [ "russh-util", "salsa20", "scrypt", - "sec1", + "sec1 0.8.1", "sha1 0.10.6", "sha1 0.11.0", "sha2 0.10.9", @@ -4552,13 +4735,27 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.10", + "generic-array 0.14.7", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "sec1" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d" dependencies = [ - "base16ct", + "base16ct 1.0.0", "ctutils", "der 0.8.0", "hybrid-array", @@ -4736,7 +4933,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ - "base16ct", + "base16ct 1.0.0", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index 849ba55..5fc2624 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,7 +148,7 @@ tracing-opentelemetry = { version = "0.33", optional = true } similar = "3" inventory = "0.3" mcp-ssh-bridge-macros = { version = "0.1.0", path = "crates/mcp-ssh-bridge-macros" } -jsonwebtoken = "9" +jsonwebtoken = { version = "10", features = ["rust_crypto", "use_pem"] } [dev-dependencies] tempfile = "3" From fc9e79da2a0a7504bfc44a85e09fcaf3d10b8689 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 22:51:47 +0200 Subject: [PATCH 07/19] chore(deps): bump russh 0.60 -> 0.61 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Core SSH transport. Only Cargo.toml + Cargo.lock required changes — all adapter code (client/session/connector/sftp/known_hosts) compiled without modification. Algorithm preferences (kex/cipher/mac constants, Preferred struct fields, Limits::new) and host-key verification (check_server_key returns Result) are unchanged in 0.61 API. Mock harness green: 3965 passed, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.lock | 443 ++++++++++++++++++++++------------------------------- Cargo.toml | 2 +- 2 files changed, 180 insertions(+), 265 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 37b6bae..916f133 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,16 +8,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common 0.1.7", - "generic-array 0.14.7", -] - [[package]] name = "aead" version = "0.6.0-rc.10" @@ -48,20 +38,7 @@ dependencies = [ "cipher 0.5.2", "cpubits", "cpufeatures 0.3.0", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead 0.5.2", - "aes 0.8.4", - "cipher 0.4.4", - "ctr 0.9.2", - "ghash 0.5.1", - "subtle", + "zeroize", ] [[package]] @@ -70,12 +47,13 @@ version = "0.11.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22c0c90bbe8d4f77c3ca9ddabe41a1f8382d6fc1f7cea89459d0f320371f972" dependencies = [ - "aead 0.6.0-rc.10", + "aead", "aes 0.9.1", "cipher 0.5.2", - "ctr 0.10.1", - "ghash 0.6.0", + "ctr", + "ghash", "subtle", + "zeroize", ] [[package]] @@ -202,13 +180,13 @@ dependencies = [ [[package]] name = "argon2" -version = "0.5.3" +version = "0.6.0-rc.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5" dependencies = [ "base64ct", "blake2", - "cpufeatures 0.2.17", + "cpufeatures 0.3.0", "password-hash", ] @@ -748,13 +726,13 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bcrypt-pbkdf" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2" +checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0" dependencies = [ "blowfish", - "pbkdf2 0.12.2", - "sha2 0.10.9", + "pbkdf2", + "sha2 0.11.0", ] [[package]] @@ -789,11 +767,11 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.6" +version = "0.11.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690" dependencies = [ - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -812,6 +790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", + "zeroize", ] [[package]] @@ -834,12 +813,12 @@ dependencies = [ [[package]] name = "blowfish" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298" dependencies = [ "byteorder", - "cipher 0.4.4", + "cipher 0.5.2", ] [[package]] @@ -887,15 +866,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "cbc" version = "0.2.1" @@ -929,17 +899,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures 0.2.17", -] - [[package]] name = "chacha20" version = "0.10.0" @@ -947,8 +906,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", + "cipher 0.5.2", "cpufeatures 0.3.0", "rand_core 0.10.1", + "zeroize", ] [[package]] @@ -1011,6 +972,7 @@ dependencies = [ "block-buffer 0.12.0", "crypto-common 0.2.2", "inout 0.2.2", + "zeroize", ] [[package]] @@ -1257,9 +1219,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.7.0-rc.28" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96dacf199529fb801ae62a9aafdc01b189e9504c0d1ee1512a4c16bcd8666a93" +checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" dependencies = [ "cpubits", "ctutils", @@ -1295,24 +1257,15 @@ dependencies = [ [[package]] name = "crypto-primes" -version = "0.7.0-pre.9" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6081ce8b60c0e533e2bba42771b94eb6149052115f4179744d5779883dc98583" +checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" dependencies = [ - "crypto-bigint 0.7.0-rc.28", + "crypto-bigint 0.7.3", "libm", "rand_core 0.10.1", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher 0.4.4", -] - [[package]] name = "ctr" version = "0.10.1" @@ -1458,6 +1411,15 @@ dependencies = [ "syn", ] +[[package]] +name = "des" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "digest" version = "0.10.7" @@ -1542,16 +1504,16 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.17.0-rc.16" +version = "0.17.0-rc.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829" +checksum = "54fb064faabbee66e1fc8e5c5a9458d4269dc2d8b638fe86a425adb2510d1a96" dependencies = [ "der 0.8.0", "digest 0.11.3", - "elliptic-curve 0.14.0-rc.28", + "elliptic-curve 0.14.0-rc.32", "rfc6979 0.5.0", "signature 3.0.0", - "spki 0.8.0-rc.4", + "spki 0.8.0", "zeroize", ] @@ -1567,11 +1529,11 @@ dependencies = [ [[package]] name = "ed25519" -version = "3.0.0-rc.4" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "signature 3.0.0", ] @@ -1591,12 +1553,12 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "3.0.0-pre.6" +version = "3.0.0-pre.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" +checksum = "20449acd54b660981ae5caa2bcb56d1fe7f25f2e37a38ec507400fab034d4bb6" dependencies = [ "curve25519-dalek 5.0.0-pre.6", - "ed25519 3.0.0-rc.4", + "ed25519 3.0.0", "rand_core 0.10.1", "serde", "sha2 0.11.0", @@ -1634,19 +1596,19 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.14.0-rc.28" +version = "0.14.0-rc.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde7860544606d222fd6bd6d9f9a0773321bf78072a637e1d560a058c0031978" +checksum = "cda94f31325c4275e9706adecbb6f0650dee2f904c915a98e3d81adaaaa757aa" dependencies = [ "base16ct 1.0.0", - "crypto-bigint 0.7.0-rc.28", + "crypto-bigint 0.7.3", "crypto-common 0.2.2", "digest 0.11.3", "hkdf 0.13.0", "hybrid-array", "once_cell", "pem-rfc7468 1.0.0", - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "rand_core 0.10.1", "rustcrypto-ff", "rustcrypto-group", @@ -1948,23 +1910,13 @@ dependencies = [ "wasip3", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval 0.6.2", -] - [[package]] name = "ghash" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "polyval 0.7.1", + "polyval", ] [[package]] @@ -2511,35 +2463,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "internal-russh-forked-ssh-key" -version = "0.6.18+upstream-0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f8a978272e3cbdf4768f7363eb1c8e1e6ba63c52a3ed05e29e222da4aec7cb" -dependencies = [ - "argon2", - "bcrypt-pbkdf", - "crypto-bigint 0.7.0-rc.28", - "ecdsa 0.17.0-rc.16", - "ed25519-dalek 3.0.0-pre.6", - "hex", - "hmac 0.13.0", - "num-bigint-dig", - "p256 0.14.0-rc.7", - "p384 0.14.0-rc.7", - "p521", - "rand_core 0.10.1", - "rsa 0.10.0-rc.16", - "sec1 0.8.1", - "sha1 0.11.0", - "sha2 0.11.0", - "signature 3.0.0", - "ssh-cipher", - "ssh-encoding", - "subtle", - "zeroize", -] - [[package]] name = "internal-russh-num-bigint" version = "0.5.0" @@ -3177,13 +3100,14 @@ dependencies = [ [[package]] name = "ml-kem" -version = "0.3.0-rc.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8198b5db27ac9773534c371751a59dc18aec8b80aa141e69abfdd1dec2e3f78c" +checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" dependencies = [ "hybrid-array", "kem", "module-lattice", + "pkcs8 0.11.0", "rand_core 0.10.1", "sha3", ] @@ -3298,7 +3222,6 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.6", - "serde", "smallvec", "zeroize", ] @@ -3357,12 +3280,6 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -3481,14 +3398,14 @@ dependencies = [ [[package]] name = "p256" -version = "0.14.0-rc.7" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "018bfbb86e05fd70a83e985921241035ee09fcd369c4a2c3680b389a01d2ad28" +checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a" dependencies = [ - "ecdsa 0.17.0-rc.16", - "elliptic-curve 0.14.0-rc.28", + "ecdsa 0.17.0-rc.18", + "elliptic-curve 0.14.0-rc.32", "primefield", - "primeorder 0.14.0-rc.7", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] @@ -3506,29 +3423,29 @@ dependencies = [ [[package]] name = "p384" -version = "0.14.0-rc.7" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c91df688211f5957dbe2ab599dcbcaade8d6d3cdc15c5b350d350d7d07ce423" +checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03" dependencies = [ - "ecdsa 0.17.0-rc.16", - "elliptic-curve 0.14.0-rc.28", + "ecdsa 0.17.0-rc.18", + "elliptic-curve 0.14.0-rc.32", "fiat-crypto 0.3.0", "primefield", - "primeorder 0.14.0-rc.7", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] [[package]] name = "p521" -version = "0.14.0-rc.7" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de6cd9451de522549d36cc78a1b45a699a3d55a872e8ea0c8f0318e502d99e2c" +checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7" dependencies = [ "base16ct 1.0.0", - "ecdsa 0.17.0-rc.16", - "elliptic-curve 0.14.0-rc.28", + "ecdsa 0.17.0-rc.18", + "elliptic-curve 0.14.0-rc.32", "primefield", - "primeorder 0.14.0-rc.7", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] @@ -3577,23 +3494,11 @@ dependencies = [ [[package]] name = "password-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", + "phc", ] [[package]] @@ -3683,6 +3588,16 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "phc" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892" +dependencies = [ + "base64ct", + "ctutils", +] + [[package]] name = "pin-project" version = "1.1.13" @@ -3733,24 +3648,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" dependencies = [ "der 0.8.0", - "spki 0.8.0-rc.4", + "spki 0.8.0", ] [[package]] name = "pkcs5" -version = "0.8.0-rc.13" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a777c6e26664bc9504b3ce3f6133f8f20d9071f130a4f9fcbd3186959d8dd6" +checksum = "279a91971a1d8eb1260a30938eae3be9cb67b472dffecb222fbbbe2fd2dc1453" dependencies = [ "aes 0.9.1", - "aes-gcm 0.11.0-rc.3", - "cbc 0.2.1", + "cbc", "der 0.8.0", - "pbkdf2 0.13.0", + "pbkdf2", "rand_core 0.10.1", "scrypt", "sha2 0.11.0", - "spki 0.8.0-rc.4", + "spki 0.8.0", ] [[package]] @@ -3765,14 +3679,14 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.11" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der 0.8.0", "pkcs5", "rand_core 0.10.1", - "spki 0.8.0-rc.4", + "spki 0.8.0", ] [[package]] @@ -3805,25 +3719,13 @@ dependencies = [ [[package]] name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash 0.5.1", -] - -[[package]] -name = "polyval" -version = "0.6.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "a00baa632505d05512f48a963e16051c54fda9a95cc9acea1a4e3c90991c4a2e" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash 0.5.1", + "cpufeatures 0.3.0", + "universal-hash", + "zeroize", ] [[package]] @@ -3834,7 +3736,7 @@ checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" dependencies = [ "cpubits", "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", ] [[package]] @@ -3888,11 +3790,11 @@ dependencies = [ [[package]] name = "primefield" -version = "0.14.0-rc.7" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93401c13cc7ff24684571cfca9d3cf9ebabfaf3d4b7b9963ade41ec54da196b5" +checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6" dependencies = [ - "crypto-bigint 0.7.0-rc.28", + "crypto-bigint 0.7.3", "crypto-common 0.2.2", "rand_core 0.10.1", "rustcrypto-ff", @@ -3911,11 +3813,11 @@ dependencies = [ [[package]] name = "primeorder" -version = "0.14.0-rc.7" +version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c5c8a39bcd764bfedf456e8d55e115fe86dda3e0f555371849f2a41cbc9706" +checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a" dependencies = [ - "elliptic-curve 0.14.0-rc.28", + "elliptic-curve 0.14.0-rc.32", ] [[package]] @@ -4118,7 +4020,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20 0.10.0", + "chacha20", "getrandom 0.4.2", "rand_core 0.10.1", ] @@ -4353,64 +4255,57 @@ dependencies = [ [[package]] name = "rsa" -version = "0.10.0-rc.16" +version = "0.10.0-rc.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb9fd8c1edd9e6a2693623baf0fe77ff05ce022a5d7746900ffc38a15c233de" +checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" dependencies = [ "const-oid 0.10.2", - "crypto-bigint 0.7.0-rc.28", + "crypto-bigint 0.7.3", "crypto-primes", "digest 0.11.3", "pkcs1 0.8.0-rc.4", - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "rand_core 0.10.1", "sha2 0.11.0", "signature 3.0.0", - "spki 0.8.0-rc.4", + "spki 0.8.0", "zeroize", ] [[package]] name = "russh" -version = "0.60.3" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324b92f459d3e42da294e14e8eb150d2215fcfb7c966838bc1127cd68bc05a0d" +checksum = "f67013f080c226e5a34db1c71f2567f44d95a6300005bb6cd4e2c8fe3c326d1b" dependencies = [ - "aead 0.6.0-rc.10", - "aes 0.8.4", "aes 0.9.1", - "aes-gcm 0.11.0-rc.3", "aws-lc-rs", "bitflags 2.11.1", - "block-padding 0.3.3", + "block-padding 0.4.2", "byteorder", "bytes", - "cbc 0.1.2", - "cbc 0.2.1", + "cbc", "cipher 0.5.2", - "crypto-bigint 0.7.0-rc.28", - "ctr 0.10.1", - "ctr 0.9.2", + "crypto-bigint 0.7.3", + "ctr", "curve25519-dalek 5.0.0-pre.6", "data-encoding", "delegate", "der 0.8.0", - "digest 0.10.7", - "ecdsa 0.17.0-rc.16", - "ed25519-dalek 3.0.0-pre.6", - "elliptic-curve 0.14.0-rc.28", + "digest 0.11.3", + "ecdsa 0.17.0-rc.18", + "ed25519-dalek 3.0.0-pre.7", + "elliptic-curve 0.14.0-rc.32", "enum_dispatch", "flate2", "futures", "generic-array 1.4.3", "getrandom 0.2.17", - "ghash 0.6.0", + "ghash", "hex-literal", "hkdf 0.13.0", - "hmac 0.12.1", "hmac 0.13.0", "inout 0.1.4", - "internal-russh-forked-ssh-key", "internal-russh-num-bigint", "keccak", "log", @@ -4418,45 +4313,43 @@ dependencies = [ "ml-kem", "module-lattice", "num-bigint", - "p256 0.14.0-rc.7", - "p384 0.14.0-rc.7", + "p256 0.14.0-rc.9", + "p384 0.14.0-rc.9", "p521", "pageant", - "pbkdf2 0.12.2", - "pbkdf2 0.13.0", + "pbkdf2", "pkcs1 0.8.0-rc.4", "pkcs5", - "pkcs8 0.11.0-rc.11", - "polyval 0.7.1", + "pkcs8 0.11.0", + "polyval", "rand 0.10.1", "rand_core 0.10.1", - "rsa 0.10.0-rc.16", + "rsa 0.10.0-rc.18", "russh-cryptovec", "russh-util", "salsa20", "scrypt", "sec1 0.8.1", - "sha1 0.10.6", "sha1 0.11.0", - "sha2 0.10.9", "sha2 0.11.0", "sha3", "signature 3.0.0", - "spki 0.8.0-rc.4", + "spki 0.8.0", "ssh-encoding", + "ssh-key", "subtle", "thiserror 2.0.18", "tokio", "typenum", - "universal-hash 0.6.1", + "universal-hash", "zeroize", ] [[package]] name = "russh-cryptovec" -version = "0.60.3" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37cb4d0360bdd8935392a306d8b5edb539cc455b30e8bf13dd213a0cf7879b40" +checksum = "443f6bbcfacb34a1aab2b12b99bf08e0c63abdc5a0db261901365df9d57fff51" dependencies = [ "log", "nix 0.31.3", @@ -4720,7 +4613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0" dependencies = [ "cfg-if", - "pbkdf2 0.13.0", + "pbkdf2", "salsa20", "sha2 0.11.0", ] @@ -5153,9 +5046,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.8.0-rc.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", "der 0.8.0", @@ -5163,31 +5056,63 @@ dependencies = [ [[package]] name = "ssh-cipher" -version = "0.2.0" +version = "0.3.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36" dependencies = [ - "aes 0.8.4", - "aes-gcm 0.10.3", - "cbc 0.1.2", - "chacha20 0.9.1", - "cipher 0.4.4", - "ctr 0.9.2", + "aead", + "aes 0.9.1", + "aes-gcm", + "cbc", + "chacha20", + "cipher 0.5.2", + "ctr", + "ctutils", + "des", "poly1305", "ssh-encoding", - "subtle", + "zeroize", ] [[package]] name = "ssh-encoding" -version = "0.2.0" +version = "0.3.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a" dependencies = [ "base64ct", "bytes", - "pem-rfc7468 0.7.0", - "sha2 0.10.9", + "crypto-bigint 0.7.3", + "ctutils", + "digest 0.11.3", + "pem-rfc7468 1.0.0", + "zeroize", +] + +[[package]] +name = "ssh-key" +version = "0.7.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45735ce3dea95690e4a9e414c4cfde7f79835063c3dcd35881df85a84118e74b" +dependencies = [ + "argon2", + "bcrypt-pbkdf", + "ctutils", + "ed25519-dalek 3.0.0-pre.7", + "hex", + "hmac 0.13.0", + "p256 0.14.0-rc.9", + "p384 0.14.0-rc.9", + "p521", + "rand_core 0.10.1", + "rsa 0.10.0-rc.18", + "sec1 0.8.1", + "sha1 0.11.0", + "sha2 0.11.0", + "signature 3.0.0", + "ssh-cipher", + "ssh-encoding", + "zeroize", ] [[package]] @@ -5753,16 +5678,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] - [[package]] name = "universal-hash" version = "0.6.1" diff --git a/Cargo.toml b/Cargo.toml index 5fc2624..8c587eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ all-protocols = ["winrm", "psrp", "telnet", "k8s-exec", "serial", "ssm", "azure" tokio = { version = "1", features = ["rt-multi-thread", "io-util", "net", "process", "time", "fs", "io-std", "macros", "sync", "signal"] } # SSH -russh = "0.60" +russh = "0.61" russh-sftp = "2.1" # Serialization From 6f3e08daae84b2081e308702d9b139b59cdb2d4b Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 23:02:28 +0200 Subject: [PATCH 08/19] chore(deps): bump serde-saphyr 0.0.21 -> 0.0.27 Hardened YAML wrapper (domain/yaml.rs) Budget/Options preserved; DoS budget-limit tests verified. Pin kept as '=' (0.0.x). Migration notes: - Direct struct construction of Budget/Options deprecated since 0.0.23; migrated hardened_options() to budget!/options! macros (no behaviour change). - All five original limits retained at same values: max_reader_input_bytes, max_anchors (100), max_aliases (1 000), max_depth (50), max_nodes (10 000). - Added max_total_scalar_bytes = MAX_YAML_BYTES (1 MiB) to cap the new scalar-amplification vector introduced in 0.0.22+. - New dependency: granit-parser 0.0.3 (pulled by serde-saphyr 0.0.27). Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 28 +++++++++++++--------------- Cargo.toml | 2 +- src/domain/yaml.rs | 29 +++++++++++++++++------------ 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 916f133..7509764 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1931,6 +1931,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "granit-parser" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f50ba32164f9e098d5da618776a32afbb32270adcbe3d3d006107dae11e37c91" +dependencies = [ + "arraydeque", + "smallvec", +] + [[package]] name = "group" version = "0.13.0" @@ -4580,17 +4590,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "saphyr-parser-bw" -version = "0.0.608" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55ae5ea09894b6d5382621db78f586df37ef18ab581bf32c754e75076b124b1" -dependencies = [ - "arraydeque", - "smallvec", - "thiserror 2.0.18", -] - [[package]] name = "schannel" version = "0.1.29" @@ -4713,19 +4712,18 @@ dependencies = [ [[package]] name = "serde-saphyr" -version = "0.0.21" +version = "0.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6fc4aa0da972ba0f51cf5c1bb16e9dba35334adc6831b09b3ffb0ec20bb264" +checksum = "5897b4c3faadadd35fdb6689f015641f3bc481d5adaaac56231ea15aeb243db3" dependencies = [ "ahash", "annotate-snippets", "base64", "encoding_rs_io", "getrandom 0.3.4", + "granit-parser", "nohash-hasher", "num-traits", - "regex", - "saphyr-parser-bw", "serde", "smallvec", "zmij", diff --git a/Cargo.toml b/Cargo.toml index 8c587eb..092f790 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ russh-sftp = "2.1" # Serialization serde = { version = "1", features = ["derive"] } serde_json = "1" -serde-saphyr = "=0.0.21" +serde-saphyr = "=0.0.27" # Error handling thiserror = "2" diff --git a/src/domain/yaml.rs b/src/domain/yaml.rs index 00d007e..7fca442 100644 --- a/src/domain/yaml.rs +++ b/src/domain/yaml.rs @@ -18,6 +18,7 @@ //! - max alias events: 1 000 //! - max structural depth: 50 //! - max nodes (sequences + maps + scalars): 10 000 +//! - max total scalar bytes: [`MAX_YAML_BYTES`] (1 MiB, matching input cap) //! //! Test fixtures inside `#[cfg(test)] mod tests` blocks intentionally keep //! using the bare `serde_saphyr::from_str` so they can exercise edge cases @@ -47,19 +48,23 @@ pub(crate) const MAX_DEPTH: usize = 50; pub(crate) const MAX_NODES: usize = 10_000; /// Build the hardened parser options for our threat model. +/// +/// Uses the `budget!` / `options!` macros introduced in serde-saphyr 0.0.23 +/// (the direct-struct-construction path is deprecated since 0.0.23). +/// All five original limits are preserved at their original values; the new +/// `max_total_scalar_bytes` field (added in 0.0.22+) is set to +/// `MAX_YAML_BYTES` so scalar amplification is bounded by the same 1 MiB +/// cap as the input itself. fn hardened_options() -> serde_saphyr::Options { - let budget = serde_saphyr::Budget { - max_reader_input_bytes: Some(MAX_YAML_BYTES), - max_anchors: MAX_ANCHORS, - max_aliases: MAX_ALIASES, - max_depth: MAX_DEPTH, - max_nodes: MAX_NODES, - ..serde_saphyr::Budget::default() - }; - - serde_saphyr::Options { - budget: Some(budget), - ..serde_saphyr::Options::default() + serde_saphyr::options! { + budget: serde_saphyr::budget! { + max_reader_input_bytes: Some(MAX_YAML_BYTES), + max_anchors: MAX_ANCHORS, + max_aliases: MAX_ALIASES, + max_depth: MAX_DEPTH, + max_nodes: MAX_NODES, + max_total_scalar_bytes: MAX_YAML_BYTES, + }, } } From d6b6548b11e4593dd3191bb281f1e71bc533bf54 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 23:23:55 +0200 Subject: [PATCH 09/19] docs(deps): refresh Known Advisories + fix import order after dep updates - RUSTSEC-2026-0098/0099/0104 (rustls-webpki) are no longer triggered by the updated dep graph; moved from active ignore to historical comment in deny.toml. Only RUSTSEC-2023-0071 (Marvin/RSA via russh) remains active. - CLAUDE.md Known Advisories section updated to reflect the new state (1 active, 6 historical). - Fix import order regression in src/security/recording.rs (serde vs const_hex alphabetical ordering after sha2 0.11 bump). Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 9 ++++----- deny.toml | 11 +++-------- src/security/recording.rs | 2 +- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b8a5ca3..4665427 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -170,13 +170,12 @@ Key sections: `hosts`, `security`, `limits`, `audit`, `tool_groups`, `recording` ## Known Advisories -6 advisories ignored in `deny.toml` — all transitive, no upstream fix available: +1 advisory actively ignored in `deny.toml` (transitive, no upstream fix): - RUSTSEC-2023-0071 — Marvin Attack on RSA (russh) -- RUSTSEC-2026-0044 — aws-lc-sys X.509 bypass (aws-sdk) -- RUSTSEC-2026-0048 — aws-lc-sys CRL logic error (aws-sdk) -- RUSTSEC-2026-0049 — rustls-webpki CRL matching (russh/aws-sdk) -- RUSTSEC-2025-0134 — rustls-pemfile unmaintained (kube) + +Previously ignored (now resolved — no longer triggered after dep-updates-2026-05-30): +RUSTSEC-2025-0134, RUSTSEC-2026-0049, RUSTSEC-2026-0074, RUSTSEC-2026-0098, RUSTSEC-2026-0099, RUSTSEC-2026-0104 ## Path-Scoped Rules diff --git a/deny.toml b/deny.toml index 12eddda..15c6095 100644 --- a/deny.toml +++ b/deny.toml @@ -22,18 +22,13 @@ yanked = "deny" # Safe for local use, timing attack requires network observation ignore = [ "RUSTSEC-2023-0071", # Marvin Attack on RSA — transitive from russh, no upstream fix - # rustls-webpki advisories: re-added 2026-04-26 after v1.14.0 CI flagged - # them. The d80d5a2 removal was based on a local dep graph that did not - # match the CI cold resolution — CI still pulls the affected aws-sdk - # paths. cargo-deny is set to fail on advisories so this list must - # cover every match, not just what the local graph shows. - "RUSTSEC-2026-0098", # webpki URI name constraints — aws-sdk chain - "RUSTSEC-2026-0099", # webpki wildcard name constraint bypass — aws-sdk chain - "RUSTSEC-2026-0104", # webpki CRL IssuingDistributionPoint panic — aws-sdk chain # Verified no longer matched (kept here as historical context): # - RUSTSEC-2025-0134 (rustls-pemfile unmaintained) # - RUSTSEC-2026-0049 (rustls-webpki CRL matching) # - RUSTSEC-2026-0074 (libcrux-ml-kem SHAKE API — patched upstream in russh) + # - RUSTSEC-2026-0098 (webpki URI name constraints — resolved by dep-updates-2026-05-30) + # - RUSTSEC-2026-0099 (webpki wildcard name constraint bypass — resolved by dep-updates-2026-05-30) + # - RUSTSEC-2026-0104 (webpki CRL IssuingDistributionPoint panic — resolved by dep-updates-2026-05-30) ] # ============================================================================= diff --git a/src/security/recording.rs b/src/security/recording.rs index cc1c9c4..9e196be 100644 --- a/src/security/recording.rs +++ b/src/security/recording.rs @@ -11,8 +11,8 @@ use std::sync::Mutex; use std::time::Instant; use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use const_hex::encode as hex_encode; +use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use tracing::{error, info, warn}; From 065474f27e99a5ad9753c5d2408a642d184f4497 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 21:34:20 +0200 Subject: [PATCH 10/19] feat(config): add RedactedSecret newtype (leak-proof zeroizing secret) --- src/config/mod.rs | 2 + src/config/secret.rs | 121 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 src/config/secret.rs diff --git a/src/config/mod.rs b/src/config/mod.rs index f4ae031..f3a21fe 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,8 +1,10 @@ mod loader; +pub mod secret; pub mod ssh_config; pub mod types; mod watcher; pub use loader::{default_config_path, load_config}; +pub use secret::RedactedSecret; pub use types::*; pub use watcher::ConfigWatcher; diff --git a/src/config/secret.rs b/src/config/secret.rs new file mode 100644 index 0000000..325595a --- /dev/null +++ b/src/config/secret.rs @@ -0,0 +1,121 @@ +//! `RedactedSecret`: a string credential that zeroizes on drop and is +//! structurally incapable of leaking through `Debug`, `Display`, or +//! `Serialize`. Use it for every in-memory secret (passwords, passphrases, +//! API tokens) instead of `String` or bare `Zeroizing`. + +use std::fmt; +use std::ops::Deref; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use zeroize::Zeroizing; + +/// A string secret that is wiped from memory on drop and never reveals its +/// contents through `Debug`, `Display`, or `Serialize`. +/// +/// Access the underlying value explicitly with [`RedactedSecret::as_str`] or +/// via `Deref` (so `&secret` coerces to `&str` at call sites). +#[derive(Clone)] +pub struct RedactedSecret(Zeroizing); + +impl RedactedSecret { + /// Wrap an owned `String` as a redacted, zeroizing secret. + #[must_use] + pub fn new(value: String) -> Self { + Self(Zeroizing::new(value)) + } + + /// Borrow the secret as `&str` for use at an authentication boundary. + #[must_use] + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl From for RedactedSecret { + fn from(value: String) -> Self { + Self::new(value) + } +} + +impl From<&str> for RedactedSecret { + fn from(value: &str) -> Self { + Self::new(value.to_owned()) + } +} + +impl Deref for RedactedSecret { + type Target = str; + + fn deref(&self) -> &str { + self.0.as_str() + } +} + +impl fmt::Debug for RedactedSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[REDACTED]") + } +} + +impl fmt::Display for RedactedSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[REDACTED]") + } +} + +impl Serialize for RedactedSecret { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str("[REDACTED]") + } +} + +impl<'de> Deserialize<'de> for RedactedSecret { + fn deserialize>(deserializer: D) -> Result { + Ok(Self::new(String::deserialize(deserializer)?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const SECRET: &str = "hunter2-super-secret"; + + #[test] + fn debug_does_not_leak() { + let s = RedactedSecret::from(SECRET); + let rendered = format!("{s:?}"); + assert!(!rendered.contains(SECRET), "Debug leaked the secret: {rendered}"); + assert_eq!(rendered, "[REDACTED]"); + } + + #[test] + fn serialize_does_not_leak() { + let s = RedactedSecret::from(SECRET); + let json = serde_json::to_string(&s).unwrap(); + assert!(!json.contains(SECRET), "Serialize leaked the secret: {json}"); + assert_eq!(json, "\"[REDACTED]\""); + } + + #[test] + fn deserialize_reads_plain_string() { + let s: RedactedSecret = serde_json::from_str("\"hunter2-super-secret\"").unwrap(); + assert_eq!(s.as_str(), SECRET); + } + + #[test] + fn deref_and_as_str_expose_value_for_use() { + let s = RedactedSecret::from(SECRET); + let via_deref: &str = &s; + assert_eq!(via_deref, SECRET); + assert_eq!(s.as_str(), SECRET); + assert_eq!(s.len(), SECRET.len()); + } + + #[test] + fn clone_is_independent() { + let a = RedactedSecret::from(SECRET); + let b = a.clone(); + assert_eq!(a.as_str(), b.as_str()); + } +} From f7128fc9cbcee146c157ff298b32ce953a3170f4 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sat, 30 May 2026 21:41:32 +0200 Subject: [PATCH 11/19] docs(config): document RedactedSecret escape hatch + intentional no-PartialEq --- src/config/secret.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/config/secret.rs b/src/config/secret.rs index 325595a..6676531 100644 --- a/src/config/secret.rs +++ b/src/config/secret.rs @@ -14,6 +14,21 @@ use zeroize::Zeroizing; /// /// Access the underlying value explicitly with [`RedactedSecret::as_str`] or /// via `Deref` (so `&secret` coerces to `&str` at call sites). +/// +/// # Escape hatch (the audited boundary) +/// +/// [`RedactedSecret::as_str`] and `Deref` deliberately expose the plaintext: they +/// are THE single audited boundary where redaction stops applying. Treat every +/// `secret.as_str()` / `&*secret` as that boundary — pass it directly into an +/// auth/transport call only, and never into a logging or formatting macro (e.g. +/// `println!`, `tracing::info!`, `format!`), which would defeat the redaction. +/// +/// # Intentionally not comparable +/// +/// This type intentionally does NOT implement `PartialEq`, `Eq`, `Hash`, or `Ord`: +/// a derived comparison would be non-constant-time and leak secret bytes through a +/// timing side-channel. Do not add `#[derive(PartialEq)]` (etc.). If equality is +/// ever needed, compare via a constant-time primitive at the auth boundary. #[derive(Clone)] pub struct RedactedSecret(Zeroizing); From 61b0c98a6f0a83a735d938feb6f52ebfe191faf8 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 00:12:57 +0200 Subject: [PATCH 12/19] refactor(config): use RedactedSecret for SSH/SOCKS/sudo credentials (fixes F1/F2 leak) Replace bare Zeroizing secret fields (sudo_password, SOCKS password, AuthConfig Key passphrase / Password / Ntlm password) with the leak-proof RedactedSecret newtype so Debug/Display/Serialize emit [REDACTED] instead of the plaintext (F1/F2). Add a Debug-does-not-leak regression test and update serialization/deserialization tests to assert redaction. Fix the SOCKS5 connect_with_password call site and all winrm/psrp/cli/ssh_status test construction sites to build RedactedSecret. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/cli/runner.rs | 11 ++--- src/config/types.rs | 62 +++++++++++++++++++---------- src/mcp/tool_handlers/ssh_status.rs | 2 +- src/psrp/mod.rs | 2 +- src/psrp/pool.rs | 2 +- src/ssh/client.rs | 11 +---- src/winrm/mod.rs | 4 +- src/winrm/pool.rs | 2 +- 8 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/cli/runner.rs b/src/cli/runner.rs index 38347d6..1dc8fd8 100644 --- a/src/cli/runner.rs +++ b/src/cli/runner.rs @@ -1369,7 +1369,8 @@ mod tests { use super::*; use crate::config::{ AuditConfig, AuthConfig, HostConfig, HostKeyVerification, HttpTransportConfig, - LimitsConfig, OsType, SecurityConfig, SessionConfig, SshConfigDiscovery, ToolGroupsConfig, + LimitsConfig, OsType, RedactedSecret, SecurityConfig, SessionConfig, SshConfigDiscovery, + ToolGroupsConfig, }; use crate::mcp::tool_handlers::utils::shell_escape; use std::collections::HashMap; @@ -1567,7 +1568,7 @@ mod tests { fn test_auth_type_name_key_with_passphrase() { let auth = AuthConfig::Key { path: "~/.ssh/id_rsa".to_string(), - passphrase: Some(zeroize::Zeroizing::new("secret".to_string())), + passphrase: Some(RedactedSecret::from("secret")), }; assert_eq!(auth_type_name(&auth), "SSH Key"); } @@ -1581,7 +1582,7 @@ mod tests { #[test] fn test_auth_type_name_password() { let auth = AuthConfig::Password { - password: zeroize::Zeroizing::new("secret".to_string()), + password: RedactedSecret::from("secret"), }; assert_eq!(auth_type_name(&auth), "Password"); } @@ -2571,11 +2572,11 @@ mod tests { fn test_host_config_with_all_auth_types() { let key_auth = AuthConfig::Key { path: "~/.ssh/id_ed25519".to_string(), - passphrase: Some(zeroize::Zeroizing::new("secret".to_string())), + passphrase: Some(RedactedSecret::from("secret")), }; let agent_auth = AuthConfig::Agent; let password_auth = AuthConfig::Password { - password: zeroize::Zeroizing::new("pass123".to_string()), + password: RedactedSecret::from("pass123"), }; assert_eq!(auth_type_name(&key_auth), "SSH Key"); diff --git a/src/config/types.rs b/src/config/types.rs index bfc2ca8..61bb5d9 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -1,7 +1,7 @@ +use crate::config::secret::RedactedSecret; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; -use zeroize::Zeroizing; #[derive(Debug, Clone, Default, Deserialize, Serialize)] #[serde(deny_unknown_fields)] @@ -248,12 +248,13 @@ pub struct HostConfig { /// Optional sudo password for this host (used with sudo commands). /// - /// Wrapped in [`Zeroizing`] so the byte buffer is overwritten when - /// the value is dropped (FIND-028). Hot-reload via `config/watcher.rs` - /// drops the old `HostConfig`, which now wipes the prior password - /// instead of leaving it resident on the heap for the process lifetime. + /// Wrapped in [`RedactedSecret`] so the byte buffer is zeroized when + /// the value is dropped (FIND-028) and never leaks via `Debug`/`Display`/ + /// `Serialize` (F1/F2). Hot-reload via `config/watcher.rs` drops the old + /// `HostConfig`, which now wipes the prior password instead of leaving it + /// resident on the heap for the process lifetime. #[serde(default)] - pub sudo_password: Option>, + pub sudo_password: Option, /// Tags for grouping hosts (e.g., "production", "staging", "database") #[serde(default)] @@ -457,12 +458,12 @@ pub struct SocksProxyConfig { /// Optional password for SOCKS5 authentication. /// - /// Wrapped in `Zeroizing` so the byte buffer is overwritten - /// when the value is dropped (FIND-014). `SocksProxyConfig` lives for - /// the entire process; without `Zeroizing` the password sits in heap - /// from start to exit. + /// Wrapped in [`RedactedSecret`] so the byte buffer is zeroized when the + /// value is dropped (FIND-014) and never leaks via `Debug`/`Display`/ + /// `Serialize` (F1/F2). `SocksProxyConfig` lives for the entire process; + /// without zeroization the password sits in heap from start to exit. #[serde(default)] - pub password: Option>, + pub password: Option, } /// SOCKS protocol version @@ -485,25 +486,26 @@ const fn default_socks_port() -> u16 { /// SSH variants (`Key`, `Agent`, `Password`) are always available. /// `WinRM` variants (`Ntlm`, `Certificate`, `Kerberos`) are feature-gated. /// -/// Sensitive fields (`password`, `passphrase`) are wrapped in [`Zeroizing`] +/// Sensitive fields (`password`, `passphrase`) are wrapped in [`RedactedSecret`] /// so they are securely erased from memory when the config is dropped -/// (e.g. on hot-reload or process exit). +/// (e.g. on hot-reload or process exit) and never leak through `Debug`, +/// `Display`, or `Serialize`. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum AuthConfig { Key { path: String, #[serde(default)] - passphrase: Option>, + passphrase: Option, }, Agent, Password { - password: Zeroizing, + password: RedactedSecret, }, /// `NTLMv2` authentication for `WinRM` (default `WinRM` auth method). #[cfg(feature = "winrm")] Ntlm { - password: Zeroizing, + password: RedactedSecret, /// Windows domain (e.g., "CORP"). Optional — omit for local accounts. #[serde(default)] domain: Option, @@ -1455,11 +1457,23 @@ mod tests { // ============== AuthConfig Tests ============== + #[test] + fn host_config_debug_does_not_leak_password() { + let auth = AuthConfig::Password { + password: RedactedSecret::from("topsecret-pw"), + }; + let rendered = format!("{auth:?}"); + assert!( + !rendered.contains("topsecret-pw"), + "AuthConfig Debug leaked the password: {rendered}" + ); + } + #[test] fn test_auth_config_key_serialization() { let auth = AuthConfig::Key { path: "/path/to/key".to_string(), - passphrase: Some(Zeroizing::new("secret".to_string())), + passphrase: Some(RedactedSecret::from("secret")), }; let json = serde_json::to_string(&auth).unwrap(); assert!(json.contains("\"type\":\"key\"")); @@ -1476,11 +1490,15 @@ mod tests { #[test] fn test_auth_config_password_serialization() { let auth = AuthConfig::Password { - password: Zeroizing::new("secret123".to_string()), + password: RedactedSecret::from("secret123"), }; let json = serde_json::to_string(&auth).unwrap(); assert!(json.contains("\"type\":\"password\"")); - assert!(json.contains("\"secret123\"")); + assert!( + !json.contains("secret123"), + "password leaked in serialization" + ); + assert!(json.contains("[REDACTED]")); } #[test] @@ -1495,7 +1513,9 @@ mod tests { assert!(matches!(key, AuthConfig::Key { path, .. } if path == "/path/to/key")); assert!(matches!(agent, AuthConfig::Agent)); - assert!(matches!(password, AuthConfig::Password { password } if *password == "secret")); + assert!( + matches!(password, AuthConfig::Password { password } if password.as_str() == "secret") + ); } // ============== LimitsConfig Tests ============== @@ -1655,7 +1675,7 @@ mod tests { assert_eq!(config.port, 9050); assert_eq!(config.version, SocksVersion::Socks4); assert_eq!(config.username, Some("user".to_string())); - assert_eq!(config.password.as_deref().map(String::as_str), Some("pass")); + assert_eq!(config.password.as_deref(), Some("pass")); } #[test] diff --git a/src/mcp/tool_handlers/ssh_status.rs b/src/mcp/tool_handlers/ssh_status.rs index 265aa73..60d95df 100644 --- a/src/mcp/tool_handlers/ssh_status.rs +++ b/src/mcp/tool_handlers/ssh_status.rs @@ -244,7 +244,7 @@ mod tests { port: 2222, user: "user3".to_string(), auth: AuthConfig::Password { - password: zeroize::Zeroizing::new("secret".to_string()), + password: crate::config::RedactedSecret::from("secret"), }, description: None, host_key_verification: HostKeyVerification::default(), diff --git a/src/psrp/mod.rs b/src/psrp/mod.rs index b9ae762..be7d569 100644 --- a/src/psrp/mod.rs +++ b/src/psrp/mod.rs @@ -322,7 +322,7 @@ mod tests { port: 5986, user: "admin".to_string(), auth: crate::config::AuthConfig::Password { - password: zeroize::Zeroizing::new("pass".to_string()), + password: crate::config::RedactedSecret::from("pass"), }, description: None, host_key_verification: crate::config::HostKeyVerification::default(), diff --git a/src/psrp/pool.rs b/src/psrp/pool.rs index 003c1d0..931f1a8 100644 --- a/src/psrp/pool.rs +++ b/src/psrp/pool.rs @@ -165,7 +165,7 @@ mod tests { port: 5986, user: "admin".to_string(), auth: AuthConfig::Password { - password: zeroize::Zeroizing::new("pass".to_string()), + password: crate::config::RedactedSecret::from("pass"), }, description: None, host_key_verification: HostKeyVerification::default(), diff --git a/src/ssh/client.rs b/src/ssh/client.rs index e33d933..cd2a044 100644 --- a/src/ssh/client.rs +++ b/src/ssh/client.rs @@ -455,7 +455,7 @@ impl SshClient { proxy_addr.as_str(), target_addr, user, - pass, + pass.as_str(), ) .await .map_err(map_err)? @@ -537,14 +537,7 @@ impl SshClient { ) -> Result { match &host.auth { AuthConfig::Key { path, passphrase } => { - Self::auth_with_key( - handle, - host_name, - host, - path, - passphrase.as_ref().map(|s| s.as_str()), - ) - .await + Self::auth_with_key(handle, host_name, host, path, passphrase.as_deref()).await } AuthConfig::Password { password } => { Self::auth_with_password(handle, host_name, host, password).await diff --git a/src/winrm/mod.rs b/src/winrm/mod.rs index ae35115..eb94dfb 100644 --- a/src/winrm/mod.rs +++ b/src/winrm/mod.rs @@ -213,7 +213,7 @@ mod tests { port: 5986, user: "admin".to_string(), auth: crate::config::AuthConfig::Password { - password: zeroize::Zeroizing::new("pass".to_string()), + password: crate::config::RedactedSecret::from("pass"), }, description: None, host_key_verification: crate::config::HostKeyVerification::default(), @@ -249,7 +249,7 @@ mod tests { fn test_build_winrm_config_ntlm() { let mut host = test_host_config(); host.auth = crate::config::AuthConfig::Ntlm { - password: zeroize::Zeroizing::new("secret".to_string()), + password: crate::config::RedactedSecret::from("secret"), domain: Some("CORP".to_string()), }; let (cfg, creds) = build_winrm_config(&host).unwrap(); diff --git a/src/winrm/pool.rs b/src/winrm/pool.rs index 6932382..ed2e933 100644 --- a/src/winrm/pool.rs +++ b/src/winrm/pool.rs @@ -167,7 +167,7 @@ mod tests { port: 5986, user: "admin".to_string(), auth: AuthConfig::Password { - password: zeroize::Zeroizing::new("pass".to_string()), + password: crate::config::RedactedSecret::from("pass"), }, description: None, host_key_verification: HostKeyVerification::default(), From 65ee244ef0e913e1e2abb26c5fb1739a8dfcdebb Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 00:22:00 +0200 Subject: [PATCH 13/19] fix(config): migrate remaining test construction sites to RedactedSecret + fmt Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plans/2026-05-30-dependency-updates.md | 546 ++++++++++++++++++ .../2026-05-30-redacted-secret-newtype.md | 529 +++++++++++++++++ src/config/secret.rs | 10 +- tests/e2e_docker.rs | 4 +- tests/e2e_raspberry.rs | 7 +- tests/sftp_integration.rs | 7 +- tests/ssh_integration.rs | 7 +- tests/ssh_mock_server.rs | 4 +- tests/sudo_password_zeroizing.rs | 53 +- 9 files changed, 1134 insertions(+), 33 deletions(-) create mode 100644 docs/superpowers/plans/2026-05-30-dependency-updates.md create mode 100644 docs/superpowers/plans/2026-05-30-redacted-secret-newtype.md diff --git a/docs/superpowers/plans/2026-05-30-dependency-updates.md b/docs/superpowers/plans/2026-05-30-dependency-updates.md new file mode 100644 index 0000000..172b42b --- /dev/null +++ b/docs/superpowers/plans/2026-05-30-dependency-updates.md @@ -0,0 +1,546 @@ +# Dependency Update Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Bring all 24 outdated dependencies current — semver-compatible bumps via `cargo update`, plus reviewed major-version bumps (jsonwebtoken, sha2, russh, similar, serde-saphyr, opentelemetry stack) and removal of the winrm-rs git-fork patch — with zero regressions. + +**Architecture:** Phased. Each phase = one isolated, independently-revertable commit gated by `cargo build` (relevant features) + `cargo test --lib`. Semver-safe bumps first (cheap, low-risk), then one phase per major bump so a single regression never blocks the rest. WSL OOM safety: `--lib`, default parallelism, never concurrent build+test. + +**Tech Stack:** Rust 2024 (MSRV 1.94), cargo, cargo-outdated, cargo-machete, make ci. Crates touched: tokio, russh/russh-sftp, jsonwebtoken, sha2, similar, serde-saphyr, opentelemetry stack, winrm-rs, aws-sdk. + +--- + +## Pre-flight (Phase 0) + +**Files:** none (git + baseline only) + +- [ ] **Step 1: Stash/park current WIP** + +Current branch `security/redacted-secret-newtype` is dirty (`src/config/mod.rs`, `src/config/secret.rs`, `docs/`). Do NOT mix dep bumps into it. + +Run: +```bash +cd /home/muchini/mcp-ssh-bridge +git stash push -u -m "wip-redacted-secret before dep-update" -- src/config/mod.rs src/config/secret.rs +git status +``` +Expected: working tree clean except untracked `docs/` (the plan itself). If `docs/` was already tracked WIP, leave it. + +- [ ] **Step 2: Branch from clean main** + +Run: +```bash +git fetch origin +git checkout main && git pull --ff-only +git checkout -b chore/dep-updates-2026-05-30 +``` +Expected: on new branch, tree clean. + +- [ ] **Step 3: Capture green baseline** + +Run: +```bash +cargo build --all-features 2>&1 | tail -5 +cargo test --lib 2>&1 | tail -15 +``` +Expected: build OK, all lib tests pass. Record the pass count — it is the regression baseline for every later phase. If baseline is red, STOP and fix before bumping anything. + +- [ ] **Step 4: Snapshot lockfile** + +Run: +```bash +cp Cargo.lock /tmp/Cargo.lock.baseline +``` +Expected: copy made (for diffing what each phase moved). + +--- + +## Phase 1: Semver-compatible bumps (`cargo update`) + +**Files:** +- Modify: `Cargo.lock` (only — no `Cargo.toml` edits) + +Covers: aws-config, aws-sdk-ssm, clap_complete, const-hex, filetime, mimalloc, reqwest, russh 0.60.1→0.60.3, russh-sftp 2.1.1→2.3.0, serde_json, tokio, tokio-socks, tower-http, uuid. + +- [ ] **Step 1: Update within semver ranges** + +Run: +```bash +cargo update +diff <(grep '^name\|^version' /tmp/Cargo.lock.baseline) <(grep '^name\|^version' Cargo.lock) | head -60 +``` +Expected: lockfile moves the 14 compatible crates. No `Cargo.toml` change. + +- [ ] **Step 2: Build all features** + +Run: +```bash +cargo build --all-features 2>&1 | tail -5 +``` +Expected: compiles clean. + +- [ ] **Step 3: Test** + +Run: +```bash +cargo test --lib 2>&1 | tail -15 +``` +Expected: pass count == baseline. + +- [ ] **Step 4: Commit** + +```bash +git add Cargo.lock +git commit -m "chore(deps): update semver-compatible dependencies + +cargo update — patch/minor bumps within existing ranges: +tokio, russh 0.60.3, russh-sftp 2.3.0, reqwest, tower-http, +aws-config, aws-sdk-ssm, clap_complete, const-hex, mimalloc, +serde_json, tokio-socks, uuid, filetime (dev)." +``` + +--- + +## Phase 2: winrm-rs 1.0 → 1.1.2 + drop git patch (FIND-018) + +**Files:** +- Modify: `Cargo.toml:120` (winrm-rs version) +- Modify: `Cargo.toml:235-236` (remove `[patch.crates-io]` if fixed) + +Context: `Cargo.toml` pins winrm-rs to a git fork because crates.io 1.0 declared an obsolete reqwest feature (`webpki-roots`) that broke `cargo outdated`. 1.1.2 is now published and may carry the fix — if so, the patch is dead weight. + +- [ ] **Step 1: Check whether 1.1.2 fixes the reqwest feature** + +Run: +```bash +cargo info winrm-rs@1.1.2 2>&1 | head -20 +# Inspect upstream Cargo.toml for the obsolete feature: +curl -sL https://crates.io/api/v1/crates/winrm-rs/1.1.2/download -o /tmp/winrm.crate \ + && tar xzf /tmp/winrm.crate -C /tmp \ + && grep -n "webpki-roots\|reqwest" /tmp/winrm-rs-1.1.2/Cargo.toml +``` +Expected: confirm `reqwest` no longer references `webpki-roots`. If the obsolete feature is gone → patch can be removed (Step 2a). If still present → keep patch, bump the git rev instead (Step 2b). + +- [ ] **Step 2a: Bump version + remove patch (if fixed)** + +In `Cargo.toml` line 120, change: +```toml +winrm-rs = { version = "1.0", optional = true } +``` +to: +```toml +winrm-rs = { version = "1.1", optional = true } +``` +Then delete the entire patch block (lines ~229-236): +```toml +# ============================================================================= +# Patch overrides (FIND-018 / audit 2026-05-09) +# ============================================================================= +[patch.crates-io] +winrm-rs = { git = "https://github.com/muchiny/winrm-rs.git", rev = "573dadf5abcaed681f65999f216164c9f33a6250" } +``` + +- [ ] **Step 2b: Fallback — keep patch, point fork at 1.1.2 (if NOT fixed)** + +Leave line 120 as `version = "1.1"`, update the fork to a rev that tracks 1.1.2, keep the `[patch.crates-io]` block. Document why in a refreshed comment dated 2026-05-30. + +- [ ] **Step 3: Build winrm + psrp features** + +Run: +```bash +cargo build --features winrm 2>&1 | tail -5 +cargo build --features psrp 2>&1 | tail -5 +``` +Expected: both compile. (psrp depends on winrm.) + +- [ ] **Step 4: Verify outdated no longer lists winrm-rs as "Removed"** + +Run: +```bash +cargo outdated --root-deps-only 2>&1 | grep -i winrm || echo "winrm-rs current" +``` +Expected: `winrm-rs current` (or shows it at 1.1.2 with no "Removed"). + +- [ ] **Step 5: Test** + +Run: +```bash +cargo test --lib --features psrp 2>&1 | tail -15 +``` +Expected: pass count >= baseline (psrp adds tests). + +- [ ] **Step 6: Commit** + +```bash +git add Cargo.toml Cargo.lock +git commit -m "chore(deps): bump winrm-rs 1.0 -> 1.1.2, drop git-fork patch (FIND-018) + +crates.io 1.1.2 ships the reqwest feature fix the local fork carried; +[patch.crates-io] override removed. Resolves the cargo-outdated +'Removed' status. Update Known Advisories in CLAUDE.md if the patch +removal changes the advisory set." +``` + +--- + +## Phase 3: OpenTelemetry stack 0.31 → 0.32 (feature `otel`) + +**Files:** +- Modify: `Cargo.toml:144-147` (opentelemetry, opentelemetry_sdk, opentelemetry-otlp, tracing-opentelemetry) +- Likely modify: telemetry init module (find via grep in Step 1) + +Coordinated bump — all four versions move together. Metrics/logs/traces APIs have been stable since 0.30, so risk is low, but `tracing-opentelemetry` must match: 0.32 → **0.33**. + +- [ ] **Step 1: Locate the otel init code** + +Run: +```bash +grep -rln "opentelemetry\|SdkTracerProvider\|tracing_opentelemetry\|OtelLayer\|otlp" src --include="*.rs" +``` +Expected: a small set (telemetry/observability module). Read each before editing. + +- [ ] **Step 2: Bump the four versions** + +In `Cargo.toml`: +```toml +opentelemetry = { version = "0.32", optional = true } +opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"], optional = true } +opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic"], optional = true } +tracing-opentelemetry = { version = "0.33", optional = true } +``` + +- [ ] **Step 3: Build the otel feature** + +Run: +```bash +cargo build --features otel 2>&1 | tail -20 +``` +Expected: compiles. If errors, they will be in the init module — most likely the `Resource::builder()` / `SdkTracerProvider` builder API (stable since 0.28, but double-check exporter constructor signatures against the 0.32 docs). Fix in the module from Step 1. + +- [ ] **Step 4: Test** + +Run: +```bash +cargo test --lib --features otel 2>&1 | tail -15 +``` +Expected: pass count >= baseline. + +- [ ] **Step 5: Commit** + +```bash +git add Cargo.toml Cargo.lock src/ +git commit -m "chore(deps): bump opentelemetry stack 0.31 -> 0.32 + +opentelemetry/_sdk/-otlp 0.32, tracing-opentelemetry 0.33 (must match). +otel feature only; trace/metric/log APIs stable since 0.30." +``` + +--- + +## Phase 4: similar 2.7 → 3.1 (default feature) + +**Files:** +- Modify: `Cargo.toml:148` (similar = "3") +- Possibly touch: `src/domain/diff.rs` (only consumer) + +Only consumer is `src/domain/diff.rs` using `ChangeTag`, `TextDiff`, `TextDiff::from_lines` — all stable across the 2→3 major. The 3.0 break is mostly MSRV/internal. + +- [ ] **Step 1: Bump version** + +In `Cargo.toml` line 148: +```toml +similar = "3" +``` + +- [ ] **Step 2: Build + test the diff module** + +Run: +```bash +cargo build 2>&1 | tail -5 +cargo test --lib diff 2>&1 | tail -15 +``` +Expected: compiles; `domain::diff` tests pass. If `TextDiff::from_lines`/`ChangeTag` signatures shifted, adjust `src/domain/diff.rs:203-209`. + +- [ ] **Step 3: Commit** + +```bash +git add Cargo.toml Cargo.lock src/domain/diff.rs +git commit -m "chore(deps): bump similar 2 -> 3" +``` + +--- + +## Phase 5: sha2 0.10 → 0.11 (default feature) + +**Files:** +- Modify: `Cargo.toml:98` (sha2 = "0.11") +- Possibly touch: `src/ssh/sftp.rs:12,268,462`, `src/security/recording.rs:15,399` + +Direct API (`Sha256::new`, `.update`, `.finalize`, `Digest`) is unchanged in 0.11. The 0.11 break is the underlying `digest` 0.11 / `hybrid-array` migration: the `finalize()` output type changes from `GenericArray` to `hybrid_array::Array`. Check that downstream `const_hex::encode(...)` and slice uses still accept it (they take `AsRef<[u8]>`, which both implement). + +- [ ] **Step 1: Bump version** + +In `Cargo.toml` line 98: +```toml +sha2 = "0.11" +``` + +- [ ] **Step 2: Build** + +Run: +```bash +cargo build 2>&1 | tail -20 +``` +Expected: compiles. If `const_hex::encode(h.finalize())` fails on the new output type, wrap with `.as_slice()` / `&h.finalize()[..]` at `src/ssh/sftp.rs:341,537`. + +- [ ] **Step 3: Test the hashing consumers** + +Run: +```bash +cargo test --lib sftp 2>&1 | tail -10 +cargo test --lib recording 2>&1 | tail -10 +``` +Expected: checksum + recording-hash tests pass (proves the hash bytes are byte-identical post-bump). + +- [ ] **Step 4: Guard against a duplicate sha2 in the tree** + +Run: +```bash +cargo tree -i sha2 2>&1 | head -30 +``` +Expected: ideally a single 0.11. If `ssh-key`/russh still pull sha2 0.10, that's a tolerated duplicate (not a break) — note it; do not force-downgrade. + +- [ ] **Step 5: Commit** + +```bash +git add Cargo.toml Cargo.lock src/ +git commit -m "chore(deps): bump sha2 0.10 -> 0.11 + +Direct API (Sha256::new/update/finalize/Digest) unchanged; 0.11 moves +to digest 0.11 / hybrid-array output. sftp checksum + audit-recording +hash tests confirm byte-identical output." +``` + +--- + +## Phase 6: jsonwebtoken 9.3 → 10.4 (feature `http`) + +**Files:** +- Modify: `Cargo.toml:151` (jsonwebtoken = "10") +- Likely touch: `src/mcp/transport/oauth.rs:34,217-239,504-535` + +Only consumer is the OAuth/JWT validator in `oauth.rs` (HTTP transport). All APIs we use exist in v10: `DecodingKey::from_rsa_components`/`from_rsa_pem`, `Validation::new`, `set_issuer`, `set_audience`, `set_required_spec_claims(&["..."])`, `validate_exp`, `validate_nbf`, `leeway`, `decode`, `decode_header`, `EncodingKey::from_rsa_pem`, `encode`, `Header`. v10 hardens defaults (e.g. `aud` rejection) — our code already requires all four spec claims, so semantics should hold. **Security-sensitive: the existing oauth test suite is the gate, do not weaken it.** + +- [ ] **Step 1: Bump version** + +In `Cargo.toml` line 151: +```toml +jsonwebtoken = "10" +``` + +- [ ] **Step 2: Build the http feature** + +Run: +```bash +cargo build --features http 2>&1 | tail -20 +``` +Expected: compiles. Likely-affected spots if v10 shifted a signature: +- `set_required_spec_claims` argument type (still `&[&str]` in v10 per docs — should be fine). +- `from_rsa_components`/`from_rsa_pem` still return `Result` — already handled with `map_err`. +Fix any signature drift inside `src/mcp/transport/oauth.rs` only. + +- [ ] **Step 3: Run the full oauth test suite (security gate)** + +Run: +```bash +cargo test --lib --features http oauth 2>&1 | tail -25 +``` +Expected: every oauth test passes — especially the negative cases (missing `sub`, wrong `aud`, expired token, bad signature). If any negative test now passes a token it should reject, STOP — v10 default change must be re-pinned explicitly in `Validation`. + +- [ ] **Step 4: Commit** + +```bash +git add Cargo.toml Cargo.lock src/mcp/transport/oauth.rs +git commit -m "chore(deps): bump jsonwebtoken 9 -> 10 + +OAuth/JWT validator (http feature). v10 hardens claim defaults; +explicit set_required_spec_claims + set_issuer/set_audience preserved +(FIND-007). Full oauth negative-case suite verified." +``` + +--- + +## Phase 7: russh 0.60 → 0.61 (default feature, core SSH) + +**Files:** +- Modify: `Cargo.toml:68` (russh = "0.61") +- Likely touch: `src/ssh/{client,session,connector,sftp,known_hosts,mod}.rs`, `src/mcp/standard_tool.rs` + +Highest blast radius (7 files) and the core transport. 0.61 is a minor-major in the 0.x sense — review the russh CHANGELOG for `client::Handler` trait changes, `Config`, channel/auth signatures, and the kex/mac algorithm registration in `src/ssh/client.rs:48-53`. + +- [ ] **Step 1: Read russh 0.61 changelog deltas** + +Run: +```bash +cargo info russh@0.61.1 2>&1 | head -20 +``` +Then check context7 (`/warp-tech/russh` or via resolve-library-id "russh") for `client::Handler` / `Config` API changes 0.60→0.61 before editing. + +- [ ] **Step 2: Bump version** + +In `Cargo.toml` line 68: +```toml +russh = "0.61" +``` + +- [ ] **Step 3: Build** + +Run: +```bash +cargo build 2>&1 | tail -30 +``` +Expected: compiles, or a small set of trait/signature errors in the ssh adapter files. Fix each against the 0.61 API. Common 0.x russh breaks: `Handler::check_server_key` signature, `Channel` async return types, `Preferred` algorithm config fields (`src/ssh/client.rs`). + +- [ ] **Step 4: Full SSH adapter test pass** + +Run: +```bash +cargo test --lib ssh 2>&1 | tail -25 +``` +Expected: pass count >= baseline for ssh modules. The russh/sftp mock harness (added in commit 4c5cca0) exercises the adapter — it must stay green. + +- [ ] **Step 5: Commit** + +```bash +git add Cargo.toml Cargo.lock src/ +git commit -m "chore(deps): bump russh 0.60 -> 0.61 + +Core SSH transport. Adapter (client/session/connector/sftp/known_hosts) +updated for 0.61 API. Mock harness green." +``` + +--- + +## Phase 8: serde-saphyr =0.0.21 → 0.0.27 (default feature, config parser) + +**Files:** +- Modify: `Cargo.toml:74` (serde-saphyr = "=0.0.27") +- Likely touch: `src/domain/yaml.rs:50-84`, `src/error.rs:132` + +Hard-pinned with `=` because it's 0.0.x — every release is potentially breaking. The central hardened wrapper is `src/domain/yaml.rs` (`Options`, `Budget`, `from_str_with_options`). Security-relevant: this parses untrusted YAML config + runbooks with budget limits. **Bump one minor at a time and read each changelog** — 0.21→0.27 is six releases. + +- [ ] **Step 1: Review the changelog 0.0.21 → 0.0.27** + +Run: +```bash +cargo info serde-saphyr@0.0.27 2>&1 | head -20 +``` +Read the repo CHANGELOG (or each version's release notes) for changes to `Options`, `Budget`, `from_str_with_options`, `from_str`, and the `Error` type. Note any field renames/additions to `Budget` (the hardening in `yaml.rs:51-57` depends on its exact fields). + +- [ ] **Step 2: Bump the pin** + +In `Cargo.toml` line 74: +```toml +serde-saphyr = "=0.0.27" +``` + +- [ ] **Step 3: Build** + +Run: +```bash +cargo build 2>&1 | tail -25 +``` +Expected: compiles, or errors in `src/domain/yaml.rs` (Budget/Options fields) and `src/error.rs:132` (Error `#[from]`). Fix against the new API, preserving every existing budget limit. + +- [ ] **Step 4: Run YAML + config + runbook tests (security gate)** + +Run: +```bash +cargo test --lib yaml 2>&1 | tail -15 +cargo test --lib config 2>&1 | tail -15 +cargo test --lib runbook 2>&1 | tail -15 +``` +Expected: all pass — especially the budget/DoS-limit tests in `domain::yaml` (they prove the hardening still trips on oversized/over-deep input). If a budget test no longer trips, the field mapping is wrong — STOP and re-check Step 1. + +- [ ] **Step 5: Commit** + +```bash +git add Cargo.toml Cargo.lock src/domain/yaml.rs src/error.rs +git commit -m "chore(deps): bump serde-saphyr 0.0.21 -> 0.0.27 + +Hardened YAML wrapper (domain/yaml.rs) Budget/Options preserved; +DoS budget-limit tests verified. Pin kept as '=' (0.0.x)." +``` + +--- + +## Phase 9: Final verification + integration + +**Files:** none (verification only) + +- [ ] **Step 1: Confirm nothing left outdated (except intentionally pinned)** + +Run: +```bash +cargo outdated --root-deps-only 2>&1 +``` +Expected: empty or only entries you consciously chose not to bump. Document any deliberate holds. + +- [ ] **Step 2: No unused deps introduced** + +Run: +```bash +cargo machete 2>&1 | tail -5 +``` +Expected: "didn't find any unused dependencies". + +- [ ] **Step 3: Full CI gate** + +Run: +```bash +make ci 2>&1 | tail -40 +``` +Expected: fmt-check, clippy (`-D warnings`), test, audit, typos all green. `cargo audit` is the check for whether any bump changed the advisory set vs `deny.toml` (6 ignored advisories — see CLAUDE.md "Known Advisories"). + +- [ ] **Step 4: Build the full feature matrix** + +Run: +```bash +cargo build --all-features 2>&1 | tail -5 +cargo build --no-default-features 2>&1 | tail -5 +cargo build --features all-protocols 2>&1 | tail -5 +``` +Expected: every combination compiles. + +- [ ] **Step 5: Update advisory/docs if changed** + +If `cargo audit` / `make ci` surfaced a resolved or new advisory, update the "Known Advisories" list in `CLAUDE.md` and `deny.toml`. Commit separately: +```bash +git add CLAUDE.md deny.toml +git commit -m "docs(deps): refresh Known Advisories after dependency updates" +``` + +- [ ] **Step 6: Restore parked WIP (optional)** + +If the secret-newtype work resumes after this branch merges: +```bash +git checkout security/redacted-secret-newtype +git stash pop +``` + +--- + +## Risk Summary (review-order, low → high) + +| Phase | Crate(s) | Blast radius | Risk | Gate | +|---|---|---|---|---| +| 1 | 14 semver-compat | Cargo.lock only | minimal | build + test | +| 2 | winrm-rs 1.1.2 | Cargo.toml (patch removal) | low | winrm/psrp build | +| 3 | otel 0.32 | otel init module | low | otel build+test | +| 4 | similar 3 | `domain/diff.rs` | low | diff tests | +| 5 | sha2 0.11 | sftp, recording (2 sites) | low-mod | checksum/hash tests | +| 6 | jsonwebtoken 10 | `oauth.rs` | moderate (security) | oauth negative-case suite | +| 7 | russh 0.61 | 7 ssh files | mod-high | ssh mock harness | +| 8 | serde-saphyr 0.0.27 | `yaml.rs`, `error.rs` | high (untrusted-input parser) | budget/DoS tests | + +If any single phase resists within ~30 min of effort, commit the phases before it, open a focused follow-up for the blocker, and ship the rest — the phase isolation is the whole point. diff --git a/docs/superpowers/plans/2026-05-30-redacted-secret-newtype.md b/docs/superpowers/plans/2026-05-30-redacted-secret-newtype.md new file mode 100644 index 0000000..970278e --- /dev/null +++ b/docs/superpowers/plans/2026-05-30-redacted-secret-newtype.md @@ -0,0 +1,529 @@ +# RedactedSecret Newtype — Secret Leak Hardening Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make every in-memory credential structurally impossible to leak through `Debug`, `Display`, or `Serialize`, while keeping zeroize-on-drop — by replacing scattered `Zeroizing` / `String` secret fields with a single `RedactedSecret` newtype. + +**Architecture:** Introduce one in-crate newtype `RedactedSecret(Zeroizing)` in `src/config/secret.rs` with hand-written `Debug` → `"[REDACTED]"`, `Serialize` → `"[REDACTED]"`, transparent `Deserialize` (reads a plain string), and `Deref` so existing call sites (`.as_str()`, `&secret` → `&str` coercion) compile unchanged. Swap the six credential fields across `config/types.rs` to use it. Add one defensive `Bearer` sanitizer pattern as an independent hardening. + +**Tech Stack:** Rust 2024 (MSRV 1.94), `serde`, `zeroize = "1"` (already a dependency), `regex` (sanitizer). No new crates. + +--- + +## Background — Findings This Plan Fixes + +From the audit (verified against `zeroize-1.8.2/src/lib.rs:622` and `:725`): + +- **F1 — Debug leak (real, latent):** `zeroize::Zeroizing` derives `Debug` (forwards inner value). `AuthConfig`, `SocksProxyConfig`, `HostConfig` all `#[derive(Debug)]` and contain secrets → any `{:?}` prints the password in cleartext (`Zeroizing("hunter2")`). No current call site triggers it; fix is regression-proofing for a security tool. +- **F2 — Serialize leak (real, latent):** `Zeroizing` impls `Serialize` (forwards inner value). The config types derive `Serialize`. No production path serializes a full `Config` (only the round-trip unit tests at `types.rs:1464-1484` do), but the derive is a live footgun. +- **F3 — AWX token not protected:** `AwxConfig::token: String` (`types.rs:59`) — plain `String`, neither zeroized nor redacted, inconsistent with the five other secret fields. +- **F4 — No `Bearer` redaction pattern:** opaque (non-JWT) bearer tokens in command output rely solely on the entropy detector (on by default). If an operator sets `entropy_detection: false`, opaque bearer tokens leak. A literal pattern closes that gap cheaply. + +**Explicitly out of scope (do NOT change):** the `secrecy` crate migration (rejected — new dependency, large blast radius), and F5 (heuristic false-negatives — inherent, no code action). + +## File Structure + +- **Create** `src/config/secret.rs` — the `RedactedSecret` newtype + its trait impls + unit tests. One responsibility: a leak-proof, zeroizing string secret. +- **Modify** `src/config/mod.rs` — declare `mod secret;` and re-export `RedactedSecret`. +- **Modify** `src/config/types.rs` — swap six field types to `RedactedSecret`; update the secret-bearing unit tests; add a `Debug`-doesn't-leak regression test. +- **Modify** `src/ssh/client.rs:452` — SOCKS proxy password call site: pass `.as_str()` explicitly (defensive against a generic tokio_socks signature). All other SSH call sites compile unchanged via `Deref` coercion. +- **Modify** `src/security/sanitizer.rs` — add one `Bearer` pattern definition + a test (F4, independent). + +The AWX call sites (`&awx.token` in ~16 handlers) compile unchanged: the use-case functions take `token: &str`, and `&RedactedSecret` deref-coerces to `&str`. + +--- + +### Task 1: `RedactedSecret` newtype + +**Files:** +- Create: `src/config/secret.rs` +- Modify: `src/config/mod.rs` + +- [ ] **Step 1: Write the failing tests** + +Create `src/config/secret.rs` with only the test module first (the type does not exist yet — this must fail to compile, which counts as a failing test): + +```rust +//! `RedactedSecret`: a string credential that zeroizes on drop and is +//! structurally incapable of leaking through `Debug`, `Display`, or +//! `Serialize`. Use it for every in-memory secret (passwords, passphrases, +//! API tokens) instead of `String` or bare `Zeroizing`. + +#[cfg(test)] +mod tests { + use super::*; + + const SECRET: &str = "hunter2-super-secret"; + + #[test] + fn debug_does_not_leak() { + let s = RedactedSecret::from(SECRET); + let rendered = format!("{s:?}"); + assert!(!rendered.contains(SECRET), "Debug leaked the secret: {rendered}"); + assert_eq!(rendered, "[REDACTED]"); + } + + #[test] + fn serialize_does_not_leak() { + let s = RedactedSecret::from(SECRET); + let json = serde_json::to_string(&s).unwrap(); + assert!(!json.contains(SECRET), "Serialize leaked the secret: {json}"); + assert_eq!(json, "\"[REDACTED]\""); + } + + #[test] + fn deserialize_reads_plain_string() { + let s: RedactedSecret = serde_json::from_str("\"hunter2-super-secret\"").unwrap(); + assert_eq!(s.as_str(), SECRET); + } + + #[test] + fn deref_and_as_str_expose_value_for_use() { + let s = RedactedSecret::from(SECRET); + // Deref lets it coerce where &str is expected. + let via_deref: &str = &s; + assert_eq!(via_deref, SECRET); + assert_eq!(s.as_str(), SECRET); + assert_eq!(s.len(), SECRET.len()); // str method via Deref + } + + #[test] + fn clone_is_independent() { + let a = RedactedSecret::from(SECRET); + let b = a.clone(); + assert_eq!(a.as_str(), b.as_str()); + } +} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cargo test --lib config::secret 2>&1 | tail -20` +Expected: compile error — `cannot find type RedactedSecret in this scope` (the type is not defined yet). + +- [ ] **Step 3: Implement the newtype** + +Insert this above the `#[cfg(test)]` module in `src/config/secret.rs`: + +```rust +use std::fmt; +use std::ops::Deref; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use zeroize::Zeroizing; + +/// A string secret that is wiped from memory on drop and never reveals its +/// contents through `Debug`, `Display`, or `Serialize`. +/// +/// Access the underlying value explicitly with [`RedactedSecret::as_str`] or +/// via `Deref` (so `&secret` coerces to `&str` at call sites). +#[derive(Clone)] +pub struct RedactedSecret(Zeroizing); + +impl RedactedSecret { + /// Wrap an owned `String` as a redacted, zeroizing secret. + #[must_use] + pub fn new(value: String) -> Self { + Self(Zeroizing::new(value)) + } + + /// Borrow the secret as `&str` for use at an authentication boundary. + #[must_use] + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl From for RedactedSecret { + fn from(value: String) -> Self { + Self::new(value) + } +} + +impl From<&str> for RedactedSecret { + fn from(value: &str) -> Self { + Self::new(value.to_owned()) + } +} + +impl Deref for RedactedSecret { + type Target = str; + + fn deref(&self) -> &str { + self.0.as_str() + } +} + +impl fmt::Debug for RedactedSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[REDACTED]") + } +} + +impl fmt::Display for RedactedSecret { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[REDACTED]") + } +} + +impl Serialize for RedactedSecret { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str("[REDACTED]") + } +} + +impl<'de> Deserialize<'de> for RedactedSecret { + fn deserialize>(deserializer: D) -> Result { + Ok(Self::new(String::deserialize(deserializer)?)) + } +} +``` + +- [ ] **Step 4: Wire the module into the config crate** + +In `src/config/mod.rs`, add `mod secret;` after the existing `mod` lines and re-export the type. The block at the top becomes: + +```rust +mod loader; +pub mod secret; +pub mod ssh_config; +pub mod types; +mod watcher; + +pub use loader::{default_config_path, load_config}; +pub use secret::RedactedSecret; +pub use types::*; +pub use watcher::ConfigWatcher; +``` + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `cargo test --lib config::secret 2>&1 | tail -20` +Expected: PASS — `debug_does_not_leak`, `serialize_does_not_leak`, `deserialize_reads_plain_string`, `deref_and_as_str_expose_value_for_use`, `clone_is_independent` all green. + +- [ ] **Step 6: Lint** + +Run: `cargo clippy --lib 2>&1 | tail -20` +Expected: no warnings on `src/config/secret.rs`. + +- [ ] **Step 7: Commit** + +```bash +git add src/config/secret.rs src/config/mod.rs +git commit -m "feat(config): add RedactedSecret newtype (leak-proof zeroizing secret)" +``` + +--- + +### Task 2: Swap config credential fields to `RedactedSecret` + +**Files:** +- Modify: `src/config/types.rs` (field declarations: `AuthConfig::Password.password`, `AuthConfig::Key.passphrase`, `AuthConfig::Ntlm.password`, `HostConfig.sudo_password`, `SocksProxyConfig.password`; the import; the secret-bearing tests) +- Modify: `src/ssh/client.rs:452` (SOCKS password call site) + +- [ ] **Step 1: Write the failing regression test** + +In the `tests` module of `src/config/types.rs`, add a new test that proves `Debug` on a host no longer leaks the password: + +```rust + #[test] + fn host_config_debug_does_not_leak_password() { + let auth = AuthConfig::Password { + password: RedactedSecret::from("topsecret-pw"), + }; + let rendered = format!("{auth:?}"); + assert!( + !rendered.contains("topsecret-pw"), + "AuthConfig Debug leaked the password: {rendered}" + ); + } +``` + +Also update the existing serialize test `test_auth_config_password_serialization` (around `types.rs:1477`) to assert redaction instead of leakage: + +```rust + #[test] + fn test_auth_config_password_serialization() { + let auth = AuthConfig::Password { + password: RedactedSecret::from("secret123"), + }; + let json = serde_json::to_string(&auth).unwrap(); + assert!(json.contains("\"type\":\"password\"")); + assert!(!json.contains("secret123"), "password leaked in serialization"); + assert!(json.contains("[REDACTED]")); + } +``` + +And update the `passphrase` construction in `test_auth_config_key_serialization` (around `types.rs:1462`) so it compiles against the new type: + +```rust + let auth = AuthConfig::Key { + path: "/path/to/key".to_string(), + passphrase: Some(RedactedSecret::from("secret")), + }; +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `cargo test --lib config::types 2>&1 | tail -25` +Expected: compile error — `RedactedSecret` not in scope in `types.rs` and/or `AuthConfig::Password.password` still expects `Zeroizing`. This is the failing state. + +- [ ] **Step 3: Swap the field types and import** + +In `src/config/types.rs`, change the import line 4 from: + +```rust +use zeroize::Zeroizing; +``` + +to: + +```rust +use crate::config::secret::RedactedSecret; +``` + +(Remove the `zeroize::Zeroizing` import if no other use remains in the file. If `cargo build` later reports it still used elsewhere, keep both lines.) + +Then change each secret field declaration: + +- `HostConfig::sudo_password` (≈line 256): + ```rust + pub sudo_password: Option, + ``` +- `SocksProxyConfig::password` (≈line 465): + ```rust + pub password: Option, + ``` +- `AuthConfig::Key::passphrase` (≈line 497): + ```rust + passphrase: Option, + ``` +- `AuthConfig::Password::password` (≈line 501): + ```rust + password: RedactedSecret, + ``` +- `AuthConfig::Ntlm::password` (≈line 506, under `#[cfg(feature = "winrm")]`): + ```rust + password: RedactedSecret, + ``` + +- [ ] **Step 4: Fix the SOCKS call site** + +In `src/ssh/client.rs`, the SOCKS branch around line 452 passes `pass` (now `&RedactedSecret`) into `tokio_socks`. Make the `&str` explicit so it is robust against a generic signature. Change the password argument from `pass` to `pass.as_str()`: + +```rust + if let (Some(user), Some(pass)) = (&socks.username, &socks.password) { + tokio_socks::tcp::Socks5Stream::connect_with_password( + proxy_addr.as_str(), + target_addr, + user, + pass.as_str(), + ) + .await + .map_err(map_err)? +``` + +(The `AuthConfig::Key` passphrase site at `client.rs:545` — `passphrase.as_ref().map(|s| s.as_str())` — and the `auth_with_password` site already work: `as_str()` exists on `RedactedSecret` and `&RedactedSecret` coerces to `&str`. No change needed there.) + +- [ ] **Step 5: Build to find any remaining call sites** + +Run: `cargo build --lib --all-features 2>&1 | tail -30` +Expected: clean build. If the compiler flags a construction site (e.g. a test building `AuthConfig::Password { password: Zeroizing::new(...) }` or `sudo_password: Some("x".to_string())`), fix it to `RedactedSecret::from(...)`. The `--all-features` flag ensures the `winrm`/`socks` `#[cfg]` paths compile. + +- [ ] **Step 6: Run the config + ssh tests to verify they pass** + +Run: `cargo test --lib config:: 2>&1 | tail -25` +Expected: PASS — including `host_config_debug_does_not_leak_password`, `test_auth_config_password_serialization` (now asserts redaction), and all deserialization round-trip tests (deserialize still reads plain YAML/JSON strings). + +Run: `cargo test --lib ssh::client 2>&1 | tail -20` +Expected: PASS. + +- [ ] **Step 7: Lint** + +Run: `cargo clippy --lib --all-features 2>&1 | tail -20` +Expected: no warnings. + +- [ ] **Step 8: Commit** + +```bash +git add src/config/types.rs src/ssh/client.rs +git commit -m "refactor(config): use RedactedSecret for SSH/SOCKS/sudo credentials (fixes F1/F2 leak)" +``` + +--- + +### Task 3: Protect the AWX OAuth token (F3) + +**Files:** +- Modify: `src/config/types.rs` (`AwxConfig::token`, ≈line 59) + +- [ ] **Step 1: Write the failing test** + +In the `tests` module of `src/config/types.rs`, add: + +```rust + #[test] + fn awx_token_is_redacted_in_debug() { + let awx = AwxConfig { + ssh_host: "h".to_string(), + url: "https://awx".to_string(), + token: RedactedSecret::from("awx-oauth-token-123"), + api_timeout: 30, + verify_ssl: true, + }; + let rendered = format!("{awx:?}"); + assert!( + !rendered.contains("awx-oauth-token-123"), + "AwxConfig Debug leaked the token: {rendered}" + ); + } +``` + +(If `AwxConfig` has additional fields, match the actual struct definition at `types.rs:51` — keep every field, only `token` uses `RedactedSecret`.) + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cargo test --lib config::types::tests::awx_token_is_redacted_in_debug 2>&1 | tail -15` +Expected: compile error — `token` field still expects `String`. + +- [ ] **Step 3: Swap the field type** + +In `src/config/types.rs`, change `AwxConfig::token` (≈line 59) from: + +```rust + pub token: String, +``` + +to: + +```rust + pub token: RedactedSecret, +``` + +- [ ] **Step 4: Build to confirm call sites coerce** + +Run: `cargo build --lib --all-features 2>&1 | tail -20` +Expected: clean build. The ~16 handlers passing `&awx.token` to use-case functions taking `token: &str` deref-coerce automatically. If any AWX test constructs `AwxConfig { token: "...".to_string(), .. }`, change it to `token: "...".into()`. + +- [ ] **Step 5: Run AWX + config tests to verify they pass** + +Run: `cargo test --lib awx 2>&1 | tail -20 && cargo test --lib config::types 2>&1 | tail -15` +Expected: PASS — including `awx_token_is_redacted_in_debug`. + +- [ ] **Step 6: Commit** + +```bash +git add src/config/types.rs +git commit -m "fix(config): wrap AwxConfig token in RedactedSecret (fixes F3 — zeroize + redact)" +``` + +--- + +### Task 4: Add `Bearer` sanitizer pattern (F4, independent) + +**Files:** +- Modify: `src/security/sanitizer.rs` (pattern definitions + a test) + +- [ ] **Step 1: Write the failing test** + +In the `tests` module of `src/security/sanitizer.rs`, add: + +```rust + #[test] + fn test_bearer_opaque_token_redacted() { + let sanitizer = Sanitizer::with_defaults(); + let input = "curl -H 'Authorization: Bearer A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6'"; + let out = sanitizer.sanitize(input); + assert!( + !out.contains("A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6"), + "opaque bearer token leaked: {out}" + ); + assert!(out.contains("[BEARER_TOKEN_REDACTED]")); + } +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cargo test --lib security::sanitizer::tests::test_bearer_opaque_token_redacted 2>&1 | tail -15` +Expected: FAIL — `assert!(out.contains("[BEARER_TOKEN_REDACTED]"))` fails (no such pattern; entropy detector either substitutes a different marker or, if disabled, leaves the token). + +- [ ] **Step 3: Add the pattern definition** + +In `src/security/sanitizer.rs`, locate the TIER 1 "Highly Specific Patterns" block (around line 417, where `ghp_` / GitHub patterns are defined as `PatternDef` entries). Add a new entry alongside them, matching the existing `PatternDef` struct shape used in that file: + +```rust + // Authorization: Bearer — closes the gap when the + // entropy detector is disabled. Placed in TIER 1 so it runs before + // the generic token patterns. + PatternDef { + name: "bearer_token", + pattern: r"(?i)bearer\s+[A-Za-z0-9._~+/=-]{16,}", + replacement: "Bearer [BEARER_TOKEN_REDACTED]", + }, +``` + +Use the exact field names of the local `PatternDef` struct (check the surrounding entries — they may be `name` / `pattern` / `replacement` or a tuple; match what is already there). The replacement keeps the literal `Bearer ` prefix and redacts only the token so the line stays readable. + +If the sanitizer keeps a "keyword pre-filter" list (Aho-Corasick literals, around line 290), confirm `"bearer"` is already present (it is, at `sanitizer.rs:297`) so the pattern is reached. + +- [ ] **Step 4: Run the test to verify it passes** + +Run: `cargo test --lib security::sanitizer::tests::test_bearer_opaque_token_redacted 2>&1 | tail -15` +Expected: PASS. + +- [ ] **Step 5: Run the full sanitizer suite (guard against pattern regressions)** + +Run: `cargo test --lib security::sanitizer 2>&1 | tail -25` +Expected: PASS — in particular the existing `test_pattern_count` (asserts `>= 50` builtin patterns) still holds, and the JWT bearer test `sanitizer.rs:1239` still passes (JWT pattern is more specific and ordered before this one, or both redact — verify the JWT test still asserts its `[JWT_TOKEN_REDACTED]` marker; if the new pattern now wins on JWT input, relax that test to assert "no cleartext token" rather than the specific marker). + +- [ ] **Step 6: Lint + commit** + +Run: `cargo clippy --lib 2>&1 | tail -15` +Expected: no warnings. + +```bash +git add src/security/sanitizer.rs +git commit -m "feat(security): redact opaque Authorization: Bearer tokens (fixes F4)" +``` + +--- + +### Task 5: Full verification + +**Files:** none (verification only) + +- [ ] **Step 1: Run the quick CI gate** + +Run: `make ci 2>&1 | tail -40` +Expected: `fmt-check`, `lint`, `test`, `audit`, `typos` all green. (Per WSL safety rules, this uses default parallelism — do not raise `-j`.) + +- [ ] **Step 2: Confirm no secret type still derives a leaking Debug/Serialize** + +Run: `grep -rn 'Zeroizing' src/ --include='*.rs'` +Expected: no matches in `config/types.rs` field declarations (only — if any — inside `secret.rs` as the newtype's inner type). Any remaining hit is a missed secret field; wrap it in `RedactedSecret`. + +- [ ] **Step 3: Commit any formatting fixups** + +```bash +cargo fmt +git diff --quiet || git commit -am "style: rustfmt after RedactedSecret migration" +``` + +--- + +## Self-Review + +**Spec coverage:** +- F1 (Debug leak) → Task 1 (`debug_does_not_leak`) + Task 2 (`host_config_debug_does_not_leak_password`). ✓ +- F2 (Serialize leak) → Task 1 (`serialize_does_not_leak`) + Task 2 (updated `test_auth_config_password_serialization`). ✓ +- F3 (AWX token) → Task 3. ✓ +- F4 (Bearer) → Task 4. ✓ +- F5 → intentionally out of scope (documented above). ✓ + +**Type consistency:** `RedactedSecret::new`, `::from`, `::as_str`, `Deref`, `Serialize`→`"[REDACTED]"`, `Debug`→`"[REDACTED]"` are defined in Task 1 and used identically in Tasks 2–3. Field types swapped consistently (`Option` for optional secrets, `RedactedSecret` for required ones). + +**Placeholder scan:** all code steps contain concrete code; line numbers are marked `≈` because they drift, with an anchoring symbol name for each. The only deliberately conditional step is the `PatternDef` field-name match in Task 4 Step 3 (the local struct shape must be read from the file) — anchored to the GitHub-pattern block. + +**Risk notes for the executor:** +- Deserialization is unchanged in behavior (still reads a plain YAML/JSON string), so existing config files and round-trip *deserialize* tests keep working. Only *serialize* output changes (now redacted) — that is the intended fix. +- Build with `--all-features` at least once (Task 2 Step 5) to compile the `winrm` (`Ntlm`) and `socks` `#[cfg]` paths. diff --git a/src/config/secret.rs b/src/config/secret.rs index 6676531..328e094 100644 --- a/src/config/secret.rs +++ b/src/config/secret.rs @@ -100,7 +100,10 @@ mod tests { fn debug_does_not_leak() { let s = RedactedSecret::from(SECRET); let rendered = format!("{s:?}"); - assert!(!rendered.contains(SECRET), "Debug leaked the secret: {rendered}"); + assert!( + !rendered.contains(SECRET), + "Debug leaked the secret: {rendered}" + ); assert_eq!(rendered, "[REDACTED]"); } @@ -108,7 +111,10 @@ mod tests { fn serialize_does_not_leak() { let s = RedactedSecret::from(SECRET); let json = serde_json::to_string(&s).unwrap(); - assert!(!json.contains(SECRET), "Serialize leaked the secret: {json}"); + assert!( + !json.contains(SECRET), + "Serialize leaked the secret: {json}" + ); assert_eq!(json, "\"[REDACTED]\""); } diff --git a/tests/e2e_docker.rs b/tests/e2e_docker.rs index 00cf678..8399121 100644 --- a/tests/e2e_docker.rs +++ b/tests/e2e_docker.rs @@ -55,13 +55,13 @@ fn build_docker_ctx() -> ToolContext { port: 2222, user: "testuser".to_string(), auth: AuthConfig::Password { - password: zeroize::Zeroizing::new("testpass123".to_string()), + password: mcp_ssh_bridge::config::RedactedSecret::from("testpass123"), }, description: Some("Docker test SSH server".to_string()), host_key_verification: HostKeyVerification::Off, proxy_jump: None, socks_proxy: None, - sudo_password: Some(zeroize::Zeroizing::new("testpass123".to_string())), + sudo_password: Some(mcp_ssh_bridge::config::RedactedSecret::from("testpass123")), tags: Vec::new(), os_type: OsType::Linux, shell: None, diff --git a/tests/e2e_raspberry.rs b/tests/e2e_raspberry.rs index b95ec84..9855ffb 100644 --- a/tests/e2e_raspberry.rs +++ b/tests/e2e_raspberry.rs @@ -90,11 +90,14 @@ mod rpi { let auth = if let Some(ref key) = config.auth.key { AuthConfig::Key { path: key.path.clone(), - passphrase: key.passphrase.clone().map(zeroize::Zeroizing::new), + passphrase: key + .passphrase + .clone() + .map(mcp_ssh_bridge::config::RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: zeroize::Zeroizing::new(password.clone()), + password: mcp_ssh_bridge::config::RedactedSecret::from(password.clone()), } } else if config.auth.agent.unwrap_or(false) { AuthConfig::Agent diff --git a/tests/sftp_integration.rs b/tests/sftp_integration.rs index 6bc1346..15a0c4d 100644 --- a/tests/sftp_integration.rs +++ b/tests/sftp_integration.rs @@ -69,11 +69,14 @@ fn to_host_config(config: &SshTestConfig) -> HostConfig { let auth = if let Some(ref key) = config.auth.key { AuthConfig::Key { path: key.path.clone(), - passphrase: key.passphrase.clone().map(zeroize::Zeroizing::new), + passphrase: key + .passphrase + .clone() + .map(mcp_ssh_bridge::config::RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: zeroize::Zeroizing::new(password.clone()), + password: mcp_ssh_bridge::config::RedactedSecret::from(password.clone()), } } else if config.auth.agent.unwrap_or(false) { AuthConfig::Agent diff --git a/tests/ssh_integration.rs b/tests/ssh_integration.rs index 9894828..69092bc 100644 --- a/tests/ssh_integration.rs +++ b/tests/ssh_integration.rs @@ -68,11 +68,14 @@ fn to_host_config(config: &SshTestConfig) -> HostConfig { let auth = if let Some(ref key) = config.auth.key { AuthConfig::Key { path: key.path.clone(), - passphrase: key.passphrase.clone().map(zeroize::Zeroizing::new), + passphrase: key + .passphrase + .clone() + .map(mcp_ssh_bridge::config::RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: zeroize::Zeroizing::new(password.clone()), + password: mcp_ssh_bridge::config::RedactedSecret::from(password.clone()), } } else if config.auth.agent.unwrap_or(false) { AuthConfig::Agent diff --git a/tests/ssh_mock_server.rs b/tests/ssh_mock_server.rs index d5eb610..418e1c2 100644 --- a/tests/ssh_mock_server.rs +++ b/tests/ssh_mock_server.rs @@ -43,7 +43,7 @@ use std::sync::Arc; use std::time::Duration; use mcp_ssh_bridge::config::{ - AuthConfig, HostConfig, HostKeyVerification, LimitsConfig, OsType, Protocol, + AuthConfig, HostConfig, HostKeyVerification, LimitsConfig, OsType, Protocol, RedactedSecret, }; use russh::keys::PrivateKey; use russh::keys::ssh_key::private::{Ed25519Keypair, KeypairData}; @@ -284,7 +284,7 @@ pub fn mock_host_config(addr: SocketAddr, user: &str, password: &str) -> HostCon port: addr.port(), user: user.to_string(), auth: AuthConfig::Password { - password: zeroize::Zeroizing::new(password.to_string()), + password: RedactedSecret::from(password), }, description: Some("mock SSH server".into()), // `Off` so we don't touch ~/.ssh/known_hosts during tests. diff --git a/tests/sudo_password_zeroizing.rs b/tests/sudo_password_zeroizing.rs index 4eb0c17..43346eb 100644 --- a/tests/sudo_password_zeroizing.rs +++ b/tests/sudo_password_zeroizing.rs @@ -1,15 +1,16 @@ -//! FIND-028: `HostConfig.sudo_password` must be wrapped in `Zeroizing` -//! so the heap residency does not survive process lifetime / hot-reload. +//! FIND-028: `HostConfig.sudo_password` must be wrapped in a zeroizing, +//! leak-proof secret type so the heap residency does not survive process +//! lifetime / hot-reload. //! -//! This test pins the field type at compile time. If the field reverts to -//! `Option`, the `Zeroizing::new(...)` literal stops type-checking -//! and this file fails to compile — which is exactly the regression signal -//! we want. +//! The field is now [`RedactedSecret`] (a newtype over `Zeroizing` +//! that also blocks `Debug`/`Display`/`Serialize` leaks). This test pins the +//! field type at compile time: if the field reverts to `Option`, the +//! `RedactedSecret::from(...)` literal stops type-checking and this file fails +//! to compile — which is exactly the regression signal we want. -use mcp_ssh_bridge::config::{AuthConfig, HostConfig, HostKeyVerification, OsType}; -use zeroize::Zeroizing; +use mcp_ssh_bridge::config::{AuthConfig, HostConfig, HostKeyVerification, OsType, RedactedSecret}; -fn host_config_with_sudo(password: Option>) -> HostConfig { +fn host_config_with_sudo(password: Option) -> HostConfig { HostConfig { hostname: "192.0.2.10".to_string(), port: 22, @@ -38,24 +39,34 @@ fn host_config_with_sudo(password: Option>) -> HostConfig { } #[test] -fn sudo_password_field_is_zeroizing() { +fn sudo_password_field_is_redacted_secret() { // Type-level assertion: this only compiles if the field is - // `Option>`. If the field type regresses to + // `Option`. If the field type regresses to // `Option`, the literal below fails to type-check. - let host = host_config_with_sudo(Some(Zeroizing::new("s3cret".to_string()))); + let host = host_config_with_sudo(Some(RedactedSecret::from("s3cret"))); // Borrow site stays backwards-compatible: callers can still grab a `&str` - // via Deref coercion. `Option>::as_deref` yields - // `Option<&String>` (one Deref hop); a second hop reaches `&str`. Real - // call sites pass `&Zeroizing` to functions taking `&str` and - // the compiler chains both Deref impls automatically. - let borrowed: Option<&String> = host.sudo_password.as_deref(); - assert_eq!(borrowed.map(String::as_str), Some("s3cret")); + // at the audited boundary. `RedactedSecret` derefs to `str`, so + // `Option::as_deref` yields `Option<&str>` directly. Real + // call sites pass `&*secret` (or `secret.as_str()`) to functions taking + // `&str` and the compiler chains the Deref impl automatically. + let borrowed: Option<&str> = host.sudo_password.as_deref(); + assert_eq!(borrowed, Some("s3cret")); - // Verify the raw secret bytes are reachable (defense-in-depth check - // that the wrapper does not silently mangle the value). - let raw: &str = host.sudo_password.as_ref().expect("set above"); + // Verify the raw secret bytes are reachable through the audited accessor + // (defense-in-depth check that the wrapper does not silently mangle the + // value). + let raw: &str = host.sudo_password.as_ref().expect("set above").as_str(); assert_eq!(raw, "s3cret"); + + // Defense-in-depth: the secret must NOT leak through Debug — `RedactedSecret` + // renders `[REDACTED]` instead of the plaintext, which is the leak-proofing + // guarantee that distinguishes it from a bare `Zeroizing`. + let debug = format!("{:?}", host.sudo_password); + assert!( + !debug.contains("s3cret"), + "sudo_password leaked via Debug: {debug}" + ); } #[test] From c828a24d25dcf41e8f2bfc97ec274ebcf4fb7ba1 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 00:28:48 +0200 Subject: [PATCH 14/19] style(config): import RedactedSecret in integration tests + drop stray plan file --- .../plans/2026-05-30-dependency-updates.md | 546 ------------------ tests/e2e_docker.rs | 8 +- tests/e2e_raspberry.rs | 10 +- tests/sftp_integration.rs | 11 +- tests/ssh_integration.rs | 11 +- 5 files changed, 18 insertions(+), 568 deletions(-) delete mode 100644 docs/superpowers/plans/2026-05-30-dependency-updates.md diff --git a/docs/superpowers/plans/2026-05-30-dependency-updates.md b/docs/superpowers/plans/2026-05-30-dependency-updates.md deleted file mode 100644 index 172b42b..0000000 --- a/docs/superpowers/plans/2026-05-30-dependency-updates.md +++ /dev/null @@ -1,546 +0,0 @@ -# Dependency Update Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Bring all 24 outdated dependencies current — semver-compatible bumps via `cargo update`, plus reviewed major-version bumps (jsonwebtoken, sha2, russh, similar, serde-saphyr, opentelemetry stack) and removal of the winrm-rs git-fork patch — with zero regressions. - -**Architecture:** Phased. Each phase = one isolated, independently-revertable commit gated by `cargo build` (relevant features) + `cargo test --lib`. Semver-safe bumps first (cheap, low-risk), then one phase per major bump so a single regression never blocks the rest. WSL OOM safety: `--lib`, default parallelism, never concurrent build+test. - -**Tech Stack:** Rust 2024 (MSRV 1.94), cargo, cargo-outdated, cargo-machete, make ci. Crates touched: tokio, russh/russh-sftp, jsonwebtoken, sha2, similar, serde-saphyr, opentelemetry stack, winrm-rs, aws-sdk. - ---- - -## Pre-flight (Phase 0) - -**Files:** none (git + baseline only) - -- [ ] **Step 1: Stash/park current WIP** - -Current branch `security/redacted-secret-newtype` is dirty (`src/config/mod.rs`, `src/config/secret.rs`, `docs/`). Do NOT mix dep bumps into it. - -Run: -```bash -cd /home/muchini/mcp-ssh-bridge -git stash push -u -m "wip-redacted-secret before dep-update" -- src/config/mod.rs src/config/secret.rs -git status -``` -Expected: working tree clean except untracked `docs/` (the plan itself). If `docs/` was already tracked WIP, leave it. - -- [ ] **Step 2: Branch from clean main** - -Run: -```bash -git fetch origin -git checkout main && git pull --ff-only -git checkout -b chore/dep-updates-2026-05-30 -``` -Expected: on new branch, tree clean. - -- [ ] **Step 3: Capture green baseline** - -Run: -```bash -cargo build --all-features 2>&1 | tail -5 -cargo test --lib 2>&1 | tail -15 -``` -Expected: build OK, all lib tests pass. Record the pass count — it is the regression baseline for every later phase. If baseline is red, STOP and fix before bumping anything. - -- [ ] **Step 4: Snapshot lockfile** - -Run: -```bash -cp Cargo.lock /tmp/Cargo.lock.baseline -``` -Expected: copy made (for diffing what each phase moved). - ---- - -## Phase 1: Semver-compatible bumps (`cargo update`) - -**Files:** -- Modify: `Cargo.lock` (only — no `Cargo.toml` edits) - -Covers: aws-config, aws-sdk-ssm, clap_complete, const-hex, filetime, mimalloc, reqwest, russh 0.60.1→0.60.3, russh-sftp 2.1.1→2.3.0, serde_json, tokio, tokio-socks, tower-http, uuid. - -- [ ] **Step 1: Update within semver ranges** - -Run: -```bash -cargo update -diff <(grep '^name\|^version' /tmp/Cargo.lock.baseline) <(grep '^name\|^version' Cargo.lock) | head -60 -``` -Expected: lockfile moves the 14 compatible crates. No `Cargo.toml` change. - -- [ ] **Step 2: Build all features** - -Run: -```bash -cargo build --all-features 2>&1 | tail -5 -``` -Expected: compiles clean. - -- [ ] **Step 3: Test** - -Run: -```bash -cargo test --lib 2>&1 | tail -15 -``` -Expected: pass count == baseline. - -- [ ] **Step 4: Commit** - -```bash -git add Cargo.lock -git commit -m "chore(deps): update semver-compatible dependencies - -cargo update — patch/minor bumps within existing ranges: -tokio, russh 0.60.3, russh-sftp 2.3.0, reqwest, tower-http, -aws-config, aws-sdk-ssm, clap_complete, const-hex, mimalloc, -serde_json, tokio-socks, uuid, filetime (dev)." -``` - ---- - -## Phase 2: winrm-rs 1.0 → 1.1.2 + drop git patch (FIND-018) - -**Files:** -- Modify: `Cargo.toml:120` (winrm-rs version) -- Modify: `Cargo.toml:235-236` (remove `[patch.crates-io]` if fixed) - -Context: `Cargo.toml` pins winrm-rs to a git fork because crates.io 1.0 declared an obsolete reqwest feature (`webpki-roots`) that broke `cargo outdated`. 1.1.2 is now published and may carry the fix — if so, the patch is dead weight. - -- [ ] **Step 1: Check whether 1.1.2 fixes the reqwest feature** - -Run: -```bash -cargo info winrm-rs@1.1.2 2>&1 | head -20 -# Inspect upstream Cargo.toml for the obsolete feature: -curl -sL https://crates.io/api/v1/crates/winrm-rs/1.1.2/download -o /tmp/winrm.crate \ - && tar xzf /tmp/winrm.crate -C /tmp \ - && grep -n "webpki-roots\|reqwest" /tmp/winrm-rs-1.1.2/Cargo.toml -``` -Expected: confirm `reqwest` no longer references `webpki-roots`. If the obsolete feature is gone → patch can be removed (Step 2a). If still present → keep patch, bump the git rev instead (Step 2b). - -- [ ] **Step 2a: Bump version + remove patch (if fixed)** - -In `Cargo.toml` line 120, change: -```toml -winrm-rs = { version = "1.0", optional = true } -``` -to: -```toml -winrm-rs = { version = "1.1", optional = true } -``` -Then delete the entire patch block (lines ~229-236): -```toml -# ============================================================================= -# Patch overrides (FIND-018 / audit 2026-05-09) -# ============================================================================= -[patch.crates-io] -winrm-rs = { git = "https://github.com/muchiny/winrm-rs.git", rev = "573dadf5abcaed681f65999f216164c9f33a6250" } -``` - -- [ ] **Step 2b: Fallback — keep patch, point fork at 1.1.2 (if NOT fixed)** - -Leave line 120 as `version = "1.1"`, update the fork to a rev that tracks 1.1.2, keep the `[patch.crates-io]` block. Document why in a refreshed comment dated 2026-05-30. - -- [ ] **Step 3: Build winrm + psrp features** - -Run: -```bash -cargo build --features winrm 2>&1 | tail -5 -cargo build --features psrp 2>&1 | tail -5 -``` -Expected: both compile. (psrp depends on winrm.) - -- [ ] **Step 4: Verify outdated no longer lists winrm-rs as "Removed"** - -Run: -```bash -cargo outdated --root-deps-only 2>&1 | grep -i winrm || echo "winrm-rs current" -``` -Expected: `winrm-rs current` (or shows it at 1.1.2 with no "Removed"). - -- [ ] **Step 5: Test** - -Run: -```bash -cargo test --lib --features psrp 2>&1 | tail -15 -``` -Expected: pass count >= baseline (psrp adds tests). - -- [ ] **Step 6: Commit** - -```bash -git add Cargo.toml Cargo.lock -git commit -m "chore(deps): bump winrm-rs 1.0 -> 1.1.2, drop git-fork patch (FIND-018) - -crates.io 1.1.2 ships the reqwest feature fix the local fork carried; -[patch.crates-io] override removed. Resolves the cargo-outdated -'Removed' status. Update Known Advisories in CLAUDE.md if the patch -removal changes the advisory set." -``` - ---- - -## Phase 3: OpenTelemetry stack 0.31 → 0.32 (feature `otel`) - -**Files:** -- Modify: `Cargo.toml:144-147` (opentelemetry, opentelemetry_sdk, opentelemetry-otlp, tracing-opentelemetry) -- Likely modify: telemetry init module (find via grep in Step 1) - -Coordinated bump — all four versions move together. Metrics/logs/traces APIs have been stable since 0.30, so risk is low, but `tracing-opentelemetry` must match: 0.32 → **0.33**. - -- [ ] **Step 1: Locate the otel init code** - -Run: -```bash -grep -rln "opentelemetry\|SdkTracerProvider\|tracing_opentelemetry\|OtelLayer\|otlp" src --include="*.rs" -``` -Expected: a small set (telemetry/observability module). Read each before editing. - -- [ ] **Step 2: Bump the four versions** - -In `Cargo.toml`: -```toml -opentelemetry = { version = "0.32", optional = true } -opentelemetry_sdk = { version = "0.32", features = ["rt-tokio"], optional = true } -opentelemetry-otlp = { version = "0.32", features = ["grpc-tonic"], optional = true } -tracing-opentelemetry = { version = "0.33", optional = true } -``` - -- [ ] **Step 3: Build the otel feature** - -Run: -```bash -cargo build --features otel 2>&1 | tail -20 -``` -Expected: compiles. If errors, they will be in the init module — most likely the `Resource::builder()` / `SdkTracerProvider` builder API (stable since 0.28, but double-check exporter constructor signatures against the 0.32 docs). Fix in the module from Step 1. - -- [ ] **Step 4: Test** - -Run: -```bash -cargo test --lib --features otel 2>&1 | tail -15 -``` -Expected: pass count >= baseline. - -- [ ] **Step 5: Commit** - -```bash -git add Cargo.toml Cargo.lock src/ -git commit -m "chore(deps): bump opentelemetry stack 0.31 -> 0.32 - -opentelemetry/_sdk/-otlp 0.32, tracing-opentelemetry 0.33 (must match). -otel feature only; trace/metric/log APIs stable since 0.30." -``` - ---- - -## Phase 4: similar 2.7 → 3.1 (default feature) - -**Files:** -- Modify: `Cargo.toml:148` (similar = "3") -- Possibly touch: `src/domain/diff.rs` (only consumer) - -Only consumer is `src/domain/diff.rs` using `ChangeTag`, `TextDiff`, `TextDiff::from_lines` — all stable across the 2→3 major. The 3.0 break is mostly MSRV/internal. - -- [ ] **Step 1: Bump version** - -In `Cargo.toml` line 148: -```toml -similar = "3" -``` - -- [ ] **Step 2: Build + test the diff module** - -Run: -```bash -cargo build 2>&1 | tail -5 -cargo test --lib diff 2>&1 | tail -15 -``` -Expected: compiles; `domain::diff` tests pass. If `TextDiff::from_lines`/`ChangeTag` signatures shifted, adjust `src/domain/diff.rs:203-209`. - -- [ ] **Step 3: Commit** - -```bash -git add Cargo.toml Cargo.lock src/domain/diff.rs -git commit -m "chore(deps): bump similar 2 -> 3" -``` - ---- - -## Phase 5: sha2 0.10 → 0.11 (default feature) - -**Files:** -- Modify: `Cargo.toml:98` (sha2 = "0.11") -- Possibly touch: `src/ssh/sftp.rs:12,268,462`, `src/security/recording.rs:15,399` - -Direct API (`Sha256::new`, `.update`, `.finalize`, `Digest`) is unchanged in 0.11. The 0.11 break is the underlying `digest` 0.11 / `hybrid-array` migration: the `finalize()` output type changes from `GenericArray` to `hybrid_array::Array`. Check that downstream `const_hex::encode(...)` and slice uses still accept it (they take `AsRef<[u8]>`, which both implement). - -- [ ] **Step 1: Bump version** - -In `Cargo.toml` line 98: -```toml -sha2 = "0.11" -``` - -- [ ] **Step 2: Build** - -Run: -```bash -cargo build 2>&1 | tail -20 -``` -Expected: compiles. If `const_hex::encode(h.finalize())` fails on the new output type, wrap with `.as_slice()` / `&h.finalize()[..]` at `src/ssh/sftp.rs:341,537`. - -- [ ] **Step 3: Test the hashing consumers** - -Run: -```bash -cargo test --lib sftp 2>&1 | tail -10 -cargo test --lib recording 2>&1 | tail -10 -``` -Expected: checksum + recording-hash tests pass (proves the hash bytes are byte-identical post-bump). - -- [ ] **Step 4: Guard against a duplicate sha2 in the tree** - -Run: -```bash -cargo tree -i sha2 2>&1 | head -30 -``` -Expected: ideally a single 0.11. If `ssh-key`/russh still pull sha2 0.10, that's a tolerated duplicate (not a break) — note it; do not force-downgrade. - -- [ ] **Step 5: Commit** - -```bash -git add Cargo.toml Cargo.lock src/ -git commit -m "chore(deps): bump sha2 0.10 -> 0.11 - -Direct API (Sha256::new/update/finalize/Digest) unchanged; 0.11 moves -to digest 0.11 / hybrid-array output. sftp checksum + audit-recording -hash tests confirm byte-identical output." -``` - ---- - -## Phase 6: jsonwebtoken 9.3 → 10.4 (feature `http`) - -**Files:** -- Modify: `Cargo.toml:151` (jsonwebtoken = "10") -- Likely touch: `src/mcp/transport/oauth.rs:34,217-239,504-535` - -Only consumer is the OAuth/JWT validator in `oauth.rs` (HTTP transport). All APIs we use exist in v10: `DecodingKey::from_rsa_components`/`from_rsa_pem`, `Validation::new`, `set_issuer`, `set_audience`, `set_required_spec_claims(&["..."])`, `validate_exp`, `validate_nbf`, `leeway`, `decode`, `decode_header`, `EncodingKey::from_rsa_pem`, `encode`, `Header`. v10 hardens defaults (e.g. `aud` rejection) — our code already requires all four spec claims, so semantics should hold. **Security-sensitive: the existing oauth test suite is the gate, do not weaken it.** - -- [ ] **Step 1: Bump version** - -In `Cargo.toml` line 151: -```toml -jsonwebtoken = "10" -``` - -- [ ] **Step 2: Build the http feature** - -Run: -```bash -cargo build --features http 2>&1 | tail -20 -``` -Expected: compiles. Likely-affected spots if v10 shifted a signature: -- `set_required_spec_claims` argument type (still `&[&str]` in v10 per docs — should be fine). -- `from_rsa_components`/`from_rsa_pem` still return `Result` — already handled with `map_err`. -Fix any signature drift inside `src/mcp/transport/oauth.rs` only. - -- [ ] **Step 3: Run the full oauth test suite (security gate)** - -Run: -```bash -cargo test --lib --features http oauth 2>&1 | tail -25 -``` -Expected: every oauth test passes — especially the negative cases (missing `sub`, wrong `aud`, expired token, bad signature). If any negative test now passes a token it should reject, STOP — v10 default change must be re-pinned explicitly in `Validation`. - -- [ ] **Step 4: Commit** - -```bash -git add Cargo.toml Cargo.lock src/mcp/transport/oauth.rs -git commit -m "chore(deps): bump jsonwebtoken 9 -> 10 - -OAuth/JWT validator (http feature). v10 hardens claim defaults; -explicit set_required_spec_claims + set_issuer/set_audience preserved -(FIND-007). Full oauth negative-case suite verified." -``` - ---- - -## Phase 7: russh 0.60 → 0.61 (default feature, core SSH) - -**Files:** -- Modify: `Cargo.toml:68` (russh = "0.61") -- Likely touch: `src/ssh/{client,session,connector,sftp,known_hosts,mod}.rs`, `src/mcp/standard_tool.rs` - -Highest blast radius (7 files) and the core transport. 0.61 is a minor-major in the 0.x sense — review the russh CHANGELOG for `client::Handler` trait changes, `Config`, channel/auth signatures, and the kex/mac algorithm registration in `src/ssh/client.rs:48-53`. - -- [ ] **Step 1: Read russh 0.61 changelog deltas** - -Run: -```bash -cargo info russh@0.61.1 2>&1 | head -20 -``` -Then check context7 (`/warp-tech/russh` or via resolve-library-id "russh") for `client::Handler` / `Config` API changes 0.60→0.61 before editing. - -- [ ] **Step 2: Bump version** - -In `Cargo.toml` line 68: -```toml -russh = "0.61" -``` - -- [ ] **Step 3: Build** - -Run: -```bash -cargo build 2>&1 | tail -30 -``` -Expected: compiles, or a small set of trait/signature errors in the ssh adapter files. Fix each against the 0.61 API. Common 0.x russh breaks: `Handler::check_server_key` signature, `Channel` async return types, `Preferred` algorithm config fields (`src/ssh/client.rs`). - -- [ ] **Step 4: Full SSH adapter test pass** - -Run: -```bash -cargo test --lib ssh 2>&1 | tail -25 -``` -Expected: pass count >= baseline for ssh modules. The russh/sftp mock harness (added in commit 4c5cca0) exercises the adapter — it must stay green. - -- [ ] **Step 5: Commit** - -```bash -git add Cargo.toml Cargo.lock src/ -git commit -m "chore(deps): bump russh 0.60 -> 0.61 - -Core SSH transport. Adapter (client/session/connector/sftp/known_hosts) -updated for 0.61 API. Mock harness green." -``` - ---- - -## Phase 8: serde-saphyr =0.0.21 → 0.0.27 (default feature, config parser) - -**Files:** -- Modify: `Cargo.toml:74` (serde-saphyr = "=0.0.27") -- Likely touch: `src/domain/yaml.rs:50-84`, `src/error.rs:132` - -Hard-pinned with `=` because it's 0.0.x — every release is potentially breaking. The central hardened wrapper is `src/domain/yaml.rs` (`Options`, `Budget`, `from_str_with_options`). Security-relevant: this parses untrusted YAML config + runbooks with budget limits. **Bump one minor at a time and read each changelog** — 0.21→0.27 is six releases. - -- [ ] **Step 1: Review the changelog 0.0.21 → 0.0.27** - -Run: -```bash -cargo info serde-saphyr@0.0.27 2>&1 | head -20 -``` -Read the repo CHANGELOG (or each version's release notes) for changes to `Options`, `Budget`, `from_str_with_options`, `from_str`, and the `Error` type. Note any field renames/additions to `Budget` (the hardening in `yaml.rs:51-57` depends on its exact fields). - -- [ ] **Step 2: Bump the pin** - -In `Cargo.toml` line 74: -```toml -serde-saphyr = "=0.0.27" -``` - -- [ ] **Step 3: Build** - -Run: -```bash -cargo build 2>&1 | tail -25 -``` -Expected: compiles, or errors in `src/domain/yaml.rs` (Budget/Options fields) and `src/error.rs:132` (Error `#[from]`). Fix against the new API, preserving every existing budget limit. - -- [ ] **Step 4: Run YAML + config + runbook tests (security gate)** - -Run: -```bash -cargo test --lib yaml 2>&1 | tail -15 -cargo test --lib config 2>&1 | tail -15 -cargo test --lib runbook 2>&1 | tail -15 -``` -Expected: all pass — especially the budget/DoS-limit tests in `domain::yaml` (they prove the hardening still trips on oversized/over-deep input). If a budget test no longer trips, the field mapping is wrong — STOP and re-check Step 1. - -- [ ] **Step 5: Commit** - -```bash -git add Cargo.toml Cargo.lock src/domain/yaml.rs src/error.rs -git commit -m "chore(deps): bump serde-saphyr 0.0.21 -> 0.0.27 - -Hardened YAML wrapper (domain/yaml.rs) Budget/Options preserved; -DoS budget-limit tests verified. Pin kept as '=' (0.0.x)." -``` - ---- - -## Phase 9: Final verification + integration - -**Files:** none (verification only) - -- [ ] **Step 1: Confirm nothing left outdated (except intentionally pinned)** - -Run: -```bash -cargo outdated --root-deps-only 2>&1 -``` -Expected: empty or only entries you consciously chose not to bump. Document any deliberate holds. - -- [ ] **Step 2: No unused deps introduced** - -Run: -```bash -cargo machete 2>&1 | tail -5 -``` -Expected: "didn't find any unused dependencies". - -- [ ] **Step 3: Full CI gate** - -Run: -```bash -make ci 2>&1 | tail -40 -``` -Expected: fmt-check, clippy (`-D warnings`), test, audit, typos all green. `cargo audit` is the check for whether any bump changed the advisory set vs `deny.toml` (6 ignored advisories — see CLAUDE.md "Known Advisories"). - -- [ ] **Step 4: Build the full feature matrix** - -Run: -```bash -cargo build --all-features 2>&1 | tail -5 -cargo build --no-default-features 2>&1 | tail -5 -cargo build --features all-protocols 2>&1 | tail -5 -``` -Expected: every combination compiles. - -- [ ] **Step 5: Update advisory/docs if changed** - -If `cargo audit` / `make ci` surfaced a resolved or new advisory, update the "Known Advisories" list in `CLAUDE.md` and `deny.toml`. Commit separately: -```bash -git add CLAUDE.md deny.toml -git commit -m "docs(deps): refresh Known Advisories after dependency updates" -``` - -- [ ] **Step 6: Restore parked WIP (optional)** - -If the secret-newtype work resumes after this branch merges: -```bash -git checkout security/redacted-secret-newtype -git stash pop -``` - ---- - -## Risk Summary (review-order, low → high) - -| Phase | Crate(s) | Blast radius | Risk | Gate | -|---|---|---|---|---| -| 1 | 14 semver-compat | Cargo.lock only | minimal | build + test | -| 2 | winrm-rs 1.1.2 | Cargo.toml (patch removal) | low | winrm/psrp build | -| 3 | otel 0.32 | otel init module | low | otel build+test | -| 4 | similar 3 | `domain/diff.rs` | low | diff tests | -| 5 | sha2 0.11 | sftp, recording (2 sites) | low-mod | checksum/hash tests | -| 6 | jsonwebtoken 10 | `oauth.rs` | moderate (security) | oauth negative-case suite | -| 7 | russh 0.61 | 7 ssh files | mod-high | ssh mock harness | -| 8 | serde-saphyr 0.0.27 | `yaml.rs`, `error.rs` | high (untrusted-input parser) | budget/DoS tests | - -If any single phase resists within ~30 min of effort, commit the phases before it, open a focused follow-up for the blocker, and ship the rest — the phase isolation is the whole point. diff --git a/tests/e2e_docker.rs b/tests/e2e_docker.rs index 8399121..92b37e9 100644 --- a/tests/e2e_docker.rs +++ b/tests/e2e_docker.rs @@ -25,8 +25,8 @@ use std::sync::Arc; use mcp_ssh_bridge::ExecutorRouter; use mcp_ssh_bridge::config::{ AuditConfig, AuthConfig, Config, HostConfig, HostKeyVerification, HttpTransportConfig, - LimitsConfig, OsType, SecurityConfig, SecurityMode, SessionConfig, SshConfigDiscovery, - ToolGroupsConfig, + LimitsConfig, OsType, RedactedSecret, SecurityConfig, SecurityMode, SessionConfig, + SshConfigDiscovery, ToolGroupsConfig, }; use mcp_ssh_bridge::domain::history::HistoryConfig; use mcp_ssh_bridge::domain::{CommandHistory, ExecuteCommandUseCase, TunnelManager}; @@ -55,13 +55,13 @@ fn build_docker_ctx() -> ToolContext { port: 2222, user: "testuser".to_string(), auth: AuthConfig::Password { - password: mcp_ssh_bridge::config::RedactedSecret::from("testpass123"), + password: RedactedSecret::from("testpass123"), }, description: Some("Docker test SSH server".to_string()), host_key_verification: HostKeyVerification::Off, proxy_jump: None, socks_proxy: None, - sudo_password: Some(mcp_ssh_bridge::config::RedactedSecret::from("testpass123")), + sudo_password: Some(RedactedSecret::from("testpass123")), tags: Vec::new(), os_type: OsType::Linux, shell: None, diff --git a/tests/e2e_raspberry.rs b/tests/e2e_raspberry.rs index 9855ffb..eaa51f2 100644 --- a/tests/e2e_raspberry.rs +++ b/tests/e2e_raspberry.rs @@ -19,7 +19,8 @@ use std::sync::Arc; use mcp_ssh_bridge::ExecutorRouter; use mcp_ssh_bridge::config::{ AuditConfig, AuthConfig, Config, HostConfig, HostKeyVerification, HttpTransportConfig, - LimitsConfig, OsType, SecurityConfig, SessionConfig, SshConfigDiscovery, ToolGroupsConfig, + LimitsConfig, OsType, RedactedSecret, SecurityConfig, SessionConfig, SshConfigDiscovery, + ToolGroupsConfig, }; use mcp_ssh_bridge::domain::history::HistoryConfig; use mcp_ssh_bridge::domain::{CommandHistory, ExecuteCommandUseCase, TunnelManager}; @@ -90,14 +91,11 @@ mod rpi { let auth = if let Some(ref key) = config.auth.key { AuthConfig::Key { path: key.path.clone(), - passphrase: key - .passphrase - .clone() - .map(mcp_ssh_bridge::config::RedactedSecret::from), + passphrase: key.passphrase.clone().map(RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: mcp_ssh_bridge::config::RedactedSecret::from(password.clone()), + password: RedactedSecret::from(password.clone()), } } else if config.auth.agent.unwrap_or(false) { AuthConfig::Agent diff --git a/tests/sftp_integration.rs b/tests/sftp_integration.rs index 15a0c4d..826370b 100644 --- a/tests/sftp_integration.rs +++ b/tests/sftp_integration.rs @@ -13,7 +13,9 @@ use std::path::Path; use std::sync::Arc; use std::sync::atomic::{AtomicU64, Ordering}; -use mcp_ssh_bridge::config::{AuthConfig, HostConfig, HostKeyVerification, LimitsConfig, OsType}; +use mcp_ssh_bridge::config::{ + AuthConfig, HostConfig, HostKeyVerification, LimitsConfig, OsType, RedactedSecret, +}; use mcp_ssh_bridge::ssh::{SshClient, TransferMode, TransferOptions, TransferProgress}; use serde::Deserialize; use tempfile::NamedTempFile; @@ -69,14 +71,11 @@ fn to_host_config(config: &SshTestConfig) -> HostConfig { let auth = if let Some(ref key) = config.auth.key { AuthConfig::Key { path: key.path.clone(), - passphrase: key - .passphrase - .clone() - .map(mcp_ssh_bridge::config::RedactedSecret::from), + passphrase: key.passphrase.clone().map(RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: mcp_ssh_bridge::config::RedactedSecret::from(password.clone()), + password: RedactedSecret::from(password.clone()), } } else if config.auth.agent.unwrap_or(false) { AuthConfig::Agent diff --git a/tests/ssh_integration.rs b/tests/ssh_integration.rs index 69092bc..6df1c09 100644 --- a/tests/ssh_integration.rs +++ b/tests/ssh_integration.rs @@ -12,7 +12,9 @@ use std::path::Path; use std::sync::Arc; use std::time::Duration; -use mcp_ssh_bridge::config::{AuthConfig, HostConfig, HostKeyVerification, LimitsConfig, OsType}; +use mcp_ssh_bridge::config::{ + AuthConfig, HostConfig, HostKeyVerification, LimitsConfig, OsType, RedactedSecret, +}; use mcp_ssh_bridge::ssh::{ConnectionPool, PoolConfig, SshClient}; use serde::Deserialize; @@ -68,14 +70,11 @@ fn to_host_config(config: &SshTestConfig) -> HostConfig { let auth = if let Some(ref key) = config.auth.key { AuthConfig::Key { path: key.path.clone(), - passphrase: key - .passphrase - .clone() - .map(mcp_ssh_bridge::config::RedactedSecret::from), + passphrase: key.passphrase.clone().map(RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: mcp_ssh_bridge::config::RedactedSecret::from(password.clone()), + password: RedactedSecret::from(password.clone()), } } else if config.auth.agent.unwrap_or(false) { AuthConfig::Agent From e5036a6c96743723868a3b13191a43ec93aacb3d Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 00:38:26 +0200 Subject: [PATCH 15/19] =?UTF-8?q?fix(config):=20wrap=20AwxConfig=20token?= =?UTF-8?q?=20in=20RedactedSecret=20(fixes=20F3=20=E2=80=94=20zeroize=20+?= =?UTF-8?q?=20redact)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/types.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/config/types.rs b/src/config/types.rs index 61bb5d9..bb12bfc 100644 --- a/src/config/types.rs +++ b/src/config/types.rs @@ -56,7 +56,11 @@ pub struct AwxConfig { pub url: String, /// `OAuth2` Bearer token for AWX API authentication. - pub token: String, + /// + /// Wrapped in [`RedactedSecret`] so the token is zeroized on drop and + /// never leaked through `Debug`/`Display`/`Serialize` (emits + /// `"[REDACTED]"`). Deserializes from a plain YAML/JSON string. + pub token: RedactedSecret, /// Request timeout in seconds (default: 30). #[serde(default = "default_awx_timeout")] @@ -1518,6 +1522,29 @@ mod tests { ); } + // ============== AwxConfig Tests ============== + + #[test] + fn awx_token_is_redacted_in_debug() { + let awx = AwxConfig { + ssh_host: "h".to_string(), + url: "https://awx".to_string(), + token: RedactedSecret::from("awx-oauth-token-123"), + api_timeout: 30, + verify_ssl: true, + }; + let rendered = format!("{awx:?}"); + assert!( + !rendered.contains("awx-oauth-token-123"), + "AwxConfig Debug leaked the token: {rendered}" + ); + let json = serde_json::to_string(&awx).unwrap(); + assert!( + !json.contains("awx-oauth-token-123"), + "AwxConfig Serialize leaked the token" + ); + } + // ============== LimitsConfig Tests ============== #[test] From b129dcdf1f7f5fc6232bb50132213a6c51586ed8 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 00:46:21 +0200 Subject: [PATCH 16/19] feat(security): redact opaque Authorization: Bearer tokens (fixes F4) --- src/security/sanitizer.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/security/sanitizer.rs b/src/security/sanitizer.rs index 3214773..4e5442c 100644 --- a/src/security/sanitizer.rs +++ b/src/security/sanitizer.rs @@ -510,6 +510,18 @@ impl Sanitizer { description: "Generic JWT token", category: "jwt", }, + // Opaque (non-JWT) Authorization: Bearer tokens. + // MUST come AFTER the JWT pattern above: the engine applies matched + // patterns in definition order, so a `Bearer eyJ...` JWT is already + // replaced by [JWT_TOKEN_REDACTED] before this generic rule is reached + // (and `[JWT_TOKEN_REDACTED]` no longer matches this token charset). + // This closes the leak when `entropy_detection: false` is configured. + PatternDef { + pattern: r"(?i)bearer\s+[A-Za-z0-9._~+/=-]{16,}", + replacement: "Bearer [BEARER_TOKEN_REDACTED]", + description: "Opaque Authorization Bearer token", + category: "generic", + }, // Anthropic API keys (specific prefix sk-ant-*) PatternDef { pattern: r"sk-ant-api\d{2}-[A-Za-z0-9_-]{80,}", @@ -1244,6 +1256,18 @@ Done"; ); } + #[test] + fn test_bearer_opaque_token_redacted() { + let sanitizer = Sanitizer::with_defaults(); + let input = "curl -H 'Authorization: Bearer A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6'"; + let out = sanitizer.sanitize(input); + assert!( + !out.contains("A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6"), + "opaque bearer token leaked: {out}" + ); + assert!(out.contains("[BEARER_TOKEN_REDACTED]")); + } + #[test] fn test_openai_key() { let sanitizer = Sanitizer::with_defaults(); From 3d3d2792a5ab072b9f69cc5f9a0f79890828b03d Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 00:50:19 +0200 Subject: [PATCH 17/19] test(security): lock JWT-over-Bearer redaction precedence --- src/security/sanitizer.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/security/sanitizer.rs b/src/security/sanitizer.rs index 4e5442c..d55e036 100644 --- a/src/security/sanitizer.rs +++ b/src/security/sanitizer.rs @@ -1268,6 +1268,19 @@ Done"; assert!(out.contains("[BEARER_TOKEN_REDACTED]")); } + #[test] + fn test_jwt_bearer_takes_jwt_precedence_over_generic_bearer() { + let sanitizer = Sanitizer::with_defaults(); + let input = "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.abc123XYZ456"; + let out = sanitizer.sanitize(input); + assert!(out.contains("[JWT_TOKEN_REDACTED]"), "JWT should win: {out}"); + assert!( + !out.contains("[BEARER_TOKEN_REDACTED]"), + "generic Bearer pattern must not steal a JWT match: {out}" + ); + assert!(!out.contains("eyJhbGci"), "JWT not redacted: {out}"); + } + #[test] fn test_openai_key() { let sanitizer = Sanitizer::with_defaults(); From 136454322b061befaa6da9935a49b83c4b96d83f Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 00:54:30 +0200 Subject: [PATCH 18/19] style(security): rustfmt JWT-precedence test --- src/security/sanitizer.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/security/sanitizer.rs b/src/security/sanitizer.rs index d55e036..580b87f 100644 --- a/src/security/sanitizer.rs +++ b/src/security/sanitizer.rs @@ -1271,9 +1271,13 @@ Done"; #[test] fn test_jwt_bearer_takes_jwt_precedence_over_generic_bearer() { let sanitizer = Sanitizer::with_defaults(); - let input = "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.abc123XYZ456"; + let input = + "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.abc123XYZ456"; let out = sanitizer.sanitize(input); - assert!(out.contains("[JWT_TOKEN_REDACTED]"), "JWT should win: {out}"); + assert!( + out.contains("[JWT_TOKEN_REDACTED]"), + "JWT should win: {out}" + ); assert!( !out.contains("[BEARER_TOKEN_REDACTED]"), "generic Bearer pattern must not steal a JWT match: {out}" From cc0beb95bfe9f1b18cbec91964c89c6c4a9e8295 Mon Sep 17 00:00:00 2001 From: loic wernert Date: Sun, 31 May 2026 01:09:45 +0200 Subject: [PATCH 19/19] fix(security): migrate DB/vault handler-arg secrets to RedactedSecret (no Debug leak) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/domain/use_cases/vault.rs | 39 ++++++------- src/mcp/tool_handlers/ssh_db_dump.rs | 66 +++++++++++++++------- src/mcp/tool_handlers/ssh_db_query.rs | 72 ++++++++++++++++-------- src/mcp/tool_handlers/ssh_db_restore.rs | 62 ++++++++++++-------- src/mcp/tool_handlers/ssh_vault_write.rs | 43 +++++++++++--- 5 files changed, 187 insertions(+), 95 deletions(-) diff --git a/src/domain/use_cases/vault.rs b/src/domain/use_cases/vault.rs index 732f123..20443f3 100644 --- a/src/domain/use_cases/vault.rs +++ b/src/domain/use_cases/vault.rs @@ -5,7 +5,7 @@ use std::fmt::Write; -use crate::config::ShellType; +use crate::config::{RedactedSecret, ShellType}; use crate::error::{BridgeError, Result}; fn shell_escape(s: &str) -> String { @@ -155,16 +155,19 @@ impl VaultCommandBuilder { /// heredoc early. Same pattern as `template_apply` (commit 2da5d55). /// /// `data` carries `key=value` pairs; values are typically secrets, so the - /// caller is expected to pass `Zeroizing` (FIND-030) to avoid - /// gratuitous heap residency. The slice is borrowed immutably here; the - /// owner controls when the secret bytes are wiped. + /// caller is expected to pass [`RedactedSecret`] (FIND-030) to avoid + /// gratuitous heap residency and to make the pairs leak-proof under + /// `Debug`/`Display`/`Serialize`. The slice is borrowed immutably here; + /// the owner controls when the secret bytes are wiped. Each element is + /// read here through its `Deref` (the audited plaintext + /// boundary) only to assemble the shell-literal heredoc body. /// /// # Errors /// /// Returns [`BridgeError::CommandDenied`] if `path` contains unsafe characters. pub fn build_write_command( path: &str, - data: &[zeroize::Zeroizing], + data: &[RedactedSecret], vault_addr: Option<&str>, mount: Option<&str>, ) -> Result { @@ -296,8 +299,8 @@ mod tests { #[test] fn test_write_simple() { let data = vec![ - zeroize::Zeroizing::new("username=admin".to_string()), - zeroize::Zeroizing::new("password=secret".to_string()), + RedactedSecret::from("username=admin"), + RedactedSecret::from("password=secret"), ]; let cmd = VaultCommandBuilder::build_write_command("secret/myapp", &data, None, None).unwrap(); @@ -311,7 +314,7 @@ mod tests { #[test] fn test_write_with_mount() { - let data = vec![zeroize::Zeroizing::new("key=value".to_string())]; + let data = vec![RedactedSecret::from("key=value")]; let cmd = VaultCommandBuilder::build_write_command("myapp/config", &data, None, Some("kv")) .unwrap(); assert!(cmd.contains("-mount='kv'")); @@ -328,9 +331,7 @@ mod tests { #[test] fn test_write_injection_in_data_value() { - let data = vec![zeroize::Zeroizing::new( - "password=s3cr3t; rm -rf /".to_string(), - )]; + let data = vec![RedactedSecret::from("password=s3cr3t; rm -rf /")]; let cmd = VaultCommandBuilder::build_write_command("secret/app", &data, None, None).unwrap(); // FIND-031: value is a heredoc body line (single-quoted terminator @@ -389,8 +390,8 @@ mod tests { #[test] fn test_write_all_options() { let data = vec![ - zeroize::Zeroizing::new("user=admin".to_string()), - zeroize::Zeroizing::new("pass=secret".to_string()), + RedactedSecret::from("user=admin"), + RedactedSecret::from("pass=secret"), ]; let cmd = VaultCommandBuilder::build_write_command( "secret/myapp", @@ -419,7 +420,7 @@ mod tests { #[test] fn test_write_empty_data() { - let data: Vec> = vec![]; + let data: Vec = vec![]; let cmd = VaultCommandBuilder::build_write_command("secret/myapp", &data, None, None).unwrap(); // FIND-031: even with no data, the heredoc structure is still produced @@ -429,7 +430,7 @@ mod tests { #[test] fn test_write_single_data_item() { - let data = vec![zeroize::Zeroizing::new("key=val".to_string())]; + let data = vec![RedactedSecret::from("key=val")]; let cmd = VaultCommandBuilder::build_write_command("secret/myapp", &data, None, None).unwrap(); // FIND-031: shape is `... 'secret/myapp' - <<'TERMINATOR'\nkey=val\nTERMINATOR`. @@ -439,7 +440,7 @@ mod tests { #[test] fn test_write_data_with_single_quotes() { - let data = vec![zeroize::Zeroizing::new("msg=it's secret".to_string())]; + let data = vec![RedactedSecret::from("msg=it's secret")]; let cmd = VaultCommandBuilder::build_write_command("secret/app", &data, None, None).unwrap(); // FIND-031: heredoc body is shell-literal; the apostrophe is preserved @@ -515,7 +516,7 @@ mod tests { /// and never in argv. #[test] fn vault_write_excludes_secret_value_from_argv() { - let data = vec![zeroize::Zeroizing::new("k=topsecret".to_string())]; + let data = vec![RedactedSecret::from("k=topsecret")]; let cmd = VaultCommandBuilder::build_write_command("secret/foo", &data, None, None).unwrap(); @@ -532,7 +533,7 @@ mod tests { /// FIND-031: the builder must use a stdin pipe (`-` argument + heredoc). #[test] fn vault_write_uses_stdin_heredoc() { - let data = vec![zeroize::Zeroizing::new("k=v".to_string())]; + let data = vec![RedactedSecret::from("k=v")]; let cmd = VaultCommandBuilder::build_write_command("secret/foo", &data, None, None).unwrap(); @@ -552,7 +553,7 @@ mod tests { /// `template_apply` (commit 2da5d55). #[test] fn vault_write_heredoc_terminator_is_randomized() { - let data = vec![zeroize::Zeroizing::new("k=v".to_string())]; + let data = vec![RedactedSecret::from("k=v")]; let cmd1 = VaultCommandBuilder::build_write_command("secret/foo", &data, None, None).unwrap(); let cmd2 = diff --git a/src/mcp/tool_handlers/ssh_db_dump.rs b/src/mcp/tool_handlers/ssh_db_dump.rs index f1d8e71..51480fb 100644 --- a/src/mcp/tool_handlers/ssh_db_dump.rs +++ b/src/mcp/tool_handlers/ssh_db_dump.rs @@ -4,9 +4,8 @@ //! Supports `MySQL` (`mysqldump`) and `PostgreSQL` (`pg_dump`). use serde::Deserialize; -use zeroize::Zeroizing; -use crate::config::HostConfig; +use crate::config::{HostConfig, RedactedSecret}; use crate::domain::{DatabaseCommandBuilder, DatabaseType}; use crate::error::Result; use crate::mcp::standard_tool::{StandardTool, StandardToolHandler, impl_common_args}; @@ -25,13 +24,13 @@ pub struct SshDbDumpArgs { #[serde(default)] db_user: Option, /// DB password from MCP JSON-RPC request body. Wrapped in - /// `Zeroizing` so the heap allocation is wiped on drop - /// (FIND-029). Production read sites pass it to the builder as - /// `Option<&str>` via `.as_deref().map(String::as_str)` — `as_deref()` - /// peels `Zeroizing` -> `&String`, then `String::as_str` - /// gives the final `&str`. + /// `RedactedSecret` (FIND-029, F1-equivalent): the heap allocation is + /// wiped on drop and `Debug`/`Display`/`Serialize` emit `[REDACTED]`, + /// so the secret cannot leak through `format!("{args:?}")`. Production + /// read sites pass it to the builder as `Option<&str>` via `as_deref()` + /// (the single audited plaintext boundary, `Deref`). #[serde(default)] - db_password: Option>, + db_password: Option, #[serde(default)] tables: Option>, #[serde(default)] @@ -126,7 +125,7 @@ impl StandardTool for DbDumpTool { db_host, db_port, db_user, - args.db_password.as_deref().map(String::as_str), + args.db_password.as_deref(), &args.database, args.tables.as_deref(), args.compress.as_deref(), @@ -311,6 +310,33 @@ mod tests { assert!(debug_str.contains("test-host")); } + /// FIND-029 (F1-equivalent): the `db_password` field must not leak its + /// plaintext through `Debug`. + #[test] + fn args_debug_does_not_leak_db_password() { + let args = SshDbDumpArgs { + host: "test-host".to_string(), + db_type: "mysql".to_string(), + database: "testdb".to_string(), + output_file: "/tmp/dump.sql".to_string(), + db_host: None, + db_port: None, + db_user: None, + db_password: Some(RedactedSecret::from("s3cr3t-pw")), + tables: None, + compress: None, + timeout_seconds: None, + max_output: None, + save_output: None, + }; + let rendered = format!("{args:?}"); + assert!( + !rendered.contains("s3cr3t-pw"), + "db_password leaked in Debug: {rendered}" + ); + assert!(rendered.contains("[REDACTED]")); + } + #[tokio::test] async fn test_invalid_json_type() { let handler = SshDbDumpHandler::new(); @@ -476,7 +502,7 @@ mod tests { db_host: Some("dbhost".to_string()), db_port: Some(5433), db_user: Some("admin".to_string()), - db_password: Some(Zeroizing::new("secret".to_string())), + db_password: Some(RedactedSecret::from("secret")), tables: Some(vec!["users".to_string()]), compress: Some("xz".to_string()), timeout_seconds: None, @@ -496,12 +522,12 @@ mod tests { assert!(cmd.contains("| xz >")); } - /// Regression: FIND-029. The `db_password` field MUST be wrapped in - /// `Zeroizing` so the heap allocation is wiped on drop. - /// This test is load-bearing at the type level: only - /// `Option>` compiles below. + /// Regression: FIND-029 (F1-equivalent). The `db_password` field MUST be + /// a `RedactedSecret` so the heap allocation is wiped on drop AND the + /// secret cannot leak through `Debug`. This test is load-bearing at the + /// type level: only `Option` compiles below. #[test] - fn test_db_password_field_is_zeroizing() { + fn test_db_password_field_is_redacted_secret() { let args: SshDbDumpArgs = serde_json::from_value(json!({ "host": "h", "db_type": "mysql", @@ -511,12 +537,12 @@ mod tests { })) .expect("deserialize"); - // Type proof: `Option>::as_deref()` yields - // `Option<&String>`; bridge to `&str` via `.map(String::as_str)`. - let pw: Option<&str> = args.db_password.as_deref().map(String::as_str); + // Audited plaintext boundary: `Option::as_deref()` + // yields `Option<&str>` directly via `Deref`. + let pw: Option<&str> = args.db_password.as_deref(); assert_eq!(pw, Some("secret")); // Final type-pinning assertion: only compiles when the field is - // exactly `Option>`. - let _typed: &Option> = &args.db_password; + // exactly `Option`. + let _typed: &Option = &args.db_password; } } diff --git a/src/mcp/tool_handlers/ssh_db_query.rs b/src/mcp/tool_handlers/ssh_db_query.rs index c0a0767..32088d8 100644 --- a/src/mcp/tool_handlers/ssh_db_query.rs +++ b/src/mcp/tool_handlers/ssh_db_query.rs @@ -4,9 +4,8 @@ //! Supports `MySQL` and `PostgreSQL` using their respective CLI clients. use serde::Deserialize; -use zeroize::Zeroizing; -use crate::config::HostConfig; +use crate::config::{HostConfig, RedactedSecret}; use crate::domain::{DatabaseCommandBuilder, DatabaseType}; use crate::error::Result; use crate::mcp::standard_tool::{StandardTool, StandardToolHandler, impl_common_args}; @@ -25,13 +24,13 @@ pub struct SshDbQueryArgs { #[serde(default)] db_user: Option, /// DB password from MCP JSON-RPC request body. Wrapped in - /// `Zeroizing` so the heap allocation is wiped on drop - /// (FIND-029). Production read sites pass it to the builder as - /// `Option<&str>` via `.as_deref().map(String::as_str)` — `as_deref()` - /// peels `Zeroizing` -> `&String`, then `String::as_str` - /// gives the final `&str`. + /// `RedactedSecret` (FIND-029, F1-equivalent): the heap allocation is + /// wiped on drop and `Debug`/`Display`/`Serialize` emit `[REDACTED]`, + /// so the secret cannot leak through `format!("{args:?}")`. Production + /// read sites pass it to the builder as `Option<&str>` via `as_deref()` + /// (the single audited plaintext boundary, `Deref`). #[serde(default)] - db_password: Option>, + db_password: Option, #[serde(default)] format: Option, #[serde(default)] @@ -133,7 +132,7 @@ impl StandardTool for DbQueryTool { db_host, db_port, db_user, - args.db_password.as_deref().map(String::as_str), + args.db_password.as_deref(), &args.database, &args.query, args.format.as_deref(), @@ -335,10 +334,7 @@ mod tests { assert_eq!(args.db_host, Some("dbhost".to_string())); assert_eq!(args.db_port, Some(3307)); assert_eq!(args.db_user, Some("admin".to_string())); - assert_eq!( - args.db_password.as_deref().map(String::as_str), - Some("secret") - ); + assert_eq!(args.db_password.as_deref(), Some("secret")); assert_eq!(args.format, Some("csv".to_string())); assert_eq!(args.timeout_seconds, Some(120)); assert_eq!(args.max_output, Some(5000)); @@ -378,6 +374,34 @@ mod tests { assert!(debug_str.contains("test-host")); } + /// FIND-029 (F1-equivalent): the `db_password` field must not leak its + /// plaintext through `Debug`. `RedactedSecret`'s hand-written `Debug` + /// emits `[REDACTED]`, so the secret value must be absent from the + /// rendered struct. + #[test] + fn args_debug_does_not_leak_db_password() { + let args = SshDbQueryArgs { + host: "test-host".to_string(), + db_type: "mysql".to_string(), + query: "SELECT 1".to_string(), + database: "testdb".to_string(), + db_host: None, + db_port: None, + db_user: None, + db_password: Some(RedactedSecret::from("s3cr3t-pw")), + format: None, + timeout_seconds: None, + max_output: None, + save_output: None, + }; + let rendered = format!("{args:?}"); + assert!( + !rendered.contains("s3cr3t-pw"), + "db_password leaked in Debug: {rendered}" + ); + assert!(rendered.contains("[REDACTED]")); + } + #[tokio::test] async fn test_invalid_json_type() { let handler = SshDbQueryHandler::new(); @@ -489,7 +513,7 @@ mod tests { db_host: Some("dbhost".to_string()), db_port: Some(5433), db_user: Some("admin".to_string()), - db_password: Some(Zeroizing::new("secret".to_string())), + db_password: Some(RedactedSecret::from("secret")), format: None, timeout_seconds: None, max_output: None, @@ -528,12 +552,13 @@ mod tests { assert!(cmd.contains("-B")); } - /// Regression: FIND-029. The `db_password` field MUST be wrapped in - /// `Zeroizing` so the heap allocation is wiped on drop. + /// Regression: FIND-029 (F1-equivalent). The `db_password` field MUST be + /// a `RedactedSecret` so the heap allocation is wiped on drop AND the + /// secret is structurally incapable of leaking through `Debug`. /// This test is load-bearing at the type level: only - /// `Option>` compiles below. + /// `Option` compiles below. #[test] - fn test_db_password_field_is_zeroizing() { + fn test_db_password_field_is_redacted_secret() { let args: SshDbQueryArgs = serde_json::from_value(json!({ "host": "h", "db_type": "mysql", @@ -543,14 +568,13 @@ mod tests { })) .expect("deserialize"); - // Type proof: `Option>::as_deref()` yields - // `Option<&String>` (one level of deref through `Zeroizing`). - // We bridge to `Option<&str>` via `.map(String::as_str)`. - let pw: Option<&str> = args.db_password.as_deref().map(String::as_str); + // Audited plaintext boundary: `Option::as_deref()` + // yields `Option<&str>` directly via `Deref`. + let pw: Option<&str> = args.db_password.as_deref(); assert_eq!(pw, Some("secret")); // Final type-pinning assertion: this line only compiles when the - // field is exactly `Option>`. - let _typed: &Option> = &args.db_password; + // field is exactly `Option`. + let _typed: &Option = &args.db_password; } #[test] diff --git a/src/mcp/tool_handlers/ssh_db_restore.rs b/src/mcp/tool_handlers/ssh_db_restore.rs index 3a5472b..d6ef8fb 100644 --- a/src/mcp/tool_handlers/ssh_db_restore.rs +++ b/src/mcp/tool_handlers/ssh_db_restore.rs @@ -4,9 +4,8 @@ //! Supports `MySQL` and `PostgreSQL`. use serde::Deserialize; -use zeroize::Zeroizing; -use crate::config::HostConfig; +use crate::config::{HostConfig, RedactedSecret}; use crate::domain::{DatabaseCommandBuilder, DatabaseType}; use crate::error::Result; use crate::mcp::standard_tool::{StandardTool, StandardToolHandler, impl_common_args}; @@ -25,13 +24,13 @@ pub struct SshDbRestoreArgs { #[serde(default)] db_user: Option, /// DB password from MCP JSON-RPC request body. Wrapped in - /// `Zeroizing` so the heap allocation is wiped on drop - /// (FIND-029). Production read sites pass it to the builder as - /// `Option<&str>` via `.as_deref().map(String::as_str)` — `as_deref()` - /// peels `Zeroizing` -> `&String`, then `String::as_str` - /// gives the final `&str`. + /// `RedactedSecret` (FIND-029, F1-equivalent): the heap allocation is + /// wiped on drop and `Debug`/`Display`/`Serialize` emit `[REDACTED]`, + /// so the secret cannot leak through `format!("{args:?}")`. Production + /// read sites pass it to the builder as `Option<&str>` via `as_deref()` + /// (the single audited plaintext boundary, `Deref`). #[serde(default)] - db_password: Option>, + db_password: Option, #[serde(default)] timeout_seconds: Option, max_output: Option, @@ -112,7 +111,7 @@ impl StandardTool for DbRestoreTool { db_host, db_port, db_user, - args.db_password.as_deref().map(String::as_str), + args.db_password.as_deref(), &args.database, &args.input_file, )) @@ -252,10 +251,7 @@ mod tests { assert_eq!(args.db_host, Some("dbhost".to_string())); assert_eq!(args.db_port, Some(3307)); assert_eq!(args.db_user, Some("admin".to_string())); - assert_eq!( - args.db_password.as_deref().map(String::as_str), - Some("secret") - ); + assert_eq!(args.db_password.as_deref(), Some("secret")); assert_eq!(args.timeout_seconds, Some(600)); } @@ -293,6 +289,26 @@ mod tests { assert!(debug_str.contains("test-host")); } + /// FIND-029 (F1-equivalent): the `db_password` field must not leak its + /// plaintext through `Debug`. + #[test] + fn args_debug_does_not_leak_db_password() { + let args: SshDbRestoreArgs = serde_json::from_value(json!({ + "host": "test-host", + "db_type": "mysql", + "database": "testdb", + "input_file": "/tmp/dump.sql", + "db_password": "s3cr3t-pw", + })) + .expect("deserialize"); + let rendered = format!("{args:?}"); + assert!( + !rendered.contains("s3cr3t-pw"), + "db_password leaked in Debug: {rendered}" + ); + assert!(rendered.contains("[REDACTED]")); + } + #[tokio::test] async fn test_invalid_json_type() { let handler = SshDbRestoreHandler::new(); @@ -336,12 +352,12 @@ mod tests { } } - /// Regression: FIND-029. The `db_password` field MUST be wrapped in - /// `Zeroizing` so the heap allocation is wiped on drop. - /// This test is load-bearing at the type level: only - /// `Option>` compiles below. + /// Regression: FIND-029 (F1-equivalent). The `db_password` field MUST be + /// a `RedactedSecret` so the heap allocation is wiped on drop AND the + /// secret cannot leak through `Debug`. This test is load-bearing at the + /// type level: only `Option` compiles below. #[test] - fn test_db_password_field_is_zeroizing() { + fn test_db_password_field_is_redacted_secret() { let args: SshDbRestoreArgs = serde_json::from_value(json!({ "host": "h", "db_type": "postgresql", @@ -351,12 +367,12 @@ mod tests { })) .expect("deserialize"); - // Type proof: `Option>::as_deref()` yields - // `Option<&String>`; bridge to `&str` via `.map(String::as_str)`. - let pw: Option<&str> = args.db_password.as_deref().map(String::as_str); + // Audited plaintext boundary: `Option::as_deref()` + // yields `Option<&str>` directly via `Deref`. + let pw: Option<&str> = args.db_password.as_deref(); assert_eq!(pw, Some("secret")); // Final type-pinning assertion: only compiles when the field is - // exactly `Option>`. - let _typed: &Option> = &args.db_password; + // exactly `Option`. + let _typed: &Option = &args.db_password; } } diff --git a/src/mcp/tool_handlers/ssh_vault_write.rs b/src/mcp/tool_handlers/ssh_vault_write.rs index 60f2bb8..b37157f 100644 --- a/src/mcp/tool_handlers/ssh_vault_write.rs +++ b/src/mcp/tool_handlers/ssh_vault_write.rs @@ -1,6 +1,6 @@ use serde::Deserialize; -use crate::config::HostConfig; +use crate::config::{HostConfig, RedactedSecret}; use crate::domain::use_cases::vault::VaultCommandBuilder; use crate::error::Result; use crate::mcp::standard_tool::{StandardTool, StandardToolHandler, impl_common_args}; @@ -10,11 +10,12 @@ use crate::mcp_standard_tool; pub struct SshVaultWriteArgs { host: String, path: String, - /// FIND-030: each `key=value` pair is wrapped in `Zeroizing` so - /// the heap allocation is wiped when this `Args` instance is dropped. - /// Local heap residency was gratuitous — the secret only needs to live - /// long enough to build the remote `vault kv put` command. - data: Vec>, + /// FIND-030 (F1-equivalent): each `key=value` pair is a `RedactedSecret` + /// so the heap allocation is wiped when this `Args` instance is dropped + /// AND the whole pair (a value may itself be a secret) is structurally + /// incapable of leaking through `format!("{args:?}")`. The pair only + /// needs to live long enough to build the remote `vault kv put` command. + data: Vec, vault_addr: Option, mount: Option, timeout_seconds: Option, @@ -155,8 +156,9 @@ mod tests { let args: SshVaultWriteArgs = serde_json::from_value(json).unwrap(); assert_eq!(args.host, "myhost"); assert_eq!(args.path, "secret/data/myapp"); - // FIND-030: data is Vec>; deref to compare as &str. - let data_strs: Vec<&str> = args.data.iter().map(|s| s.as_str()).collect(); + // FIND-030: data is Vec; read via the audited + // `as_str()` boundary to compare as &str. + let data_strs: Vec<&str> = args.data.iter().map(RedactedSecret::as_str).collect(); assert_eq!(data_strs, vec!["username=admin", "password=secret123"]); assert_eq!( args.vault_addr.as_deref(), @@ -174,7 +176,7 @@ mod tests { let args: SshVaultWriteArgs = serde_json::from_value(json).unwrap(); assert_eq!(args.host, "myhost"); assert_eq!(args.path, "secret/data/myapp"); - let data_strs: Vec<&str> = args.data.iter().map(|s| s.as_str()).collect(); + let data_strs: Vec<&str> = args.data.iter().map(RedactedSecret::as_str).collect(); assert_eq!(data_strs, vec!["key=value"]); assert!(args.vault_addr.is_none()); assert!(args.mount.is_none()); @@ -204,6 +206,29 @@ mod tests { assert!(debug_str.contains("SshVaultWriteArgs")); } + /// FIND-030 (F1-equivalent): each `key=value` pair in `data` must not + /// leak its (possibly-secret) value through `Debug`. `RedactedSecret`'s + /// hand-written `Debug` emits `[REDACTED]`, so the secret value is absent. + #[test] + fn args_debug_does_not_leak_data() { + let args = SshVaultWriteArgs { + host: "myhost".to_string(), + path: "secret/data/myapp".to_string(), + data: vec![RedactedSecret::from("api_key=s3cr3t")], + vault_addr: None, + mount: None, + timeout_seconds: None, + max_output: None, + save_output: None, + }; + let rendered = format!("{args:?}"); + assert!( + !rendered.contains("s3cr3t"), + "vault data leaked in Debug: {rendered}" + ); + assert!(rendered.contains("[REDACTED]")); + } + #[tokio::test] async fn test_invalid_json_type() { let handler = SshVaultWriteHandler::new();