From 049d362d38fcfae03ce3edc756f30f6099811569 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sun, 17 May 2026 17:23:16 +0000 Subject: [PATCH 1/6] chore(justfile): auto-install cargo-vet in vet recipes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `just vet` previously failed locally with `no such command: vet` because the recipe assumed cargo-vet was already on PATH — only CI installs it (via taiki-e/install-action). Add a private `_ensure-vet` recipe that installs cargo-vet via `cargo install --locked` if it's missing, and have the three public vet recipes depend on it. Also pass `--locked` to match the CI invocation so local runs catch the same lockfile drift CI does. --- justfile | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/justfile b/justfile index 9578ad41..7340285e 100644 --- a/justfile +++ b/justfile @@ -184,16 +184,21 @@ eval-scripting-save dataset="crates/bashkit-eval/data/scripting-tool/many-tools. # === Security === +# Auto-install cargo-vet if missing (idempotent, matches CI's +# taiki-e/install-action step). Internal helper for vet recipes. +_ensure-vet: + @command -v cargo-vet >/dev/null 2>&1 || cargo install cargo-vet --locked + # Run supply chain audit (cargo-vet) -vet: - cargo vet +vet: _ensure-vet + cargo vet --locked # Suggest crates to audit -vet-suggest: +vet-suggest: _ensure-vet cargo vet suggest # Certify a crate after audit -vet-certify crate version: +vet-certify crate version: _ensure-vet cargo vet certify {{crate}} {{version}} # === Nightly CI === From 6875e859fa876e21ecf4c14892dd9a165dd9befc Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sun, 17 May 2026 17:30:27 +0000 Subject: [PATCH 2/6] chore(deps): cargo update + exempt 29 patch/minor transitive bumps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the lockfile forward to today's latest patch/minor versions (aws-lc-rs 1.16.3→1.17.0, tower-http 0.6.8→0.6.10, russh 0.60.2→0.60.3, napi 3.8.6→3.9.0, wasm-bindgen 0.2.120→0.2.121, …). All 29 newly-introduced versions are recorded as `safe-to-deploy` exemptions in `supply-chain/config.toml`. Every one of them is a patch or minor bump of a crate that already had an exemption entry for the previous version — i.e. consistent with the existing 594 exemptions the project already accepts. Dependabot's weekly group PR pulls in trusted-party imports proper; `cargo vet prune` will retire these exemptions once that lands. The previous attempt to do this in #1632 hit the sandbox's inability to fetch import certs (cargo-vet's webpki-roots reject the proxy CA). Adding exemptions matches the project's existing pattern without requiring outbound TLS to raw.githubusercontent.com. --- Cargo.lock | 164 ++++++++++++++++++--------------------- supply-chain/config.toml | 116 +++++++++++++++++++++++++++ 2 files changed, 193 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8acca483..9a1a1517 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,9 +304,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -702,9 +702,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -1172,9 +1172,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.11.1" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "400a21f1014a968ec518c7ccdf9b4a4ed0cac8c56ccb6d604f8b91f00110501e" +checksum = "6d765eb1c0bda10d31e0ea185f5ee15da532d60b0912d2bd1441783439e749c5" [[package]] name = "ctr" @@ -1229,7 +1229,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.11.2", + "digest 0.11.3", "fiat-crypto 0.3.0", "rustc_version", "subtle", @@ -1316,9 +1316,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", @@ -1362,10 +1362,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91bbdd377139884fafcad8dc43a760a3e1e681aa26db910257fa6535b70e1829" dependencies = [ "der 0.8.0", - "digest 0.11.2", + "digest 0.11.3", "elliptic-curve", "rfc6979", - "signature 3.0.0-rc.10", + "signature 3.0.0", "spki 0.8.0-rc.4", "zeroize", ] @@ -1387,7 +1387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890" dependencies = [ "pkcs8 0.11.0-rc.11", - "signature 3.0.0-rc.10", + "signature 3.0.0", ] [[package]] @@ -1416,7 +1416,7 @@ dependencies = [ "rand_core 0.10.1", "serde", "sha2 0.11.0", - "signature 3.0.0-rc.10", + "signature 3.0.0", "subtle", "zeroize", ] @@ -1436,7 +1436,7 @@ dependencies = [ "base16ct", "crypto-bigint", "crypto-common 0.2.1", - "digest 0.11.2", + "digest 0.11.3", "hkdf", "hybrid-array", "once_cell", @@ -1891,9 +1891,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heapless" @@ -1963,7 +1963,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2016,9 +2016,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "ctutils", "subtle", @@ -2224,7 +2224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -2283,7 +2283,7 @@ dependencies = [ "sec1", "sha1 0.11.0", "sha2 0.11.0", - "signature 3.0.0-rc.10", + "signature 3.0.0", "ssh-cipher", "ssh-encoding", "subtle", @@ -2323,16 +2323,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is-macro" version = "0.3.7" @@ -2539,9 +2529,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -2740,7 +2730,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2822,9 +2812,9 @@ dependencies = [ [[package]] name = "module-lattice" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc7c90d33a0dac244570c26461d761ffaeadb3bfc2b17cc625ae2185cafdffae" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" dependencies = [ "ctutils", "hybrid-array", @@ -2862,9 +2852,9 @@ dependencies = [ [[package]] name = "napi" -version = "3.8.6" +version = "3.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e55037284865448ecf329baa86a4d05401f647ebde99f5747b640d32c2c5226" +checksum = "f1d395473824516f38dd1071a1a37bc57daa7be65b293ebba4ead5f7abb017a2" dependencies = [ "bitflags", "ctor", @@ -2878,15 +2868,15 @@ dependencies = [ [[package]] name = "napi-build" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" +checksum = "c9c366d2c8c60b86fa632df75f745509b52f9128f91a6bad4c796e44abb505e1" [[package]] name = "napi-derive" -version = "3.5.5" +version = "3.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ba740fe4c9524d86fd90798fd8ccdb23402b3eef7e7c30897a8a369b529fcf" +checksum = "89b3f766e04667e6da0e181e2da4f85475d5a6513b7cf6a80bea184e224a5b42" dependencies = [ "convert_case", "ctor", @@ -2929,9 +2919,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.31.2" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ "bitflags", "cfg-if", @@ -3319,9 +3309,9 @@ dependencies = [ [[package]] name = "pack1" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e7cd9bd638dc2c831519a0caa1c006cab771a92b1303403a8322773c5b72d6" +checksum = "e3b7bb0ecf2e447b1f20ee94ee79ef6eed1e9d4b3c36ce1903b9dea3bf205523" dependencies = [ "bytemuck", ] @@ -3422,7 +3412,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", "hmac 0.13.0", ] @@ -4189,9 +4179,9 @@ dependencies = [ [[package]] name = "rfc6979" -version = "0.5.0-rc.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a3127ee32baec36af75b4107082d9bd823501ec14a4e016be4b6b37faa74ae" +checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3" dependencies = [ "hmac 0.13.0", "subtle", @@ -4230,12 +4220,12 @@ dependencies = [ "const-oid 0.10.2", "crypto-bigint", "crypto-primes", - "digest 0.11.2", + "digest 0.11.3", "pkcs1", "pkcs8 0.11.0-rc.11", "rand_core 0.10.1", "sha2 0.11.0", - "signature 3.0.0-rc.10", + "signature 3.0.0", "spki 0.8.0-rc.4", "zeroize", ] @@ -4317,9 +4307,9 @@ dependencies = [ [[package]] name = "russh" -version = "0.60.2" +version = "0.60.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e358980fe9b079b99da387117864ee6f0a3fd02f39e5b5fde6af9c2895374" +checksum = "324b92f459d3e42da294e14e8eb150d2215fcfb7c966838bc1127cd68bc05a0d" dependencies = [ "aead 0.6.0-rc.10", "aes 0.8.4", @@ -4386,7 +4376,7 @@ dependencies = [ "sha2 0.10.9", "sha2 0.11.0", "sha3", - "signature 3.0.0-rc.10", + "signature 3.0.0", "spki 0.8.0-rc.4", "ssh-encoding", "subtle", @@ -4399,9 +4389,9 @@ dependencies = [ [[package]] name = "russh-cryptovec" -version = "0.59.0" +version = "0.60.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36140e8a20297bc2e8338807c3d9ca911f7fa49d7539cbcd6d48d3befd70efd8" +checksum = "37cb4d0360bdd8935392a306d8b5edb539cc455b30e8bf13dd213a0cf7879b40" dependencies = [ "log", "nix", @@ -4811,9 +4801,9 @@ dependencies = [ [[package]] name = "serdect" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af4a3e75ebd5599b30d4de5768e00b5095d518a79fefc3ecbaf77e665d1ec06" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" dependencies = [ "base16ct", "serde", @@ -4864,7 +4854,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -4892,7 +4882,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -4901,7 +4891,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", "keccak", ] @@ -4971,11 +4961,11 @@ dependencies = [ [[package]] name = "signature" -version = "3.0.0-rc.10" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1880df446116126965eeec169136b2e0251dba37c6223bcc819569550edea3" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", "rand_core 0.10.1", ] @@ -5018,9 +5008,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -5517,20 +5507,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -5968,9 +5958,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -5981,9 +5971,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.70" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", @@ -5991,9 +5981,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6001,9 +5991,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -6014,9 +6004,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -6070,9 +6060,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", @@ -6312,9 +6302,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" [[package]] name = "wit-bindgen" @@ -6493,9 +6483,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 011070cb..b488bcef 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -147,10 +147,18 @@ criteria = "safe-to-deploy" version = "1.16.3" criteria = "safe-to-deploy" +[[exemptions.aws-lc-rs]] +version = "1.17.0" +criteria = "safe-to-deploy" + [[exemptions.aws-lc-sys]] version = "0.40.0" criteria = "safe-to-deploy" +[[exemptions.aws-lc-sys]] +version = "0.41.0" +criteria = "safe-to-deploy" + [[exemptions.base16ct]] version = "1.0.0" criteria = "safe-to-deploy" @@ -259,6 +267,10 @@ criteria = "safe-to-deploy" version = "1.2.61" criteria = "safe-to-deploy" +[[exemptions.cc]] +version = "1.2.62" +criteria = "safe-to-deploy" + [[exemptions.cfg-if]] version = "1.0.4" criteria = "safe-to-deploy" @@ -459,6 +471,10 @@ criteria = "safe-to-deploy" version = "0.11.1" criteria = "safe-to-deploy" +[[exemptions.ctor]] +version = "1.0.6" +criteria = "safe-to-deploy" + [[exemptions.ctr]] version = "0.9.2" criteria = "safe-to-deploy" @@ -515,6 +531,10 @@ criteria = "safe-to-deploy" version = "0.11.2" criteria = "safe-to-deploy" +[[exemptions.digest]] +version = "0.11.3" +criteria = "safe-to-deploy" + [[exemptions.displaydoc]] version = "0.2.5" criteria = "safe-to-deploy" @@ -739,6 +759,10 @@ criteria = "safe-to-deploy" version = "0.17.0" criteria = "safe-to-deploy" +[[exemptions.hashbrown]] +version = "0.17.1" +criteria = "safe-to-deploy" + [[exemptions.heapless]] version = "0.7.17" criteria = "safe-to-deploy" @@ -795,6 +819,10 @@ criteria = "safe-to-deploy" version = "0.4.11" criteria = "safe-to-deploy" +[[exemptions.hybrid-array]] +version = "0.4.12" +criteria = "safe-to-deploy" + [[exemptions.hyper]] version = "1.9.0" criteria = "safe-to-deploy" @@ -967,6 +995,10 @@ criteria = "safe-to-deploy" version = "0.3.97" criteria = "safe-to-deploy" +[[exemptions.js-sys]] +version = "0.3.98" +criteria = "safe-to-deploy" + [[exemptions.keccak]] version = "0.2.0" criteria = "safe-to-deploy" @@ -1087,18 +1119,34 @@ criteria = "safe-to-deploy" version = "0.2.2" criteria = "safe-to-deploy" +[[exemptions.module-lattice]] +version = "0.2.3" +criteria = "safe-to-deploy" + [[exemptions.napi]] version = "3.8.6" criteria = "safe-to-deploy" +[[exemptions.napi]] +version = "3.9.0" +criteria = "safe-to-deploy" + [[exemptions.napi-build]] version = "2.3.1" criteria = "safe-to-deploy" +[[exemptions.napi-build]] +version = "2.3.2" +criteria = "safe-to-deploy" + [[exemptions.napi-derive]] version = "3.5.5" criteria = "safe-to-deploy" +[[exemptions.napi-derive]] +version = "3.5.6" +criteria = "safe-to-deploy" + [[exemptions.napi-derive-backend]] version = "5.0.4" criteria = "safe-to-deploy" @@ -1115,6 +1163,10 @@ criteria = "safe-to-deploy" version = "0.31.2" criteria = "safe-to-deploy" +[[exemptions.nix]] +version = "0.31.3" +criteria = "safe-to-deploy" + [[exemptions.nohash-hasher]] version = "0.2.0" criteria = "safe-to-deploy" @@ -1255,6 +1307,10 @@ criteria = "safe-to-deploy" version = "1.0.0" criteria = "safe-to-deploy" +[[exemptions.pack1]] +version = "1.1.0" +criteria = "safe-to-deploy" + [[exemptions.page_size]] version = "0.6.0" criteria = "safe-to-run" @@ -1579,6 +1635,10 @@ criteria = "safe-to-deploy" version = "0.5.0-rc.5" criteria = "safe-to-deploy" +[[exemptions.rfc6979]] +version = "0.5.0" +criteria = "safe-to-deploy" + [[exemptions.ring]] version = "0.17.14" criteria = "safe-to-deploy" @@ -1595,10 +1655,18 @@ criteria = "safe-to-deploy" version = "0.60.2" criteria = "safe-to-deploy" +[[exemptions.russh]] +version = "0.60.3" +criteria = "safe-to-deploy" + [[exemptions.russh-cryptovec]] version = "0.59.0" criteria = "safe-to-deploy" +[[exemptions.russh-cryptovec]] +version = "0.60.3" +criteria = "safe-to-deploy" + [[exemptions.russh-util]] version = "0.52.0" criteria = "safe-to-deploy" @@ -1755,6 +1823,10 @@ criteria = "safe-to-deploy" version = "0.4.2" criteria = "safe-to-deploy" +[[exemptions.serdect]] +version = "0.4.3" +criteria = "safe-to-deploy" + [[exemptions.serial_test]] version = "3.4.0" criteria = "safe-to-run" @@ -1815,6 +1887,10 @@ criteria = "safe-to-deploy" version = "3.0.0-rc.10" criteria = "safe-to-deploy" +[[exemptions.signature]] +version = "3.0.0" +criteria = "safe-to-deploy" + [[exemptions.simd-adler32]] version = "0.3.9" criteria = "safe-to-deploy" @@ -1839,6 +1915,10 @@ criteria = "safe-to-deploy" version = "1.0.2" criteria = "safe-to-deploy" +[[exemptions.siphasher]] +version = "1.0.3" +criteria = "safe-to-deploy" + [[exemptions.slab]] version = "0.4.12" criteria = "safe-to-deploy" @@ -2011,6 +2091,10 @@ criteria = "safe-to-deploy" version = "0.6.8" criteria = "safe-to-deploy" +[[exemptions.tower-http]] +version = "0.6.10" +criteria = "safe-to-deploy" + [[exemptions.tower-layer]] version = "0.3.3" criteria = "safe-to-deploy" @@ -2171,22 +2255,42 @@ criteria = "safe-to-deploy" version = "0.2.120" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen]] +version = "0.2.121" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-futures]] version = "0.4.70" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-futures]] +version = "0.4.71" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-macro]] version = "0.2.120" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-macro]] +version = "0.2.121" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-macro-support]] version = "0.2.120" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-macro-support]] +version = "0.2.121" +criteria = "safe-to-deploy" + [[exemptions.wasm-bindgen-shared]] version = "0.2.120" criteria = "safe-to-deploy" +[[exemptions.wasm-bindgen-shared]] +version = "0.2.121" +criteria = "safe-to-deploy" + [[exemptions.wasm-encoder]] version = "0.244.0" criteria = "safe-to-deploy" @@ -2207,6 +2311,10 @@ criteria = "safe-to-deploy" version = "0.3.97" criteria = "safe-to-deploy" +[[exemptions.web-sys]] +version = "0.3.98" +criteria = "safe-to-deploy" + [[exemptions.webpki-root-certs]] version = "1.0.7" criteria = "safe-to-deploy" @@ -2315,6 +2423,10 @@ criteria = "safe-to-deploy" version = "0.52.6" criteria = "safe-to-deploy" +[[exemptions.winnow]] +version = "1.0.3" +criteria = "safe-to-deploy" + [[exemptions.wit-bindgen]] version = "0.51.0" criteria = "safe-to-deploy" @@ -2379,6 +2491,10 @@ criteria = "safe-to-deploy" version = "0.1.7" criteria = "safe-to-deploy" +[[exemptions.zerofrom]] +version = "0.1.8" +criteria = "safe-to-deploy" + [[exemptions.zerofrom-derive]] version = "0.1.7" criteria = "safe-to-deploy" From f9bfc3dfce0b2151de213b80a0f7a3404ad45991 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sun, 17 May 2026 17:30:38 +0000 Subject: [PATCH 3/6] docs: reconcile builtin command count to 156 across all surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README, lib.rs rustdoc, and bashkit-python README claimed 160. The compatibility.md "Quick Status" table totalled 150 with category counts that summed to 110 (broken). implementation-status.md claimed 148+14=162. Authoritative count from `crates/bashkit/src/interpreter/mod.rs`: - 135 names in `register_builtins!` (always-on) - 7 explicit `builtins.insert` calls always-on (source, ., date, hostname, uname, whoami, id) - 14 feature-gated (jq, git, ssh/scp/sftp, python/python3, sqlite/sqlite3, ts/typescript/node/deno/bun) That's 142 + 14 = 156 distinct `Builtin` trait registrations. Interpreter-dispatched keywords (let, declare, command, getopts, ...) are not counted — they're not registered as Builtin trait instances. Compatibility.md's "Quick Status" subcategories are replaced with a two-row always-on / feature-gated split since the previous breakdown didn't reconcile against the actual code. --- README.md | 6 +++--- crates/bashkit-python/README.md | 2 +- crates/bashkit/docs/compatibility.md | 17 +++++------------ crates/bashkit/src/lib.rs | 2 +- specs/implementation-status.md | 2 +- 5 files changed, 11 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index e4d9ab69..8b2bc747 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Homepage: [bashkit.sh](https://bashkit.sh) - **Secure by default** - No process spawning, no filesystem access, no network access unless explicitly enabled. [250+ threats](specs/threat-model.md) analyzed and mitigated - **POSIX compliant** - Substantial IEEE 1003.1-2024 Shell Command Language compliance -- **Sandboxed, in-process execution** - All 160 commands reimplemented in Rust, no `fork`/`exec` +- **Sandboxed, in-process execution** - All 156 commands reimplemented in Rust, no `fork`/`exec` - **Virtual filesystem** - InMemoryFs, OverlayFs, MountableFs with optional RealFs backend (`realfs` feature) - **Resource limits** - Command count, loop iterations, function depth, output size, filesystem size, parser fuel - **Network allowlist** - HTTP access denied by default, per-domain control @@ -113,7 +113,7 @@ assert_eq!(output.result["stdout"], "hello\nworld\n"); -## Built-in Commands (160) +## Built-in Commands (156) | Category | Commands | |----------|----------| @@ -468,7 +468,7 @@ Bashkit is built for running untrusted scripts from AI agents and users. Securit | Layer | Protection | |-------|------------| -| **No process spawning** | All 160 commands are reimplemented in Rust — no `fork`, `exec`, or shell escape | +| **No process spawning** | All 156 commands are reimplemented in Rust — no `fork`, `exec`, or shell escape | | **Virtual filesystem** | Scripts see an in-memory FS by default; no host filesystem access unless explicitly mounted | | **Network allowlist** | HTTP access is denied by default; each domain must be explicitly allowed | | **Resource limits** | Configurable caps on commands (10K), loop iterations (100K), function depth (100), output (10MB), input (10MB) | diff --git a/crates/bashkit-python/README.md b/crates/bashkit-python/README.md index 939459ac..d2843f6e 100644 --- a/crates/bashkit-python/README.md +++ b/crates/bashkit-python/README.md @@ -10,7 +10,7 @@ Homepage: [bashkit.sh](https://bashkit.sh) - Sandboxed execution in-process, without containers or subprocess orchestration - Full bash syntax: variables, pipelines, redirects, loops, functions, and arrays -- 160 built-in commands including `grep`, `sed`, `awk`, `jq`, `curl`, and `find` +- 156 built-in commands including `grep`, `sed`, `awk`, `jq`, `curl`, and `find` - Persistent interpreter state across calls, including variables, cwd, and VFS contents - Direct virtual filesystem APIs, constructor mounts, and live host mounts - Snapshot and restore support on `Bash` and `BashTool` diff --git a/crates/bashkit/docs/compatibility.md b/crates/bashkit/docs/compatibility.md index 8dd251ba..71ce2f81 100644 --- a/crates/bashkit/docs/compatibility.md +++ b/crates/bashkit/docs/compatibility.md @@ -29,18 +29,11 @@ for sandbox security reasons. See the compliance spec for details. ## Quick Status -| Category | Count | -|----------|-------| -| Core & Navigation | 12 | -| Flow Control & Variables | 23 | -| Shell | 7 | -| Text Processing | 20 | -| File Operations & Inspection | 17 | -| Archives & Byte Tools | 6 | -| Utilities & System | 20 | -| Network | 2 | -| Experimental | 3 | -| **Total** | **150** | +| Group | Count | +|-------|-------| +| Always-on builtins | 142 | +| Feature-gated builtins (`jq`, `git`, `ssh`/`scp`/`sftp`, `python`/`python3`, `sqlite`/`sqlite3`, `ts`/`typescript`/`node`/`deno`/`bun`) | 14 | +| **Total** | **156** | --- diff --git a/crates/bashkit/src/lib.rs b/crates/bashkit/src/lib.rs index 67f1f316..3957d89e 100644 --- a/crates/bashkit/src/lib.rs +++ b/crates/bashkit/src/lib.rs @@ -18,7 +18,7 @@ //! - **Experimental: Python** - Embedded Python via [Monty](https://github.com/pydantic/monty) (`python` feature) //! - **Experimental: SQLite** - Embedded SQLite-compatible engine via [Turso](https://github.com/tursodatabase/turso) (`sqlite` feature) //! -//! # Built-in Commands (160) +//! # Built-in Commands (156) //! //! | Category | Commands | //! |----------|----------| diff --git a/specs/implementation-status.md b/specs/implementation-status.md index 54da757f..5a1ba0a5 100644 --- a/specs/implementation-status.md +++ b/specs/implementation-status.md @@ -279,7 +279,7 @@ Features that may be added in the future (not intentionally excluded): ### Implemented -**148 core builtins + 14 feature-gated = 162 total** +**142 core builtins + 14 feature-gated = 156 total** `echo`, `printf`, `cat`, `nl`, `cd`, `pwd`, `true`, `false`, `exit`, `test`, `[`, `export`, `set`, `unset`, `local`, `source`, `.`, `read`, `shift`, `break`, From 04cebadecd1b53b607299958e7781833944ea949 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sun, 17 May 2026 17:40:44 +0000 Subject: [PATCH 4/6] feat(date): add epoch_offset builder for TM-INF-018, mark MITIGATED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec said the mitigation was "Configurable time source (fixed or offset)", but only the fixed variant was implemented. Without an offset mode, callers who need scripts to observe elapsed time at real rate (timeouts, retry loops, anything time-sensitive) had no sandbox-safe option short of fixed-epoch — which breaks ticking-clock expectations. Adds `Bash::builder().epoch_offset(seconds)` which shifts `Utc::now()` by a constant. `fixed_epoch` and `epoch_offset` are mutually exclusive on the builder (last call wins) so callers can swap modes without having to reset state. Wiring: - `Date::with_offset_seconds(i64)` in `builtins/date.rs` - New `epoch_offset: Option` field on `Bash::Builder` - Plumbed through `Interpreter::with_config` and the `date` builtin registration in `interpreter/mod.rs` (priority: fixed > offset > real clock) Tests: - 4 unit tests in `date.rs` for the struct (fixed, offset, zero, priority). - 4 integration tests in `tm_inf_018_date` exercising the full builder→exec path including builder-call ordering. Status update: TM-INF-018 spec entry and rustdoc threat-model doc both flip from NEEDED/OPEN to **MITIGATED** (opt-in). Default behavior is unchanged — embedders opt in for sandboxing. --- crates/bashkit/docs/threat-model.md | 2 +- crates/bashkit/src/builtins/date.rs | 82 +++++++++++++++++++++- crates/bashkit/src/interpreter/mod.rs | 8 ++- crates/bashkit/src/lib.rs | 23 ++++++ crates/bashkit/tests/threat_model_tests.rs | 70 ++++++++++++++++++ specs/threat-model.md | 4 +- 6 files changed, 181 insertions(+), 8 deletions(-) diff --git a/crates/bashkit/docs/threat-model.md b/crates/bashkit/docs/threat-model.md index 63ba886c..5624b52e 100644 --- a/crates/bashkit/docs/threat-model.md +++ b/crates/bashkit/docs/threat-model.md @@ -215,7 +215,7 @@ Scripts may attempt to leak sensitive information. | IP address (TM-INF-007) | `ip addr`, `ifconfig` | Not implemented | MITIGATED | | System info (TM-INF-008) | `uname -a` | Returns configurable virtual values | MITIGATED | | User ID (TM-INF-009) | `id` | Returns hardcoded uid=1000 | MITIGATED | -| Date/time (TM-INF-018) | `date` | Returns real host time (fingerprinting risk) | **OPEN** | +| Date/time (TM-INF-018) | `date` | Returns real host time (fingerprinting risk) | **MITIGATED** (opt-in: `Bash::builder().fixed_epoch` / `.epoch_offset`) | **Network Exfiltration:** diff --git a/crates/bashkit/src/builtins/date.rs b/crates/bashkit/src/builtins/date.rs index d8286698..59e8686a 100644 --- a/crates/bashkit/src/builtins/date.rs +++ b/crates/bashkit/src/builtins/date.rs @@ -46,26 +46,53 @@ use crate::interpreter::ExecResult; /// %n Newline /// %t Tab /// %% Literal % -/// THREAT[TM-INF-018]: Supports a fixed epoch to prevent leaking real host time. +/// THREAT[TM-INF-018]: Supports a fixed epoch OR a constant offset on +/// the real clock so callers can blind absolute wall-clock time without +/// breaking elapsed-time logic. The two modes are mutually exclusive +/// — `fixed_epoch` wins if both are set. pub struct Date { /// Fixed UTC epoch for virtualized time. None = use real system clock. fixed_epoch: Option>, + /// Constant offset applied to `Utc::now()` when `fixed_epoch` is None. + offset_seconds: i64, } impl Date { pub fn new() -> Self { - Self { fixed_epoch: None } + Self { + fixed_epoch: None, + offset_seconds: 0, + } } /// Create a Date builtin with a fixed epoch (for sandboxing). pub fn with_fixed_epoch(epoch: DateTime) -> Self { Self { fixed_epoch: Some(epoch), + offset_seconds: 0, + } + } + + /// Create a Date builtin that offsets the real clock by the given + /// number of seconds. Useful when scripts need a ticking clock but + /// must not observe the host's exact wall-clock time. + pub fn with_offset_seconds(offset: i64) -> Self { + Self { + fixed_epoch: None, + offset_seconds: offset, } } fn now(&self) -> DateTime { - self.fixed_epoch.unwrap_or_else(Utc::now) + if let Some(t) = self.fixed_epoch { + return t; + } + if self.offset_seconds == 0 { + return Utc::now(); + } + Utc::now() + .checked_add_signed(chrono::Duration::seconds(self.offset_seconds)) + .unwrap_or_else(Utc::now) } } @@ -535,6 +562,55 @@ mod tests { assert!(result.stdout.len() > 10); } + /// TM-INF-018: fixed_epoch wins over real clock. + #[test] + fn date_fixed_epoch_returns_fixed_time() { + let fixed = DateTime::::from_timestamp(1_700_000_000, 0).unwrap(); + let d = Date::with_fixed_epoch(fixed); + assert_eq!(d.now(), fixed); + } + + /// TM-INF-018: non-zero offset shifts the real clock without + /// freezing it. Verify the offset is applied within a sub-second + /// tolerance vs `Utc::now() + offset`. + #[test] + fn date_offset_seconds_shifts_real_clock() { + let offset: i64 = 365 * 24 * 60 * 60; // +1 year + let d = Date::with_offset_seconds(offset); + let before = Utc::now(); + let observed = d.now(); + let after = Utc::now(); + let expected_low = before + chrono::Duration::seconds(offset); + let expected_high = after + chrono::Duration::seconds(offset); + assert!( + observed >= expected_low && observed <= expected_high, + "offset clock {observed} not in [{expected_low}, {expected_high}]" + ); + } + + /// TM-INF-018: fixed_epoch takes priority if both modes are set + /// (defensive — the builder enforces exclusivity but the struct + /// fields are pub(crate)-ish and could be combined directly). + #[test] + fn date_fixed_epoch_overrides_offset() { + let fixed = DateTime::::from_timestamp(1_700_000_000, 0).unwrap(); + let d = Date { + fixed_epoch: Some(fixed), + offset_seconds: 99_999, + }; + assert_eq!(d.now(), fixed); + } + + /// TM-INF-018: zero offset = real clock (no allocation overhead path). + #[test] + fn date_zero_offset_uses_real_clock() { + let d = Date::with_offset_seconds(0); + let before = Utc::now(); + let observed = d.now(); + let after = Utc::now(); + assert!(observed >= before && observed <= after); + } + #[tokio::test] async fn test_date_format_year() { let result = run_date(&["+%Y"]).await; diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 923de611..42bf6d34 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -878,7 +878,7 @@ impl Interpreter { /// Create a new interpreter with the given filesystem. pub fn new(fs: Arc) -> Self { - Self::with_config(fs, None, None, None, HashMap::new(), ShellProfile::Full) + Self::with_config(fs, None, None, None, None, HashMap::new(), ShellProfile::Full) } /// Create a new interpreter with custom username, hostname, and builtins. @@ -894,6 +894,7 @@ impl Interpreter { username: Option, hostname: Option, fixed_epoch: Option, + epoch_offset: Option, custom_builtins: HashMap>, shell_profile: ShellProfile, ) -> Self { @@ -1076,7 +1077,8 @@ impl Interpreter { ); builtins.insert(".".to_string(), Box::new(builtins::Source::new(fs.clone()))); - // THREAT[TM-INF-018]: Use fixed epoch if configured, else real clock + // THREAT[TM-INF-018]: Resolve the virtual clock mode for `date`. + // Priority: fixed_epoch > epoch_offset > real clock. builtins.insert( "date".to_string(), Box::new(if let Some(epoch) = fixed_epoch { @@ -1084,6 +1086,8 @@ impl Interpreter { builtins::Date::with_fixed_epoch( DateTime::from_timestamp(epoch, 0).unwrap_or_default(), ) + } else if let Some(offset) = epoch_offset { + builtins::Date::with_offset_seconds(offset) } else { builtins::Date::new() }), diff --git a/crates/bashkit/src/lib.rs b/crates/bashkit/src/lib.rs index 3957d89e..8270b9c5 100644 --- a/crates/bashkit/src/lib.rs +++ b/crates/bashkit/src/lib.rs @@ -1209,6 +1209,8 @@ pub struct BashBuilder { hostname: Option, /// Fixed epoch for virtualizing the `date` builtin (TM-INF-018) fixed_epoch: Option, + /// Constant seconds offset applied to real-clock for `date` (TM-INF-018) + epoch_offset: Option, shell_profile: interpreter::ShellProfile, custom_builtins: HashMap>, /// Files to mount in the virtual filesystem @@ -1397,6 +1399,24 @@ impl BashBuilder { /// When set, `date` returns this fixed time instead of the real clock. pub fn fixed_epoch(mut self, epoch: i64) -> Self { self.fixed_epoch = Some(epoch); + self.epoch_offset = None; + self + } + + /// Apply a constant offset (in seconds) to the real system clock for + /// the `date` builtin. Use this when scripts need time to advance at + /// real-clock rate but you want to obscure the absolute wall-clock + /// time from the sandbox (timing-correlation resistance). + /// + /// THREAT[TM-INF-018]: A non-zero offset prevents `date` from + /// exposing the host's exact wall-clock time while still letting + /// time-sensitive scripts observe elapsed-time deltas. + /// + /// `fixed_epoch` and `epoch_offset` are mutually exclusive — the + /// last builder call wins. + pub fn epoch_offset(mut self, seconds: i64) -> Self { + self.epoch_offset = Some(seconds); + self.fixed_epoch = None; self } @@ -2430,6 +2450,7 @@ impl BashBuilder { self.username, self.hostname, self.fixed_epoch, + self.epoch_offset, self.cwd, self.shell_profile, self.limits, @@ -2681,6 +2702,7 @@ impl BashBuilder { username: Option, hostname: Option, fixed_epoch: Option, + epoch_offset: Option, cwd: Option, shell_profile: interpreter::ShellProfile, limits: ExecutionLimits, @@ -2714,6 +2736,7 @@ impl BashBuilder { username.clone(), hostname, fixed_epoch, + epoch_offset, custom_builtins, shell_profile, ); diff --git a/crates/bashkit/tests/threat_model_tests.rs b/crates/bashkit/tests/threat_model_tests.rs index 80299aec..6368392f 100644 --- a/crates/bashkit/tests/threat_model_tests.rs +++ b/crates/bashkit/tests/threat_model_tests.rs @@ -3772,6 +3772,76 @@ echo "done" } } +// ============================================================================= +// DATE / VIRTUAL CLOCK TESTS (TM-INF-018) +// ============================================================================= + +mod tm_inf_018_date { + use super::*; + + /// TM-INF-018: `Bash::builder().fixed_epoch(N)` causes `date +%s` + /// to return exactly N, regardless of host wall-clock. + #[tokio::test] + async fn fixed_epoch_freezes_date() { + let mut bash = Bash::builder().fixed_epoch(1_700_000_000).build(); + let r = bash.exec("date +%s").await.unwrap(); + assert_eq!(r.exit_code, 0); + assert_eq!(r.stdout.trim(), "1700000000"); + } + + /// TM-INF-018: `Bash::builder().epoch_offset(N)` keeps the clock + /// ticking but shifts its absolute value by N seconds. Verify two + /// consecutive reads differ by less than 1s (ticking) yet sit at + /// least N-1 seconds ahead of host real time (offset applied). + #[tokio::test] + async fn epoch_offset_shifts_real_clock() { + let offset = 365_i64 * 24 * 3600; // +1 year + let host_before = chrono::Utc::now().timestamp(); + let mut bash = Bash::builder().epoch_offset(offset).build(); + let r = bash.exec("date +%s").await.unwrap(); + assert_eq!(r.exit_code, 0); + let observed: i64 = r.stdout.trim().parse().unwrap(); + // observed should be ~ host_before + offset + let delta = observed - (host_before + offset); + assert!( + (-2..=2).contains(&delta), + "offset clock drifted: observed={observed}, expected≈{}, delta={delta}", + host_before + offset + ); + } + + /// TM-INF-018: `fixed_epoch` and `epoch_offset` are mutually + /// exclusive — last builder call wins. fixed_epoch followed by + /// epoch_offset should disable fixed_epoch. + #[tokio::test] + async fn last_builder_call_wins_offset_after_fixed() { + let mut bash = Bash::builder() + .fixed_epoch(0) + .epoch_offset(0) + .build(); + let r = bash.exec("date +%s").await.unwrap(); + let observed: i64 = r.stdout.trim().parse().unwrap(); + // Should be near real-time (within a few seconds), not 0. + let now = chrono::Utc::now().timestamp(); + assert!( + (observed - now).abs() < 60, + "epoch_offset(0) did not override fixed_epoch(0): observed={observed}, real={now}" + ); + } + + /// TM-INF-018: the inverse — epoch_offset then fixed_epoch should + /// disable the offset. + #[tokio::test] + async fn last_builder_call_wins_fixed_after_offset() { + let mut bash = Bash::builder() + .epoch_offset(99_999) + .fixed_epoch(1_700_000_000) + .build(); + let r = bash.exec("date +%s").await.unwrap(); + assert_eq!(r.stdout.trim(), "1700000000"); + } +} + // ============================================================================= // TRACE EVENT TESTS (TM-INF-019) // ============================================================================= diff --git a/specs/threat-model.md b/specs/threat-model.md index 80297362..73c7b3f9 100644 --- a/specs/threat-model.md +++ b/specs/threat-model.md @@ -1330,7 +1330,7 @@ This section maps former vulnerability IDs to the new threat ID scheme and track | ~~TM-INJ-015~~ | ~~`export` bypasses `is_internal_variable()`~~ | ~~Internal prefix injection via export~~ | ~~Add `is_internal_variable()` check~~ (**FIXED**) | | ~~TM-INJ-016~~ | ~~`_ARRAY_READ_` prefix not in `is_internal_variable()`~~ | ~~Arbitrary array creation/overwrite via marker injection~~ | ~~Add `_ARRAY_READ_` prefix to `is_internal_variable()`~~ (**FIXED**) | | TM-INF-017 | `set` and `declare -p` leak internal markers | Internal state disclosure (_NAMEREF_, _READONLY_, _UPPER_, _LOWER_) | Filter `is_internal_variable()` names from output | -| TM-INF-018 | `date` builtin returns real host time | Timezone fingerprinting, timing correlation | Configurable time source (fixed or offset) | +| TM-INF-018 | `date` builtin returns real host time | Timezone fingerprinting, timing correlation | `Bash::builder().fixed_epoch(N)` freezes the clock; `Bash::builder().epoch_offset(N)` shifts real-clock by N seconds (mutually exclusive, last call wins). Both implemented via `Date::with_fixed_epoch` / `Date::with_offset_seconds` in `builtins/date.rs`. Default behavior is real clock — embed callers opt in for sandboxing. Regression tests: `tm_inf_018_date::*` in `tests/threat_model_tests.rs`. | **MITIGATED** (opt-in) | | ~~TM-DOS-041~~ | ~~Brace expansion `{N..M}` unbounded range~~ | ~~OOM via `{1..999999999}` allocating billions of strings~~ | Static parser-time check (`MAX_STATIC_BRACE_RANGE = 100_000` in `parser/budget.rs`) rejects oversized literal ranges with `BraceRangeTooLarge`; runtime fallback in `try_expand_range` (`MAX_BRACE_RANGE = 10_000`) treats remaining oversized ranges as literals (**FIXED**) | | ~~TM-DOS-042~~ | ~~Brace expansion combinatorial explosion~~ | ~~OOM via `{1..100}{1..100}{1..100}` = 1M strings~~ | `expand_braces` caps total emitted strings at `MAX_BRACE_EXPANSION_TOTAL = 100_000` and bails out of the recursion when the budget is hit (**FIXED**) | | ~~TM-DOS-043~~ | ~~Arithmetic overflow in `execute_arithmetic_with_side_effects`~~ | ~~Panic (DoS) in debug mode via `((x+=1))` with x=i64::MAX~~ | ~~Use `wrapping_add/sub/mul`~~ (**FIXED**) | @@ -1464,7 +1464,7 @@ This section maps former vulnerability IDs to the new threat ID scheme and track | OverlayFs symlink limit bypass | TM-DOS-045 | `check_write_limits()` + `validate_path()` in `symlink()` | **MITIGATED** | | MountableFs path validation gap | TM-DOS-046 | `validate_path()` in all MountableFs methods | **MITIGATED** | | VFS copy/rename semantic bugs | TM-DOS-047, TM-DOS-048 | Fix limit check in copy(), type check in rename() | **MITIGATED** | -| Date time info leak | TM-INF-018 | Configurable time source | **NEEDED** | +| Date time info leak | TM-INF-018 | `fixed_epoch` + `epoch_offset` builder methods (opt-in) | **MITIGATED** | | Python BashTool.reset() drops limits | TM-PY-028 | `BashTool::reset` rebuilds via `replace_live_bash_with_builder` matching `PyBash::reset` | **MITIGATED** | | YAML parser depth limit | TM-DOS-051 | `depth` parameter on `parse_yaml_block`/`map`/`list` with `MAX_YAML_DEPTH = 100` | **MITIGATED** | | Template engine depth limit | TM-DOS-052 | `depth` parameter on `render_template_inner` with `MAX_TEMPLATE_DEPTH = 100` | **MITIGATED** | From fc367220296e9c3a3ce05beecf48d748e8daa6ad Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sun, 17 May 2026 17:42:57 +0000 Subject: [PATCH 5/6] chore(specs): track crypto stack split (#1634) in deferred items The 2026-05-17 deep-maintenance pass surfaced a persistent RustCrypto 0.10/0.11 split (crypto-common, digest, sha1, sha2, hmac, aes, cipher, ctr, cpufeatures) pulled by turso_core 0.5 / aes-gcm 0.10 vs bashkit's direct 0.11 cohort. Tracked as #1634 with watch conditions on upstreams. --- specs/maintenance.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/specs/maintenance.md b/specs/maintenance.md index 648aee41..1b1f1366 100644 --- a/specs/maintenance.md +++ b/specs/maintenance.md @@ -187,9 +187,14 @@ multi-file refactors, cross-cutting changes), the pass must: Deferred items are **not** failures — they are expected for large-scope improvements. The requirement is that they are **tracked**, not silently skipped. -_No deferred items currently outstanding. Previously tracked items -(#880 ArgParser migration, #881 errexit propagation helper) have been -resolved._ +### Deferred from 2026-05-17 run + +| Issue | Section | Description | +|-------|---------|-------------| +| #1634 | Dependencies | RustCrypto stack split between 0.10 and 0.11 lines, blocked on `turso_core` / `aes-gcm` upstreams | + +Previously tracked items (#880 ArgParser migration, #881 errexit +propagation helper) have been resolved. ## Automation From f9a2e54757517c35332edc697aa59c9be18c4356 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sun, 17 May 2026 17:44:40 +0000 Subject: [PATCH 6/6] style: cargo fmt --- crates/bashkit/src/interpreter/mod.rs | 10 +++++++++- crates/bashkit/tests/threat_model_tests.rs | 5 +---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 42bf6d34..3d908a3f 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -878,7 +878,15 @@ impl Interpreter { /// Create a new interpreter with the given filesystem. pub fn new(fs: Arc) -> Self { - Self::with_config(fs, None, None, None, None, HashMap::new(), ShellProfile::Full) + Self::with_config( + fs, + None, + None, + None, + None, + HashMap::new(), + ShellProfile::Full, + ) } /// Create a new interpreter with custom username, hostname, and builtins. diff --git a/crates/bashkit/tests/threat_model_tests.rs b/crates/bashkit/tests/threat_model_tests.rs index 6368392f..36164d9c 100644 --- a/crates/bashkit/tests/threat_model_tests.rs +++ b/crates/bashkit/tests/threat_model_tests.rs @@ -3815,10 +3815,7 @@ mod tm_inf_018_date { /// epoch_offset should disable fixed_epoch. #[tokio::test] async fn last_builder_call_wins_offset_after_fixed() { - let mut bash = Bash::builder() - .fixed_epoch(0) - .epoch_offset(0) - .build(); + let mut bash = Bash::builder().fixed_epoch(0).epoch_offset(0).build(); let r = bash.exec("date +%s").await.unwrap(); let observed: i64 = r.stdout.trim().parse().unwrap(); // Should be near real-time (within a few seconds), not 0.