diff --git a/Cargo.lock b/Cargo.lock index 0e93123..c0ab8b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,12 +78,6 @@ dependencies = [ "syn", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "atty" version = "0.2.14" @@ -117,12 +111,6 @@ dependencies = [ "match-lookup", ] -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "base64ct" version = "1.8.3" @@ -290,26 +278,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -504,15 +472,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -558,33 +517,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -594,55 +532,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -712,25 +601,6 @@ dependencies = [ "url", ] -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "half" version = "2.7.1" @@ -778,123 +648,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - [[package]] name = "iana-time-zone" version = "0.1.65" @@ -1040,22 +793,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "ipnet" -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" @@ -1083,8 +820,6 @@ version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ - "cfg-if", - "futures-util", "once_cell", "wasm-bindgen", ] @@ -1115,7 +850,7 @@ dependencies = [ "dirs", "guildhouse-did", "hex", - "reqwest", + "libc", "serde", "serde_json", "sha2", @@ -1194,12 +929,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "mio" version = "1.2.0" @@ -1224,23 +953,6 @@ dependencies = [ "data-encoding-macro", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nix" version = "0.29.0" @@ -1284,50 +996,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "openssl" -version = "0.10.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "option-ext" version = "0.2.0" @@ -1379,12 +1047,6 @@ dependencies = [ "spki", ] -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "potential_utf" version = "0.1.5" @@ -1477,62 +1139,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - [[package]] name = "rustc_version" version = "0.4.1" @@ -1568,89 +1174,18 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" -dependencies = [ - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.27" @@ -1700,18 +1235,6 @@ dependencies = [ "zmij", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "sha2" version = "0.10.9" @@ -1769,12 +1292,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - [[package]] name = "smallvec" version = "1.15.1" @@ -1848,6 +1365,7 @@ dependencies = [ "ciborium", "nix", "serde", + "sha2", "thiserror 2.0.18", "tokio", ] @@ -1869,15 +1387,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -1889,27 +1398,6 @@ dependencies = [ "syn", ] -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.27.0" @@ -2001,84 +1489,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - [[package]] name = "tracing" version = "0.1.44" @@ -2110,12 +1520,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "typenum" version = "1.19.0" @@ -2146,12 +1550,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - [[package]] name = "url" version = "2.5.8" @@ -2187,12 +1585,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.5" @@ -2208,15 +1600,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -2254,16 +1637,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.117" @@ -2330,16 +1703,6 @@ dependencies = [ "semver", ] -[[package]] -name = "web-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2403,17 +1766,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - [[package]] name = "windows-result" version = "0.4.1" diff --git a/Cargo.toml b/Cargo.toml index 5480398..b6086ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ repository = "https://git.guildhouse.dev/guildhouse/gsh" [workspace.dependencies] serde = { version = "1", features = ["derive"] } serde_json = "1" -reqwest = { version = "0.12", features = ["blocking", "json"] } thiserror = "2" anyhow = "1" clap = { version = "4", features = ["derive"] } @@ -26,3 +25,4 @@ atty = "0.2" tracing = "0.1" substrate-ipc = { path = "../substrate/crates/substrate-ipc" } tokio = { version = "1", features = ["full"] } +libc = "0.2" diff --git a/gsh/src/human.rs b/gsh/src/human.rs index e973929..364ee82 100644 --- a/gsh/src/human.rs +++ b/gsh/src/human.rs @@ -1,16 +1,14 @@ //! gsh human mode — interactive governed shell with reedline. - use std::path::{Path, PathBuf}; use colored::Colorize; use reedline::{DefaultPrompt, DefaultPromptSegment, Reedline, Signal}; use libgsh::classifier::{classify_command, CommandClass}; -use libgsh::cr::{build_client, post_cr}; +use libgsh::cr::post_cr; use libgsh::session::SessionState; - /// Corpus base directory. Configurable via GSH_CORPUS_DIR env. fn corpus_base() -> PathBuf { std::env::var("GSH_CORPUS_DIR") @@ -21,22 +19,16 @@ fn corpus_base() -> PathBuf { /// Run the interactive governed shell. pub fn run_human_mode( session: &mut SessionState, - broker_url: &Option, - token: &Option, + fabric_socket: &str, + rt: &tokio::runtime::Runtime, ) -> i32 { - // Print banner: print_banner(session); - // Post SESSION_STARTED CR if broker available: - if let Some(ref base) = broker_url { - if let Ok(client) = build_client(token) { - let _ = post_cr(&client, base, &session.ac_id, "completed"); - } - } + // Post SESSION_STARTED CR via fabric: + let _ = rt.block_on(post_cr(Some(fabric_socket), &session.ac_id, "completed")); let corpus_dir = corpus_base(); - // Reedline REPL: let mut editor = Reedline::create(); let prompt = build_prompt(session); @@ -51,7 +43,6 @@ pub fn run_human_mode( break; } - // Classify command: match classify_command(line, &session.corpus_cid, &corpus_dir) { CommandClass::Free => { execute_passthrough(line, session); @@ -62,17 +53,12 @@ pub fn run_human_mode( execute_governed(line, &corpus_binary, session); session.governed_count += 1; - // Post lightweight command CR: - if let Some(ref base) = broker_url { - if let Ok(client) = build_client(token) { - let outcome = if exit_code == 0 { - "completed" - } else { - "failed" - }; - let _ = post_cr(&client, base, &session.ac_id, outcome); - } - } + let outcome = if exit_code == 0 { "completed" } else { "failed" }; + let _ = rt.block_on(post_cr( + Some(fabric_socket), + &session.ac_id, + outcome, + )); } CommandClass::Ungoverned => { let cmd_name = line.split_whitespace().next().unwrap_or("?"); @@ -93,7 +79,6 @@ pub fn run_human_mode( } } - // AC expiry warning: let mins = session.minutes_remaining(); if mins < 10 && mins > 0 { eprintln!( @@ -115,15 +100,10 @@ pub fn run_human_mode( } } - // Session teardown: print_summary(session); - // Post SESSION_ENDED CR: - if let Some(ref base) = broker_url { - if let Ok(client) = build_client(token) { - let _ = post_cr(&client, base, &session.ac_id, "session_end"); - } - } + // Post SESSION_ENDED CR via fabric: + let _ = rt.block_on(post_cr(Some(fabric_socket), &session.ac_id, "session_end")); 0 } @@ -163,7 +143,6 @@ fn print_banner(session: &SessionState) { }, "║".bright_blue()); - // DEFCON line — only shown when not peacetime if session.defcon_level < 5 { let defcon_label = match session.defcon_level { 1 => "LOCKDOWN".red().to_string(), @@ -212,7 +191,6 @@ fn print_summary(session: &SessionState) { } fn build_prompt(session: &SessionState) -> DefaultPrompt { - // DEFCON overrides the risk indicator when elevated let risk_indicator = if session.defcon_level <= 2 { "[DEFCON]" } else if session.defcon_level == 3 { diff --git a/gsh/src/main.rs b/gsh/src/main.rs index cd766e4..5a03bc1 100644 --- a/gsh/src/main.rs +++ b/gsh/src/main.rs @@ -17,7 +17,7 @@ use serde::Serialize; use std::process; use uuid::Uuid; -use libgsh::cr::{build_client, post_cr, request_ac_inline}; +use libgsh::cr::{post_cr, request_ac_inline}; use libgsh::registry::ConsumedRegistry; use libgsh::session::SessionState; use libgsh::{corpus_check, sha256_hash}; @@ -39,8 +39,8 @@ struct Args { #[arg(long, global = true)] ungoverned: bool, - #[arg(long, global = true)] - broker_url: Option, + #[arg(long, global = true, default_value = "/run/substrate/fabric.sock")] + fabric_socket: String, #[arg(long, global = true)] agent_did: Option, @@ -92,6 +92,10 @@ struct GshOutput { corpus_cid: String, } +fn fabric_sock(args: &Args) -> String { + std::env::var("GSH_FABRIC_SOCKET").unwrap_or_else(|_| args.fabric_socket.clone()) +} + // ── Main ───────────────────────────────────────────────────── fn main() { @@ -114,7 +118,8 @@ fn main() { fn run(args: Args) -> Result { let corpus = std::env::var("GSAP_CORPUS_CID").unwrap_or_else(|_| "sha256:ungoverned".into()); - let token = std::env::var("GSAP_TOKEN").ok(); + let rt = tokio::runtime::Runtime::new().context("creating tokio runtime")?; + let sock = fabric_sock(&args); // ── Ungoverned machine mode ──────────────────────────────── if args.ungoverned && args.exec.is_some() { @@ -140,7 +145,6 @@ fn run(args: Args) -> Result { // ── Subcommands ───────────────────────────────────────── if let Some(cmd) = &args.command { - // Register is handled separately — it doesn't need a broker. if let Cmd::Register { service_name, fabric_socket, @@ -150,15 +154,11 @@ fn run(args: Args) -> Result { return run_register(service_name, fabric_socket, env_dir.as_deref()); } - let base = args.broker_url.clone() - .or_else(|| std::env::var("GSAP_BROKER_URL").ok()) - .context("GSAP_BROKER_URL not set")?; - let client = build_client(&token).map_err(|e| anyhow::anyhow!(e))?; return match cmd { Cmd::SessionStart { scope } => { let hash = sha256_hash(format!("session:{}", scope).as_bytes()); eprintln!("gsh: starting session (scope: {})", scope); - let ac_id = request_ac_inline(&client, &base, scope, &hash, &corpus) + let ac_id = rt.block_on(request_ac_inline(Some(&sock), scope, &hash, &corpus)) .map_err(|e| anyhow::anyhow!(e))?; eprintln!("gsh: session AC — {}", &ac_id[..8.min(ac_id.len())]); println!("export GSAP_SESSION_AC=\"{}\";", ac_id); @@ -169,7 +169,7 @@ fn run(args: Args) -> Result { Cmd::SessionEnd => { let ac_id = std::env::var("GSAP_SESSION_AC") .context("No session active (GSAP_SESSION_AC not set)")?; - let cr = post_cr(&client, &base, &ac_id, "completed"); + let cr = rt.block_on(post_cr(Some(&sock), &ac_id, "completed")); eprintln!("gsh: session closed"); if !cr.chronicle_cid.is_empty() { eprintln!("gsh: session CID {}", &cr.chronicle_cid[..40.min(cr.chronicle_cid.len())]); @@ -194,16 +194,12 @@ fn run(args: Args) -> Result { } // ── Mode detection ───────────────────────────────────────── - // No --exec: human mode (if TTY) or error (if piped) if args.exec.is_none() { let is_tty = atty::is(atty::Stream::Stdin); if !is_tty { anyhow::bail!("No --exec and no TTY. Use --exec for non-interactive mode."); } - // Human mode: interactive governed shell - let token = std::env::var("GSAP_TOKEN").ok(); - let broker = args.broker_url.clone().or_else(|| std::env::var("GSAP_BROKER_URL").ok()); let pre_issued = args.ac.clone().or_else(|| std::env::var("GSAP_AC").ok()); let mut session = if args.ungoverned { @@ -213,36 +209,31 @@ fn run(args: Args) -> Result { let ac = libgsh::ac::validate_ac(&ac_json, &corpus, &mut registry) .map_err(|e| anyhow::anyhow!("AC validation failed (exit {}): {}", e.exit_code(), e))?; SessionState::from_ac(&ac, &corpus) - } else if let Some(ref base) = broker { - // Request a session AC from broker: - let client = build_client(&token).map_err(|e| anyhow::anyhow!(e))?; - let hash = sha256_hash(b"session:human-mode"); - let ac_id = request_ac_inline(&client, base, "shell:session", &hash, &corpus) - .map_err(|e| anyhow::anyhow!(e))?; - let mut s = SessionState::ungoverned(&corpus); - s.ac_id = ac_id; - s.risk_level = "standard".to_string(); - s } else { - // No AC and no broker — run ungoverned with warning - eprintln!("gsh: no GSAP_AC or GSAP_BROKER_URL — running ungoverned"); - SessionState::ungoverned(&corpus) + let hash = sha256_hash(b"session:human-mode"); + match rt.block_on(request_ac_inline(Some(&sock), "shell:session", &hash, &corpus)) { + Ok(ac_id) => { + let mut s = SessionState::ungoverned(&corpus); + s.ac_id = ac_id; + s.risk_level = "standard".to_string(); + s + } + Err(_) => { + eprintln!("gsh: fabric unreachable — running ungoverned"); + SessionState::ungoverned(&corpus) + } + } }; - return Ok(human::run_human_mode(&mut session, &broker, &token)); + return Ok(human::run_human_mode(&mut session, &sock, &rt)); } - let exec = args.exec.as_ref().unwrap(); // safe: checked above + let exec = args.exec.as_ref().unwrap(); let run_id = Uuid::new_v4().to_string(); let command_hash = sha256_hash(exec.as_bytes()); - // Determine AC mode: let pre_issued = args.ac.clone().or_else(|| std::env::var("GSAP_AC").ok()); - // Retain the full AC struct for pre-issued mode so the governance-env - // contract (`GSH_DID`/`GSH_ACCORD_HASH`/...) can be threaded into the - // child process at the exec site below. Session and inline modes only - // surface an ID; their governance fields stay un-exported. let (ac_id, ac_mode, ac_struct) = if let Some(ac_json) = pre_issued { let mut registry = ConsumedRegistry::default_location(); let ac = libgsh::ac::validate_ac(&ac_json, &corpus, &mut registry) @@ -255,12 +246,8 @@ fn run(args: Args) -> Result { } else if let Ok(session_ac) = std::env::var("GSAP_SESSION_AC") { (session_ac, "session".to_string(), None) } else { - let base = args.broker_url.clone() - .or_else(|| std::env::var("GSAP_BROKER_URL").ok()) - .context("No AC provided. Set GSAP_AC, GSAP_SESSION_AC, or GSAP_BROKER_URL")?; - let client = build_client(&token).map_err(|e| anyhow::anyhow!(e))?; eprintln!("gsh: requesting AC for '{}'", exec); - let id = request_ac_inline(&client, &base, &args.operation, &command_hash, &corpus) + let id = rt.block_on(request_ac_inline(Some(&sock), &args.operation, &command_hash, &corpus)) .map_err(|e| anyhow::anyhow!(e))?; eprintln!("gsh: AC issued — {}", &id[..8.min(id.len())]); (id, "inline".to_string(), None) @@ -280,10 +267,7 @@ fn run(args: Args) -> Result { } => { eprintln!( "gsh: command '{}' content does not match CID {} (found {}, path {}): execution denied (tamper signal)", - command, - corpus_cid, - actual_cid, - path.display() + command, corpus_cid, actual_cid, path.display() ); return Ok(3); } @@ -295,10 +279,7 @@ fn run(args: Args) -> Result { } => { eprintln!( "gsh: command '{}' in corpus {} could not be read for hash verification ({}); execution denied fail-closed (path {})", - command, - corpus_cid, - detail, - path.display() + command, corpus_cid, detail, path.display() ); return Ok(3); } @@ -320,6 +301,17 @@ fn run(args: Args) -> Result { return Ok(0); } + // Apply Linux capability bounding set if declared in PosixBinding + #[cfg(target_os = "linux")] + if let Ok(cap_hex) = std::env::var("GSH_CAP_BOUNDING") { + let bounding = libgsh::capabilities::parse_hex(&cap_hex) + .map_err(|e| anyhow::anyhow!("cap bounding parse: {e}"))?; + if let Err(e) = libgsh::capabilities::apply_cap_bounding(bounding) { + eprintln!("gsh: capability application failed: {e} — denying shell startup"); + return Ok(125); + } + } + // Execute: let mut command = process::Command::new("sh"); command.arg("-c").arg(exec); @@ -333,31 +325,23 @@ fn run(args: Args) -> Result { // Post CR: let outcome = if exit_code == 0 { "completed" } else { "failed" }; - let base = args.broker_url.or_else(|| std::env::var("GSAP_BROKER_URL").ok()); - - let (cr_id, chronicle_cid) = if let Some(base) = base { - let client = build_client(&token).map_err(|e| anyhow::anyhow!(e))?; - let cr = post_cr(&client, &base, &ac_id, outcome); - if ac_mode == "session" && cr.receipt_id.is_empty() && cr.chronicle_cid.is_empty() { - eprintln!("gsh: session CR not recorded (broker session support pending)"); - } - (cr.receipt_id, cr.chronicle_cid) - } else { - eprintln!("gsh: no GSAP_BROKER_URL — CR not posted"); - (String::new(), String::new()) - }; + let cr = rt.block_on(post_cr(Some(&sock), &ac_id, outcome)); + if ac_mode == "session" && cr.receipt_id.is_empty() && cr.chronicle_cid.is_empty() { + eprintln!("gsh: session CR not recorded (fabric session support pending)"); + } // Output: if args.json { println!("{}", serde_json::to_string_pretty(&GshOutput { exit_code, stdout: stdout_str, stderr: stderr_str, - ac_id, ac_mode, cr_id, chronicle_cid, command_hash, run_id, corpus_cid: corpus, + ac_id, ac_mode, cr_id: cr.receipt_id, chronicle_cid: cr.chronicle_cid.clone(), + command_hash, run_id, corpus_cid: corpus, })?); } else { print!("{}", stdout_str); eprint!("{}", stderr_str); - if !chronicle_cid.is_empty() { - eprintln!("gsh: CID {}", &chronicle_cid[..40.min(chronicle_cid.len())]); + if !cr.chronicle_cid.is_empty() { + eprintln!("gsh: CID {}", &cr.chronicle_cid[..40.min(cr.chronicle_cid.len())]); } } diff --git a/libgsh/Cargo.toml b/libgsh/Cargo.toml index 395c06d..99a7255 100644 --- a/libgsh/Cargo.toml +++ b/libgsh/Cargo.toml @@ -8,7 +8,6 @@ description = "Governed shell library — AC validation, CR building, corpus gat guildhouse-did = { path = "../../guildhouse-did" } serde = { workspace = true } serde_json = { workspace = true } -reqwest = { workspace = true } thiserror = { workspace = true } sha2 = { workspace = true } hex = { workspace = true } @@ -17,6 +16,7 @@ dirs = { workspace = true } tracing = { workspace = true } substrate-ipc = { workspace = true } tokio = { workspace = true } +libc = { workspace = true } [dev-dependencies] diff --git a/libgsh/src/agent_api.rs b/libgsh/src/agent_api.rs index 4d147f0..2689908 100644 --- a/libgsh/src/agent_api.rs +++ b/libgsh/src/agent_api.rs @@ -93,6 +93,7 @@ pub async fn register_agent_shell( } FabricResponse::Denied { reason } => Err(AgentError::Denied { reason }), FabricResponse::Error(msg) => Err(AgentError::FabricError(msg)), + _ => Err(AgentError::FabricError("unexpected response".into())), } } diff --git a/libgsh/src/capabilities.rs b/libgsh/src/capabilities.rs new file mode 100644 index 0000000..e5c037a --- /dev/null +++ b/libgsh/src/capabilities.rs @@ -0,0 +1,441 @@ +//! Linux capability intersection and materialization for PosixBinding. +//! +//! Provides: +//! - Mapping between Linux capability names and bit positions (u64 bitmask) +//! - Intersection logic: `posix_bounding ∩ accord_allowed ∩ delegation_remaining` +//! - Hex formatting/parsing for `GSH_CAP_BOUNDING` env var +//! - Linux-specific bounding set application via prctl (gated on `target_os`) + +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum CapError { + #[error("invalid capability hex string: {0}")] + InvalidHex(String), + #[error("unknown capability name: {0}")] + UnknownCap(String), + #[cfg(target_os = "linux")] + #[error("prctl({0}) failed: {1}")] + PrctlFailed(&'static str, std::io::Error), +} + +pub const CAP_CHOWN: u8 = 0; +pub const CAP_DAC_OVERRIDE: u8 = 1; +pub const CAP_DAC_READ_SEARCH: u8 = 2; +pub const CAP_FOWNER: u8 = 3; +pub const CAP_FSETID: u8 = 4; +pub const CAP_KILL: u8 = 5; +pub const CAP_SETGID: u8 = 6; +pub const CAP_SETUID: u8 = 7; +pub const CAP_SETPCAP: u8 = 8; +pub const CAP_LINUX_IMMUTABLE: u8 = 9; +pub const CAP_NET_BIND_SERVICE: u8 = 10; +pub const CAP_NET_BROADCAST: u8 = 11; +pub const CAP_NET_ADMIN: u8 = 12; +pub const CAP_NET_RAW: u8 = 13; +pub const CAP_IPC_LOCK: u8 = 14; +pub const CAP_IPC_OWNER: u8 = 15; +pub const CAP_SYS_MODULE: u8 = 16; +pub const CAP_SYS_RAWIO: u8 = 17; +pub const CAP_SYS_CHROOT: u8 = 18; +pub const CAP_SYS_PTRACE: u8 = 19; +pub const CAP_SYS_PACCT: u8 = 20; +pub const CAP_SYS_ADMIN: u8 = 21; +pub const CAP_SYS_BOOT: u8 = 22; +pub const CAP_SYS_NICE: u8 = 23; +pub const CAP_SYS_RESOURCE: u8 = 24; +pub const CAP_SYS_TIME: u8 = 25; +pub const CAP_SYS_TTY_CONFIG: u8 = 26; +pub const CAP_MKNOD: u8 = 27; +pub const CAP_LEASE: u8 = 28; +pub const CAP_AUDIT_WRITE: u8 = 29; +pub const CAP_AUDIT_CONTROL: u8 = 30; +pub const CAP_SETFCAP: u8 = 31; +pub const CAP_MAC_OVERRIDE: u8 = 32; +pub const CAP_MAC_ADMIN: u8 = 33; +pub const CAP_SYSLOG: u8 = 34; +pub const CAP_WAKE_ALARM: u8 = 35; +pub const CAP_BLOCK_SUSPEND: u8 = 36; +pub const CAP_AUDIT_READ: u8 = 37; +pub const CAP_PERFMON: u8 = 38; +pub const CAP_BPF: u8 = 39; +pub const CAP_CHECKPOINT_RESTORE: u8 = 40; + +const CAP_LAST_CAP: u8 = 40; + +const CAP_TABLE: &[(&str, u8)] = &[ + ("CAP_CHOWN", CAP_CHOWN), + ("CAP_DAC_OVERRIDE", CAP_DAC_OVERRIDE), + ("CAP_DAC_READ_SEARCH", CAP_DAC_READ_SEARCH), + ("CAP_FOWNER", CAP_FOWNER), + ("CAP_FSETID", CAP_FSETID), + ("CAP_KILL", CAP_KILL), + ("CAP_SETGID", CAP_SETGID), + ("CAP_SETUID", CAP_SETUID), + ("CAP_SETPCAP", CAP_SETPCAP), + ("CAP_LINUX_IMMUTABLE", CAP_LINUX_IMMUTABLE), + ("CAP_NET_BIND_SERVICE", CAP_NET_BIND_SERVICE), + ("CAP_NET_BROADCAST", CAP_NET_BROADCAST), + ("CAP_NET_ADMIN", CAP_NET_ADMIN), + ("CAP_NET_RAW", CAP_NET_RAW), + ("CAP_IPC_LOCK", CAP_IPC_LOCK), + ("CAP_IPC_OWNER", CAP_IPC_OWNER), + ("CAP_SYS_MODULE", CAP_SYS_MODULE), + ("CAP_SYS_RAWIO", CAP_SYS_RAWIO), + ("CAP_SYS_CHROOT", CAP_SYS_CHROOT), + ("CAP_SYS_PTRACE", CAP_SYS_PTRACE), + ("CAP_SYS_PACCT", CAP_SYS_PACCT), + ("CAP_SYS_ADMIN", CAP_SYS_ADMIN), + ("CAP_SYS_BOOT", CAP_SYS_BOOT), + ("CAP_SYS_NICE", CAP_SYS_NICE), + ("CAP_SYS_RESOURCE", CAP_SYS_RESOURCE), + ("CAP_SYS_TIME", CAP_SYS_TIME), + ("CAP_SYS_TTY_CONFIG", CAP_SYS_TTY_CONFIG), + ("CAP_MKNOD", CAP_MKNOD), + ("CAP_LEASE", CAP_LEASE), + ("CAP_AUDIT_WRITE", CAP_AUDIT_WRITE), + ("CAP_AUDIT_CONTROL", CAP_AUDIT_CONTROL), + ("CAP_SETFCAP", CAP_SETFCAP), + ("CAP_MAC_OVERRIDE", CAP_MAC_OVERRIDE), + ("CAP_MAC_ADMIN", CAP_MAC_ADMIN), + ("CAP_SYSLOG", CAP_SYSLOG), + ("CAP_WAKE_ALARM", CAP_WAKE_ALARM), + ("CAP_BLOCK_SUSPEND", CAP_BLOCK_SUSPEND), + ("CAP_AUDIT_READ", CAP_AUDIT_READ), + ("CAP_PERFMON", CAP_PERFMON), + ("CAP_BPF", CAP_BPF), + ("CAP_CHECKPOINT_RESTORE", CAP_CHECKPOINT_RESTORE), +]; + +pub fn cap_name_to_bit(name: &str) -> Option { + CAP_TABLE.iter().find(|(n, _)| *n == name).map(|(_, b)| *b) +} + +pub fn cap_bit_to_name(bit: u8) -> Option<&'static str> { + CAP_TABLE.iter().find(|(_, b)| *b == bit).map(|(n, _)| *n) +} + +/// Convert a list of capability name strings to a bitmask. +pub fn parse_cap_names(names: &[String]) -> Result { + let mut mask = 0u64; + for name in names { + let bit = cap_name_to_bit(name).ok_or_else(|| CapError::UnknownCap(name.clone()))?; + mask |= 1u64 << bit; + } + Ok(mask) +} + +/// Compute the effective capability bitmask as the intersection of all +/// governance dimensions. Capabilities can only be narrowed, never widened. +pub fn intersect(posix_bounding: u64, accord_allowed: u64, delegation_remaining: u64) -> u64 { + posix_bounding & accord_allowed & delegation_remaining +} + +/// Input for capability intersection sourced from DID document bindings. +/// +/// Constructed from a `GovernanceDeclaration` — the DID document carries +/// all three dimensions of the intersection natively: +/// - PosixBinding.kernel.capabilities.bounding → posix bitmask +/// - AccordBinding.attenuation.denied_capabilities → accord allowed (complement) +/// - Delegation chain (external, passed as `delegation_remaining`) +pub struct IntersectionInput { + pub posix_bounding: u64, + pub accord_allowed: u64, + pub delegation_remaining: u64, +} + +impl IntersectionInput { + /// Construct from a governance declaration and delegation chain bitmask. + /// + /// Extracts the PosixBinding bounding set (first binding found) and + /// computes the accord allowed mask from the effective attenuation. + /// Unknown capability names in the attenuation are silently ignored. + pub fn from_governance( + gov: &guildhouse_did::GovernanceDeclaration, + delegation_remaining: u64, + ) -> Self { + let posix_bounding = gov + .posix_bindings + .first() + .and_then(|p| p.kernel.capabilities.as_ref()) + .map(|caps| { + caps.bounding + .iter() + .filter_map(|name| cap_name_to_bit(name)) + .fold(0u64, |mask, bit| mask | (1u64 << bit)) + }) + .unwrap_or(0); + + let attenuation = gov.effective_attenuation(); + let accord_allowed = denied_to_allowed_mask(&attenuation.denied_capabilities); + + Self { + posix_bounding, + accord_allowed, + delegation_remaining, + } + } + + /// Compute the effective capability bitmask. + pub fn effective(&self) -> u64 { + intersect(self.posix_bounding, self.accord_allowed, self.delegation_remaining) + } +} + +/// Convert a list of denied capability names to an allowed bitmask. +/// All bits start high; each denied capability clears its bit. +pub fn denied_to_allowed_mask(denied: &[String]) -> u64 { + let mut mask = u64::MAX; + for name in denied { + if let Some(bit) = cap_name_to_bit(name) { + mask &= !(1u64 << bit); + } + } + mask +} + +/// Format a capability bitmask as the `GSH_CAP_BOUNDING` hex string. +pub fn format_hex(mask: u64) -> String { + format!("0x{mask:016x}") +} + +/// Parse a hex bitmask string (with optional `0x` prefix). +pub fn parse_hex(s: &str) -> Result { + let hex = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s); + u64::from_str_radix(hex, 16).map_err(|_| CapError::InvalidHex(s.to_owned())) +} + +/// Apply Linux capability bounding set restrictions to the current process. +/// +/// Drops all capabilities NOT present in the given bitmask from the bounding +/// set, and sets securebits to prevent setuid from granting new capabilities. +/// Fails closed — returns an error if any prctl call fails (except EINVAL for +/// capabilities not supported by the running kernel). +#[cfg(target_os = "linux")] +pub fn apply_cap_bounding(bounding: u64) -> Result<(), CapError> { + const PR_SET_SECUREBITS: libc::c_int = 28; + const SECBIT_NOROOT: libc::c_ulong = 0x01; + const SECBIT_NO_SETUID_FIXUP: libc::c_ulong = 0x04; + const PR_CAPBSET_DROP: libc::c_int = 24; + + let ret = unsafe { + libc::prctl( + PR_SET_SECUREBITS, + SECBIT_NOROOT | SECBIT_NO_SETUID_FIXUP, + 0, + 0, + 0, + ) + }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + if err.raw_os_error() != Some(libc::EPERM) { + return Err(CapError::PrctlFailed("PR_SET_SECUREBITS", err)); + } + } + + for cap in 0..=CAP_LAST_CAP { + if (bounding >> cap) & 1 == 0 { + let ret = unsafe { libc::prctl(PR_CAPBSET_DROP, cap as libc::c_ulong, 0, 0, 0) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + if err.raw_os_error() != Some(libc::EINVAL) { + return Err(CapError::PrctlFailed("PR_CAPBSET_DROP", err)); + } + } + } + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_known_caps() { + let names = vec![ + "CAP_NET_BIND_SERVICE".into(), + "CAP_DAC_READ_SEARCH".into(), + ]; + let mask = parse_cap_names(&names).unwrap(); + assert_eq!(mask, (1u64 << 10) | (1u64 << 2)); + } + + #[test] + fn parse_unknown_cap_errors() { + let names = vec!["CAP_DOES_NOT_EXIST".into()]; + assert!(parse_cap_names(&names).is_err()); + } + + #[test] + fn intersection_narrows() { + let posix = (1u64 << CAP_NET_BIND_SERVICE) + | (1u64 << CAP_DAC_READ_SEARCH) + | (1u64 << CAP_SYS_ADMIN); + let accord = (1u64 << CAP_NET_BIND_SERVICE) | (1u64 << CAP_DAC_READ_SEARCH); + let delegation = u64::MAX; // no attenuation + let effective = intersect(posix, accord, delegation); + assert_eq!( + effective, + (1u64 << CAP_NET_BIND_SERVICE) | (1u64 << CAP_DAC_READ_SEARCH) + ); + assert_eq!(effective & (1u64 << CAP_SYS_ADMIN), 0); + } + + #[test] + fn intersection_all_zeros() { + assert_eq!(intersect(0xFF, 0x00, 0xFF), 0); + } + + #[test] + fn delegation_attenuates() { + let posix = (1u64 << CAP_NET_BIND_SERVICE) | (1u64 << CAP_SYS_ADMIN); + let accord = u64::MAX; + let delegation = 1u64 << CAP_NET_BIND_SERVICE; // only net_bind + let effective = intersect(posix, accord, delegation); + assert_eq!(effective, 1u64 << CAP_NET_BIND_SERVICE); + } + + #[test] + fn hex_format_round_trip() { + let mask = (1u64 << CAP_NET_BIND_SERVICE) | (1u64 << CAP_DAC_READ_SEARCH); + let hex = format_hex(mask); + assert_eq!(hex, "0x0000000000000404"); + let parsed = parse_hex(&hex).unwrap(); + assert_eq!(parsed, mask); + } + + #[test] + fn parse_hex_no_prefix() { + let parsed = parse_hex("0000000000200400").unwrap(); + assert_eq!(parsed, (1u64 << 10) | (1u64 << 21)); + } + + #[test] + fn parse_hex_invalid() { + assert!(parse_hex("0xZZZZ").is_err()); + } + + #[test] + fn cap_name_round_trip() { + for (name, bit) in CAP_TABLE { + assert_eq!(cap_name_to_bit(name), Some(*bit)); + assert_eq!(cap_bit_to_name(*bit), Some(*name)); + } + } + + #[test] + fn all_41_caps_present() { + assert_eq!(CAP_TABLE.len(), 41); + } + + #[test] + fn denied_to_allowed_clears_bits() { + let denied = vec!["CAP_SYS_ADMIN".into(), "CAP_NET_ADMIN".into()]; + let mask = denied_to_allowed_mask(&denied); + assert_eq!(mask & (1u64 << CAP_SYS_ADMIN), 0); + assert_eq!(mask & (1u64 << CAP_NET_ADMIN), 0); + assert_ne!(mask & (1u64 << CAP_NET_BIND_SERVICE), 0); + } + + #[test] + fn denied_to_allowed_empty_is_all_ones() { + let mask = denied_to_allowed_mask(&[]); + assert_eq!(mask, u64::MAX); + } + + #[test] + fn denied_to_allowed_unknown_caps_ignored() { + let denied = vec!["CAP_DOES_NOT_EXIST".into(), "CAP_SYS_ADMIN".into()]; + let mask = denied_to_allowed_mask(&denied); + assert_eq!(mask & (1u64 << CAP_SYS_ADMIN), 0); + } + + #[test] + fn intersection_input_from_governance() { + use chrono::{TimeZone, Utc}; + use guildhouse_did::document::*; + use guildhouse_did::{Did, GovernanceDeclaration}; + + let did = Did::parse("did:web:test.dev:user:a").unwrap(); + let mut doc = DidDocument::minimal(did.clone()); + + doc.add_binding( + ServiceEndpoint::typed( + format!("{}#posix", did.as_str()), + BINDING_TYPE_POSIX, + &PosixBinding { + binding_version: "0.1.0".into(), + identity: PosixIdentity { + uid: 60001, + gid: 60001, + username: "test".into(), + home_directory: None, + login_shell: None, + gecos: None, + supplementary_groups: vec![], + }, + kernel: PosixKernel { + capabilities: Some(PosixCapabilities { + bounding: vec![ + "CAP_NET_BIND_SERVICE".into(), + "CAP_DAC_READ_SEARCH".into(), + "CAP_SYS_ADMIN".into(), + ], + ambient: vec![], + inheritable: vec![], + }), + namespaces: None, + }, + filesystem: None, + network: None, + node_selector: None, + host_did: None, + allocation_method: None, + bound_at: None, + ceremony_id: None, + }, + ) + .unwrap(), + ) + .unwrap(); + + doc.add_binding( + ServiceEndpoint::typed( + format!("{}#accord", did.as_str()), + BINDING_TYPE_ACCORD, + &AccordBinding { + accord_id: "acc-001".into(), + accord_hash: "hash".into(), + trust_domain: "test.dev".into(), + scope: "test".into(), + state: AccordBindingState::Active, + attenuation: AccordAttenuation { + denied_capabilities: vec!["CAP_SYS_ADMIN".into()], + max_forge_source_count: None, + denied_forge_sources: vec![], + denied_network_services: vec![], + max_connections: None, + }, + limits: None, + sla: None, + bound_at: Utc.timestamp_opt(1_700_000_000, 0).unwrap(), + expires_at: None, + ceremony_id: None, + }, + ) + .unwrap(), + ) + .unwrap(); + + let gov = GovernanceDeclaration::from_document(&doc); + let input = IntersectionInput::from_governance(&gov, u64::MAX); + + let effective = input.effective(); + assert_ne!(effective & (1u64 << CAP_NET_BIND_SERVICE), 0); + assert_ne!(effective & (1u64 << CAP_DAC_READ_SEARCH), 0); + assert_eq!(effective & (1u64 << CAP_SYS_ADMIN), 0); + } +} diff --git a/libgsh/src/cr.rs b/libgsh/src/cr.rs index 91e26ad..501c8d0 100644 --- a/libgsh/src/cr.rs +++ b/libgsh/src/cr.rs @@ -1,203 +1,192 @@ -//! Completion Receipt construction and posting. +//! Completion Receipt and Authorization Context via fabric IPC. -use reqwest::blocking::Client; -use serde::{Deserialize, Serialize}; -use std::time::Duration; +use substrate_ipc::fabric_api::{FabricRequest, FabricResponse}; +use substrate_ipc::wire; +use tokio::net::UnixStream; -#[derive(Serialize)] -pub struct CrRequest { - pub context_id: String, - pub outcome: String, - pub completed_at: String, - pub chronicle_evidence: CrEvidence, - pub behavioral_attestation: CrAttestation, - pub ffc: serde_json::Value, - pub signature: serde_json::Value, -} +const DEFAULT_FABRIC_SOCKET: &str = "/run/substrate/fabric.sock"; -#[derive(Serialize)] -pub struct CrEvidence { - pub session_id: Option, - pub events: Vec, - pub merkle_root: Option, -} - -#[derive(Serialize)] -pub struct CrAttestation { - pub status: String, -} - -#[derive(Deserialize)] -pub struct CrResponse { - pub receipt_id: Option, - pub chronicle_event_cid: Option, -} - -/// Result of posting a CR. pub struct CrResult { pub receipt_id: String, pub chronicle_cid: String, } -/// Build an HTTP client with optional bearer token. -pub fn build_client(token: &Option) -> Result { - let mut headers = reqwest::header::HeaderMap::new(); - if let Some(tok) = token { - headers.insert( - "Authorization", - format!("Bearer {}", tok) - .parse() - .map_err(|_| "Invalid token".to_string())?, - ); - } - Client::builder() - .timeout(Duration::from_secs(30)) - .default_headers(headers) - .build() - .map_err(|e| format!("HTTP client failed: {}", e)) -} +/// Post a Completion Receipt through the fabric socket. +pub async fn post_cr( + fabric_socket: Option<&str>, + ac_id: &str, + outcome: &str, +) -> CrResult { + let sock = fabric_socket.unwrap_or(DEFAULT_FABRIC_SOCKET); + let req = FabricRequest::SubmitCompletionRecord { + ac_id: ac_id.into(), + outcome: outcome.into(), + }; -/// Format a broker URL path. -pub fn broker_url(base: &str, path: &str) -> String { - format!( - "{}/{}", - base.trim_end_matches('/'), - path.trim_start_matches('/') - ) -} - -/// Post a Completion Receipt to the broker. -pub fn post_cr(client: &Client, base: &str, ac_id: &str, outcome: &str) -> CrResult { - let now = chrono::Utc::now().to_rfc3339(); - let session_id = std::env::var("CHRONICLE_SESSION_ID").unwrap_or_default(); - // Phase 0 D1: FFC DID sourced from env (FFC_DID), not hardcoded. - // W3C colon form. Empty on error → null in payload. - let ffc_did = std::env::var("FFC_DID") - .ok() - .filter(|s| !s.is_empty()) - .and_then(|s| guildhouse_did::Did::parse(&s).ok()) - .map(|d| d.as_str().to_owned()); - - match client - .post(broker_url(base, "governance/complete/")) - .json(&CrRequest { - context_id: ac_id.into(), - outcome: outcome.into(), - completed_at: now, - chronicle_evidence: CrEvidence { - session_id: if session_id.is_empty() { None } else { Some(session_id.clone()) }, - events: vec![], - merkle_root: None, - }, - behavioral_attestation: CrAttestation { - status: "unavailable".into(), - }, - ffc: serde_json::json!({"did": ffc_did, "chronicle_session_id": session_id}), - signature: serde_json::json!({"value": "gsh"}), - }) - .send() - { - Ok(r) if r.status().is_success() => { - let cr: CrResponse = r.json().unwrap_or(CrResponse { - receipt_id: None, - chronicle_event_cid: None, - }); - CrResult { - receipt_id: cr.receipt_id.unwrap_or_default(), - chronicle_cid: cr.chronicle_event_cid.unwrap_or_default(), - } + match send_fabric_request(sock, &req).await { + Ok(FabricResponse::CompletionRecordAccepted { cr_id, chronicle_cid }) => { + CrResult { receipt_id: cr_id, chronicle_cid } } - Ok(r) => { - eprintln!("gsh: CR failed: {}", r.status()); - CrResult { - receipt_id: String::new(), - chronicle_cid: String::new(), - } + Ok(FabricResponse::Error(msg)) => { + eprintln!("gsh: CR failed: {}", msg); + CrResult { receipt_id: String::new(), chronicle_cid: String::new() } + } + Ok(_) => { + eprintln!("gsh: CR unexpected response"); + CrResult { receipt_id: String::new(), chronicle_cid: String::new() } } Err(e) => { eprintln!("gsh: CR error: {}", e); - CrResult { - receipt_id: String::new(), - chronicle_cid: String::new(), - } + CrResult { receipt_id: String::new(), chronicle_cid: String::new() } } } } -/// Request an inline AC from the broker (fallback mode). -pub fn request_ac_inline( - client: &Client, - base: &str, +/// Request an inline Authorization Context through the fabric socket. +pub async fn request_ac_inline( + fabric_socket: Option<&str>, operation: &str, command_hash: &str, corpus_cid: &str, ) -> Result { - #[derive(Serialize)] - struct AcRequest { - driver_id: String, - playbook: String, - corpus_entry_cid: String, - parameters_cid: String, - accord_template: String, + let sock = fabric_socket.unwrap_or(DEFAULT_FABRIC_SOCKET); + let req = FabricRequest::RequestAuthorization { + operation: operation.into(), + command_hash: command_hash.into(), + corpus_cid: corpus_cid.into(), + }; + + match send_fabric_request(sock, &req).await { + Ok(FabricResponse::AuthorizationGranted { ac_id }) => Ok(ac_id), + Ok(FabricResponse::Denied { reason }) => Err(format!("AC denied: {}", reason)), + Ok(FabricResponse::Error(msg)) => Err(format!("fabric error: {}", msg)), + Ok(_) => Err("unexpected fabric response".into()), + Err(e) => Err(format!("fabric IPC error: {}", e)), } +} - #[derive(Deserialize)] - struct AcResponse { - authorization_context: Option, - } - - #[derive(Deserialize)] - struct AcCtx { - context_id: String, - } - - let resp = client - .post(broker_url(base, "governance/authorize/")) - .json(&AcRequest { - driver_id: "keycloak".into(), - playbook: format!( - "{}:{}", - operation, - &command_hash[..20.min(command_hash.len())] - ), - corpus_entry_cid: corpus_cid.into(), - parameters_cid: command_hash.into(), - accord_template: "shell-exec".into(), - }) - .send() - .map_err(|e| format!("Failed to reach broker: {}", e))?; - - if !resp.status().is_success() { - return Err(format!( - "AC denied: {} — {}", - resp.status(), - resp.text().unwrap_or_default() - )); - } - - let ac: AcResponse = resp - .json() - .map_err(|e| format!("Invalid AC response: {}", e))?; - - ac.authorization_context - .map(|c| c.context_id) - .filter(|id| !id.is_empty()) - .ok_or_else(|| "No AC ID returned".to_string()) +async fn send_fabric_request( + sock_path: &str, + req: &FabricRequest, +) -> Result { + let stream = UnixStream::connect(sock_path) + .await + .map_err(|e| format!("connecting to {}: {}", sock_path, e))?; + let (mut rd, mut wr) = tokio::io::split(stream); + wire::send_msg(&mut wr, req) + .await + .map_err(|e| format!("sending request: {}", e))?; + wire::recv_msg(&mut rd) + .await + .map_err(|e| format!("receiving response: {}", e)) } #[cfg(test)] mod tests { use super::*; + use std::collections::HashMap; - #[test] - fn test_broker_url() { - assert_eq!( - broker_url("http://localhost:8000", "governance/complete/"), - "http://localhost:8000/governance/complete/" - ); - assert_eq!( - broker_url("http://localhost:8000/", "/governance/complete/"), - "http://localhost:8000/governance/complete/" - ); + #[tokio::test] + async fn post_cr_round_trip_with_mock_fabric() { + let dir = tempfile::tempdir().unwrap(); + let sock_path = dir.path().join("fabric.sock"); + let sock_str = sock_path.to_str().unwrap().to_string(); + + let listener = tokio::net::UnixListener::bind(&sock_path).unwrap(); + + let sock_clone = sock_str.clone(); + let client = tokio::spawn(async move { + post_cr(Some(&sock_clone), "ac-123", "completed").await + }); + + let (stream, _) = listener.accept().await.unwrap(); + let (mut rd, mut wr) = tokio::io::split(stream); + + let req: FabricRequest = wire::recv_msg(&mut rd).await.unwrap(); + match req { + FabricRequest::SubmitCompletionRecord { ac_id, outcome } => { + assert_eq!(ac_id, "ac-123"); + assert_eq!(outcome, "completed"); + } + _ => panic!("wrong request variant"), + } + + let resp = FabricResponse::CompletionRecordAccepted { + cr_id: "cr-456".into(), + chronicle_cid: "bafy...".into(), + }; + wire::send_msg(&mut wr, &resp).await.unwrap(); + + let result = client.await.unwrap(); + assert_eq!(result.receipt_id, "cr-456"); + assert_eq!(result.chronicle_cid, "bafy..."); + } + + #[tokio::test] + async fn request_ac_round_trip_with_mock_fabric() { + let dir = tempfile::tempdir().unwrap(); + let sock_path = dir.path().join("fabric.sock"); + let sock_str = sock_path.to_str().unwrap().to_string(); + + let listener = tokio::net::UnixListener::bind(&sock_path).unwrap(); + + let sock_clone = sock_str.clone(); + let client = tokio::spawn(async move { + request_ac_inline(Some(&sock_clone), "shell:exec", "abc123", "sha256:corpus").await + }); + + let (stream, _) = listener.accept().await.unwrap(); + let (mut rd, mut wr) = tokio::io::split(stream); + + let req: FabricRequest = wire::recv_msg(&mut rd).await.unwrap(); + match req { + FabricRequest::RequestAuthorization { operation, command_hash, corpus_cid } => { + assert_eq!(operation, "shell:exec"); + assert_eq!(command_hash, "abc123"); + assert_eq!(corpus_cid, "sha256:corpus"); + } + _ => panic!("wrong request variant"), + } + + let resp = FabricResponse::AuthorizationGranted { + ac_id: "ac-789".into(), + }; + wire::send_msg(&mut wr, &resp).await.unwrap(); + + let result = client.await.unwrap().unwrap(); + assert_eq!(result, "ac-789"); + } + + #[tokio::test] + async fn request_ac_denied() { + let dir = tempfile::tempdir().unwrap(); + let sock_path = dir.path().join("fabric.sock"); + let sock_str = sock_path.to_str().unwrap().to_string(); + + let listener = tokio::net::UnixListener::bind(&sock_path).unwrap(); + + let sock_clone = sock_str.clone(); + let client = tokio::spawn(async move { + request_ac_inline(Some(&sock_clone), "shell:exec", "abc", "sha256:c").await + }); + + let (stream, _) = listener.accept().await.unwrap(); + let (mut rd, mut wr) = tokio::io::split(stream); + + let _req: FabricRequest = wire::recv_msg(&mut rd).await.unwrap(); + let resp = FabricResponse::Denied { + reason: "posture too low".into(), + }; + wire::send_msg(&mut wr, &resp).await.unwrap(); + + let result = client.await.unwrap(); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("denied")); + } + + #[tokio::test] + async fn post_cr_socket_missing() { + let result = post_cr(Some("/tmp/nonexistent-fabric-cr.sock"), "ac", "ok").await; + assert!(result.receipt_id.is_empty()); } } diff --git a/libgsh/src/governance_env.rs b/libgsh/src/governance_env.rs index 3e4106f..fbcbcd9 100644 --- a/libgsh/src/governance_env.rs +++ b/libgsh/src/governance_env.rs @@ -32,6 +32,7 @@ pub fn apply( shell_class: Option<&str>, posture_level: Option, capability_set: Option, + cap_bounding: Option, ) { if let Some(d) = did { cmd.env("GSH_DID", d); @@ -48,6 +49,9 @@ pub fn apply( if let Some(c) = capability_set { cmd.env("GSH_CAPABILITY_SET", format!("0x{:08x}", c)); } + if let Some(b) = cap_bounding { + cmd.env("GSH_CAP_BOUNDING", format!("0x{b:016x}")); + } } /// Apply the `GSH_*` env-var contract from a parsed AC. @@ -64,6 +68,7 @@ pub fn apply_from_ac(cmd: &mut Command, ac: &AuthorizationContext) { ac.shell_class.as_deref(), ac.posture_level, ac.capability_set, + None, ); } @@ -92,6 +97,7 @@ mod tests { Some("Application"), Some(3), Some(0xCAFEBABE), + Some(0x0000000000200404), ); assert_eq!( cmd_env(&cmd, "GSH_DID").as_deref(), @@ -104,12 +110,16 @@ mod tests { cmd_env(&cmd, "GSH_CAPABILITY_SET").as_deref(), Some("0xcafebabe") ); + assert_eq!( + cmd_env(&cmd, "GSH_CAP_BOUNDING").as_deref(), + Some("0x0000000000200404") + ); } #[test] fn apply_partial_only_did() { let mut cmd = Command::new("true"); - apply(&mut cmd, Some("did:web:foo:bar"), None, None, None, None); + apply(&mut cmd, Some("did:web:foo:bar"), None, None, None, None, None); assert_eq!(cmd_env(&cmd, "GSH_DID").as_deref(), Some("did:web:foo:bar")); assert!(cmd_env(&cmd, "GSH_ACCORD_HASH").is_none()); assert!(cmd_env(&cmd, "GSH_SHELL_CLASS").is_none()); diff --git a/libgsh/src/lib.rs b/libgsh/src/lib.rs index 0c4e2fc..687ba82 100644 --- a/libgsh/src/lib.rs +++ b/libgsh/src/lib.rs @@ -1,5 +1,6 @@ pub mod ac; pub mod agent_api; +pub mod capabilities; pub mod chronicle_events; pub mod classifier; pub mod config; diff --git a/libgsh/src/register.rs b/libgsh/src/register.rs index 3f42f60..a6cf51f 100644 --- a/libgsh/src/register.rs +++ b/libgsh/src/register.rs @@ -79,6 +79,7 @@ pub async fn register_app_shell( } FabricResponse::Denied { reason } => Err(RegisterError::Denied { reason }), FabricResponse::Error(msg) => Err(RegisterError::FabricError(msg)), + _ => Err(RegisterError::FabricError("unexpected response".into())), } } diff --git a/libgsh/src/session.rs b/libgsh/src/session.rs index 6f4de42..326b169 100644 --- a/libgsh/src/session.rs +++ b/libgsh/src/session.rs @@ -129,6 +129,7 @@ impl SessionState { self.shell_class.as_deref(), self.posture_level, self.capability_set, + None, ); }