diff --git a/CLAUDE.md b/CLAUDE.md index b8a5ca33..46654271 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/Cargo.lock b/Cargo.lock index 0a7eecb7..75097648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,23 +8,13 @@ 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" 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,27 +31,14 @@ 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", -] - -[[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", - "aes 0.9.0", - "cipher 0.5.1", - "ctr 0.10.0", - "ghash 0.6.0", + "aead", + "aes 0.9.1", + "cipher 0.5.2", + "ctr", + "ghash", "subtle", + "zeroize", ] [[package]] @@ -85,7 +63,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 +104,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,15 +169,24 @@ 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" +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", ] @@ -229,15 +215,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 +235,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 +264,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 +275,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 +287,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 +302,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 +312,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 +329,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 +353,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 +378,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 +403,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 +423,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 +453,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 +472,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 +494,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 +524,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 +550,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 +577,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 +625,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 +648,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 +679,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body 1.0.1", "http-body-util", "mime", @@ -687,6 +690,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" @@ -717,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]] @@ -758,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]] @@ -781,6 +790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", + "zeroize", ] [[package]] @@ -803,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]] @@ -824,9 +834,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" @@ -858,27 +868,18 @@ 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.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 +887,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" @@ -904,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" @@ -922,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]] @@ -979,13 +965,14 @@ 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", + "zeroize", ] [[package]] @@ -1012,9 +999,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 +1035,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 +1068,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 +1090,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 +1108,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" @@ -1238,6 +1205,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.3" @@ -1267,9 +1246,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", @@ -1282,37 +1261,44 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21f41f23de7d24cdbda7f0c4d9c0351f99a4ceb258ef30e5c1927af8987ffe5a" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.7.3", "libm", "rand_core 0.10.1", ] [[package]] name = "ctr" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher 0.4.4", + "cipher 0.5.2", ] [[package]] -name = "ctr" -version = "0.10.0" +name = "ctutils" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17469f8eb9bdbfad10f71f4cfddfd38b01143520c0e717d8796ccb4d44d44e42" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ - "cipher 0.5.1", + "cmov", + "subtle", ] [[package]] -name = "ctutils" -version = "0.4.2" +name = "curve25519-dalek" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ - "cmov", + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", "subtle", + "zeroize", ] [[package]] @@ -1324,8 +1310,8 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.11.2", - "fiat-crypto", + "digest 0.11.3", + "fiat-crypto 0.3.0", "rustc_version", "subtle", "zeroize", @@ -1342,6 +1328,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" @@ -1366,6 +1366,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", ] @@ -1410,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" @@ -1424,13 +1434,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 +1467,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,70 +1490,129 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "ecdsa" -version = "0.17.0-rc.17" +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.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4bf51f0534ed6e59a0f2f26272b64ba55c470133f8424c2adfd1c4d59d9988" +checksum = "54fb064faabbee66e1fc8e5c5a9458d4269dc2d8b638fe86a425adb2510d1a96" dependencies = [ "der 0.8.0", - "digest 0.11.2", - "elliptic-curve", - "rfc6979", - "signature 3.0.0-rc.10", + "digest 0.11.3", + "elliptic-curve 0.14.0-rc.32", + "rfc6979 0.5.0", + "signature 3.0.0", "spki 0.8.0", "zeroize", ] [[package]] name = "ed25519" -version = "3.0.0-rc.4" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8 0.11.0-rc.11", - "signature 3.0.0-rc.10", + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "pkcs8 0.11.0", + "signature 3.0.0", ] [[package]] name = "ed25519-dalek" -version = "3.0.0-pre.6" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053618a4c3d3bc24f188aa660ae75a46eeab74ef07fb415c61431e5e7cd4749b" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "curve25519-dalek", - "ed25519", + "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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20449acd54b660981ae5caa2bcb56d1fe7f25f2e37a38ec507400fab034d4bb6" +dependencies = [ + "curve25519-dalek 5.0.0-pre.6", + "ed25519 3.0.0", "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.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b148a81cede8f4023248f980cffdf7611c46f2add469c6980e815b7c5b764ba5" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", - "crypto-common 0.2.1", - "digest 0.11.2", - "hkdf", + "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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda94f31325c4275e9706adecbb6f0650dee2f904c915a98e3d81adaaaa757aa" +dependencies = [ + "base16ct 1.0.0", + "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", - "sec1", + "sec1 0.8.1", "subtle", "zeroize", ] @@ -1606,6 +1675,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" @@ -1614,13 +1699,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 +1723,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" @@ -1783,13 +1855,14 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[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", @@ -1839,21 +1912,44 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "opaque-debug", - "polyval 0.6.2", + "polyval", ] [[package]] -name = "ghash" -version = "0.6.0" +name = "gloo-timers" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +checksum = "482ce8a491a501da4cd806bd190275363d674f2845005c6ddbd5d3e1dd54495d" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "polyval 0.7.1", + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -1877,16 +1973,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 +2001,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 +2018,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 +2028,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" @@ -1950,6 +2046,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" @@ -1974,7 +2079,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 +2095,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 +2121,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 +2132,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 +2151,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 +2187,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 +2228,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 +2245,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 +2262,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 +2400,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 +2415,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", ] @@ -2364,39 +2469,10 @@ dependencies = [ "console", "once_cell", "serde", - "similar", + "similar 2.7.0", "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", - "ecdsa", - "ed25519-dalek", - "hex", - "hmac 0.13.0", - "num-bigint-dig", - "p256", - "p384", - "p521", - "rand_core 0.10.1", - "rsa 0.10.0-rc.17", - "sec1", - "sha1 0.11.0", - "sha2 0.11.0", - "signature 3.0.0-rc.10", - "ssh-cipher", - "ssh-encoding", - "subtle", - "zeroize", -] - [[package]] name = "internal-russh-num-bigint" version = "0.5.0" @@ -2434,16 +2510,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 +2590,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 +2600,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 +2631,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 +2690,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", @@ -2644,17 +2715,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]] @@ -2685,7 +2765,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 +2781,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 +2810,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 +2822,7 @@ dependencies = [ "k8s-openapi", "kube-core", "pem", - "rustls 0.23.39", + "rustls 0.23.40", "secrecy", "serde", "serde_json", @@ -2764,7 +2844,7 @@ checksum = "f126d2db7a8b532ec1d839ece2a71e2485dc3bbca6cc3c3f929becaa810e719e" dependencies = [ "derive_more", "form_urlencoded", - "http 1.4.0", + "http 1.4.1", "jiff", "k8s-openapi", "serde", @@ -2802,23 +2882,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 +2921,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,14 +2992,14 @@ dependencies = [ "psrp-rs", "rayon", "regex", - "reqwest 0.13.1", + "reqwest", "russh", "russh-sftp", "serde", "serde-saphyr", "serde_json", - "sha2 0.10.9", - "similar", + "sha2 0.11.0", + "similar 3.1.1", "tempfile", "thiserror 2.0.18", "tokio", @@ -2957,7 +3034,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 +3043,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 +3054,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 +3085,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,22 +3110,23 @@ dependencies = [ [[package]] name = "ml-kem" -version = "0.3.0-rc.2" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04437cb1a66c0b78740927b76cc61f218344b9f6ef3dd430e283274a718ef0e9" +checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66" dependencies = [ "hybrid-array", "kem", "module-lattice", + "pkcs8 0.11.0", "rand_core 0.10.1", "sha3", ] [[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 +3158,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", @@ -3154,16 +3232,15 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.6", - "serde", "smallvec", "zeroize", ] [[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 +3272,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" @@ -3223,12 +3290,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" @@ -3237,9 +3298,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", @@ -3251,41 +3312,41 @@ 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.0", + "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.0", + "http 1.4.1", "opentelemetry", "opentelemetry-http", "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", @@ -3296,15 +3357,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", @@ -3332,30 +3394,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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b97e3bf0465157ae90975ff52dbeb1362ba618924878c9f74c25baa27a65f9a" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.17.0-rc.18", + "elliptic-curve 0.14.0-rc.32", "primefield", - "primeorder", + "primeorder 0.14.0-rc.9", "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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "437f30ebcb1e16ff48acead5f08bd69fbcdbc82421687bb48af5c315a0bfab03" dependencies = [ - "ecdsa", - "elliptic-curve", - "fiat-crypto", + "ecdsa 0.17.0-rc.18", + "elliptic-curve 0.14.0-rc.32", + "fiat-crypto 0.3.0", "primefield", - "primeorder", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] @@ -3365,11 +3451,11 @@ version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e9fd792bab86ecf6249561752fb5a413511f999887107dd054bbda5143743d7" dependencies = [ - "base16ct", - "ecdsa", - "elliptic-curve", + "base16ct 1.0.0", + "ecdsa 0.17.0-rc.18", + "elliptic-curve 0.14.0-rc.32", "primefield", - "primeorder", + "primeorder 0.14.0-rc.9", "sha2 0.11.0", ] @@ -3385,33 +3471,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 1.0.0", "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,30 +3497,18 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "password-hash" -version = "0.5.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b" dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", + "phc", ] [[package]] @@ -3452,7 +3517,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", ] @@ -3533,20 +3598,30 @@ 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.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", @@ -3588,15 +3663,14 @@ dependencies = [ [[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.0", - "aes-gcm 0.11.0-rc.3", - "cbc 0.2.0", + "aes 0.9.1", + "cbc", "der 0.8.0", - "pbkdf2 0.13.0", + "pbkdf2", "rand_core 0.10.1", "scrypt", "sha2 0.11.0", @@ -3615,9 +3689,9 @@ 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", @@ -3625,12 +3699,6 @@ dependencies = [ "spki 0.8.0", ] -[[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" @@ -3661,25 +3729,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]] @@ -3690,7 +3746,7 @@ checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" dependencies = [ "cpubits", "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", ] [[package]] @@ -3748,21 +3804,30 @@ version = "0.14.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b52e6ee42db392378a95622b463c9740631171d1efce43fa445a569c1600cb6" dependencies = [ - "crypto-bigint", - "crypto-common 0.2.1", + "crypto-bigint 0.7.3", + "crypto-common 0.2.2", "rand_core 0.10.1", "rustcrypto-ff", "subtle", "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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0556580e42c19833f5d232aca11a7687a503ee41f937b54f5ae1d50fc2a6a36a" dependencies = [ - "elliptic-curve", + "elliptic-curve 0.14.0-rc.32", ] [[package]] @@ -3816,6 +3881,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" @@ -3864,8 +3938,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 +3959,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 +3977,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -3956,7 +4030,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", ] @@ -4043,15 +4117,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" @@ -4106,51 +4171,19 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.4.0", - "http-body 1.0.1", - "http-body-util", - "hyper 1.9.0", - "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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" -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 +4191,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 +4206,23 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", ] [[package]] name = "rfc6979" -version = "0.5.0-rc.5" +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" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a3127ee32baec36af75b4107082d9bd823501ec14a4e016be4b6b37faa74ae" +checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" dependencies = [ "hmac 0.13.0", "subtle", @@ -4223,116 +4265,126 @@ dependencies = [ [[package]] name = "rsa" -version = "0.10.0-rc.17" +version = "0.10.0-rc.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ed3e93fc7e473e464b9726f4759659e72bc8665e4b8ea227547024f416d905" +checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf" dependencies = [ "const-oid 0.10.2", - "crypto-bigint", + "crypto-bigint 0.7.3", "crypto-primes", - "digest 0.11.2", + "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-rc.10", + "signature 3.0.0", "spki 0.8.0", "zeroize", ] [[package]] name = "russh" -version = "0.60.1" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d937f3f4a79bffd67fc12fd437785effdfc8b94edc89ab90392f9ac9e11cc9fc" +checksum = "f67013f080c226e5a34db1c71f2567f44d95a6300005bb6cd4e2c8fe3c326d1b" dependencies = [ - "aes 0.8.4", + "aes 0.9.1", "aws-lc-rs", "bitflags 2.11.1", - "block-padding 0.3.3", + "block-padding 0.4.2", "byteorder", "bytes", - "cbc 0.1.2", - "cipher 0.5.1", - "crypto-bigint", - "ctr 0.9.2", - "curve25519-dalek", + "cbc", + "cipher 0.5.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", - "ed25519-dalek", - "elliptic-curve", + "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.3.5", + "generic-array 1.4.3", "getrandom 0.2.17", + "ghash", "hex-literal", - "hmac 0.12.1", + "hkdf 0.13.0", + "hmac 0.13.0", "inout 0.1.4", - "internal-russh-forked-ssh-key", "internal-russh-num-bigint", + "keccak", "log", "md5", "ml-kem", "module-lattice", - "p256", - "p384", + "num-bigint", + "p256 0.14.0-rc.9", + "p384 0.14.0-rc.9", "p521", "pageant", - "pbkdf2 0.12.2", + "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.17", + "rsa 0.10.0-rc.18", "russh-cryptovec", "russh-util", - "sec1", - "sha1 0.10.6", - "sha2 0.10.9", - "signature 3.0.0-rc.10", + "salsa20", + "scrypt", + "sec1 0.8.1", + "sha1 0.11.0", + "sha2 0.11.0", + "sha3", + "signature 3.0.0", "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.59.0" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36140e8a20297bc2e8338807c3d9ca911f7fa49d7539cbcd6d48d3befd70efd8" +checksum = "443f6bbcfacb34a1aab2b12b99bf08e0c63abdc5a0db261901365df9d57fff51" 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 +4462,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 +4500,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 +4578,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]] @@ -4538,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" @@ -4571,7 +4612,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", ] @@ -4586,13 +4627,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", @@ -4633,12 +4688,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" @@ -4663,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", @@ -4691,6 +4739,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 +4771,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,11 +4820,11 @@ 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", + "base16ct 1.0.0", "serde", ] @@ -4807,7 +4865,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -4829,7 +4887,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -4838,7 +4896,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 +4911,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 +4937,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,12 +4951,37 @@ 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" 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" @@ -4935,9 +5018,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", @@ -4971,31 +5054,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]] @@ -5140,15 +5255,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 +5292,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 +5333,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 +5353,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 +5402,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,15 +5428,26 @@ 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", "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" @@ -5352,18 +5469,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 +5487,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "url", "uuid", ] @@ -5432,9 +5549,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", @@ -5499,7 +5616,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http 1.4.1", "httparse", "log", "rand 0.9.4", @@ -5516,9 +5633,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" @@ -5559,23 +5676,13 @@ 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" 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 +5742,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 +5823,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 +5836,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 +5846,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 +5856,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 +5869,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 +5912,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 +6080,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 +6107,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 +6149,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 +6161,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 +6173,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 +6197,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 +6209,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 +6221,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 +6233,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 +6247,20 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winrm-rs" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99d0b3bbecaddf971ea5c2739b801b5a347c186a2b0996deb543f8a9f13cf5" +checksum = "6296df157160d4d327779aad2c45ed189979955282cc52c323fc8a93c0efefb9" dependencies = [ "base64", "hmac 0.13.0", "md-5", "md4", "rand 0.10.1", - "reqwest 0.13.1", - "rustls 0.23.39", + "reqwest", + "rustls 0.23.40", "secrecy", "sha2 0.11.0", + "subtle", "thiserror 2.0.18", "tokio", "tokio-util", @@ -6359,18 +6401,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 +6421,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", ] @@ -6405,6 +6447,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]] @@ -6445,8 +6499,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" diff --git a/Cargo.toml b/Cargo.toml index 27858cfd..092f7909 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,13 +65,13 @@ 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 serde = { version = "1", features = ["derive"] } serde_json = "1" -serde-saphyr = "=0.0.21" +serde-saphyr = "=0.0.27" # Error handling thiserror = "2" @@ -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"] } @@ -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 } @@ -141,14 +141,14 @@ 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 } -similar = "2.6" +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 = "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" @@ -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" } diff --git a/deny.toml b/deny.toml index 12edddae..15c6095f 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/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 00000000..970278ea --- /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/cli/runner.rs b/src/cli/runner.rs index 38347d62..1dc8fd89 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/mod.rs b/src/config/mod.rs index f4ae031a..f3a21fe4 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 00000000..328e0947 --- /dev/null +++ b/src/config/secret.rs @@ -0,0 +1,142 @@ +//! `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). +/// +/// # 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); + +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()); + } +} diff --git a/src/config/types.rs b/src/config/types.rs index bfc2ca8d..bb12bfcd 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)] @@ -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")] @@ -248,12 +252,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 +462,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 +490,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 +1461,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 +1494,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 +1517,32 @@ 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") + ); + } + + // ============== 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 ============== @@ -1655,7 +1702,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/domain/use_cases/vault.rs b/src/domain/use_cases/vault.rs index 732f1233..20443f32 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/domain/yaml.rs b/src/domain/yaml.rs index 00d007e9..7fca442c 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, + }, } } diff --git a/src/mcp/tool_handlers/ssh_db_dump.rs b/src/mcp/tool_handlers/ssh_db_dump.rs index f1d8e714..51480fb9 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 c0a07676..32088d8d 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 3a5472b1..d6ef8fb4 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_status.rs b/src/mcp/tool_handlers/ssh_status.rs index 265aa732..60d95dff 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/mcp/tool_handlers/ssh_vault_write.rs b/src/mcp/tool_handlers/ssh_vault_write.rs index 60f2bb8f..b37157fe 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(); diff --git a/src/psrp/mod.rs b/src/psrp/mod.rs index b9ae762d..be7d569b 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 003c1d03..931f1a8e 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/security/recording.rs b/src/security/recording.rs index 6b4cd0b7..9e196be2 100644 --- a/src/security/recording.rs +++ b/src/security/recording.rs @@ -11,6 +11,7 @@ use std::sync::Mutex; use std::time::Instant; use chrono::{DateTime, Utc}; +use const_hex::encode as hex_encode; use serde::{Deserialize, Serialize}; 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 diff --git a/src/security/sanitizer.rs b/src/security/sanitizer.rs index 32147735..580b87f4 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,35 @@ 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_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(); diff --git a/src/ssh/client.rs b/src/ssh/client.rs index e33d933c..cd2a0447 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 ae35115f..eb94dfb7 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 69323829..ed2e9334 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(), diff --git a/tests/e2e_docker.rs b/tests/e2e_docker.rs index 00cf6783..92b37e98 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: zeroize::Zeroizing::new("testpass123".to_string()), + 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(zeroize::Zeroizing::new("testpass123".to_string())), + 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 b95ec848..eaa51f2a 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,11 +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(zeroize::Zeroizing::new), + passphrase: key.passphrase.clone().map(RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: zeroize::Zeroizing::new(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 6bc1346b..826370b0 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,11 +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(zeroize::Zeroizing::new), + passphrase: key.passphrase.clone().map(RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: zeroize::Zeroizing::new(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 9894828e..6df1c091 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,11 +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(zeroize::Zeroizing::new), + passphrase: key.passphrase.clone().map(RedactedSecret::from), } } else if let Some(ref password) = config.auth.password { AuthConfig::Password { - password: zeroize::Zeroizing::new(password.clone()), + password: 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 d5eb610a..418e1c2e 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 4eb0c17d..43346eb9 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]