feat(libgsh): DID-sourced capability intersection via IntersectionInput
IntersectionInput replaces separate accord lookups with a DID-document- sourced three-way intersection: effective = posix_bounding & accord_allowed & delegation_remaining. denied_to_allowed_mask() converts denied capability names to a bitmask for the intersection. Includes workspace-level cleanup and gsh binary refactoring. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Tyler J King <tking@guildhouse.dev>
This commit is contained in:
parent
7c84854222
commit
d0b674f6cd
12 changed files with 682 additions and 924 deletions
652
Cargo.lock
generated
652
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
token: &Option<String>,
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
#[arg(long, global = true, default_value = "/run/substrate/fabric.sock")]
|
||||
fabric_socket: String,
|
||||
|
||||
#[arg(long, global = true)]
|
||||
agent_did: Option<String>,
|
||||
|
|
@ -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<i32> {
|
||||
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<i32> {
|
|||
|
||||
// ── 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<i32> {
|
|||
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<i32> {
|
|||
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<i32> {
|
|||
}
|
||||
|
||||
// ── 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<i32> {
|
|||
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))?;
|
||||
} else {
|
||||
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))?;
|
||||
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
|
||||
} else {
|
||||
// No AC and no broker — run ungoverned with warning
|
||||
eprintln!("gsh: no GSAP_AC or GSAP_BROKER_URL — running ungoverned");
|
||||
}
|
||||
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<i32> {
|
|||
} 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<i32> {
|
|||
} => {
|
||||
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<i32> {
|
|||
} => {
|
||||
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<i32> {
|
|||
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<i32> {
|
|||
|
||||
// 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);
|
||||
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 (broker session support pending)");
|
||||
eprintln!("gsh: session CR not recorded (fabric session support pending)");
|
||||
}
|
||||
(cr.receipt_id, cr.chronicle_cid)
|
||||
} else {
|
||||
eprintln!("gsh: no GSAP_BROKER_URL — CR not posted");
|
||||
(String::new(), String::new())
|
||||
};
|
||||
|
||||
// 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())]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
441
libgsh/src/capabilities.rs
Normal file
441
libgsh/src/capabilities.rs
Normal file
|
|
@ -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<u8> {
|
||||
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<u64, CapError> {
|
||||
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<u64, CapError> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
331
libgsh/src/cr.rs
331
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<String>,
|
||||
pub events: Vec<serde_json::Value>,
|
||||
pub merkle_root: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct CrAttestation {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CrResponse {
|
||||
pub receipt_id: Option<String>,
|
||||
pub chronicle_event_cid: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<String>) -> Result<Client, String> {
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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(),
|
||||
/// 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(),
|
||||
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(FabricResponse::Error(msg)) => {
|
||||
eprintln!("gsh: CR failed: {}", msg);
|
||||
CrResult { receipt_id: String::new(), chronicle_cid: String::new() }
|
||||
}
|
||||
Ok(r) => {
|
||||
eprintln!("gsh: CR failed: {}", r.status());
|
||||
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<String, String> {
|
||||
#[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<AcCtx>,
|
||||
}
|
||||
|
||||
#[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<FabricResponse, String> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ pub fn apply(
|
|||
shell_class: Option<&str>,
|
||||
posture_level: Option<u8>,
|
||||
capability_set: Option<u32>,
|
||||
cap_bounding: Option<u64>,
|
||||
) {
|
||||
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());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod ac;
|
||||
pub mod agent_api;
|
||||
pub mod capabilities;
|
||||
pub mod chronicle_events;
|
||||
pub mod classifier;
|
||||
pub mod config;
|
||||
|
|
|
|||
|
|
@ -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())),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ impl SessionState {
|
|||
self.shell_class.as_deref(),
|
||||
self.posture_level,
|
||||
self.capability_set,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue