From da55095c9740599721590ec2227878a576fae25a Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sat, 24 Oct 2026 17:29:23 -0500 Subject: [PATCH] fix(security): complete standalone runtime audits --- Cargo.lock | 4 ++-- crates/bashkit-js/README.md | 6 +++++ examples/hyperlight/Cargo.lock | 4 ++-- examples/hyperlight/build-and-run.sh | 4 ++-- knowledge/log.md | 6 +++++ knowledge/runtimes/node-package.md | 9 ++++++++ knowledge/runtimes/non-js-wasm.md | 2 ++ knowledge/security/threat-model.md | 15 ++++++++++--- scripts/audit-lockfiles.sh | 32 +++++---------------------- scripts/tests/test_audit_lockfiles.py | 23 +++++++++++++++++-- supply-chain/audits.toml | 6 +++++ 11 files changed, 74 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56a1ae9ff..3d8f5ad5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -986,9 +986,9 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clipboard-win" -version = "5.4.1" +version = "5.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a" dependencies = [ "error-code", ] diff --git a/crates/bashkit-js/README.md b/crates/bashkit-js/README.md index ae6048708..9bf017939 100644 --- a/crates/bashkit-js/README.md +++ b/crates/bashkit-js/README.md @@ -25,6 +25,12 @@ bun add @everruns/bashkit # Bun deno add npm:@everruns/bashkit # Deno ``` +Use a patched, supported Node.js release. The October 2026 security minimums +are **22.23.4**, **24.22.1**, and **26.12.1** in their respective release lines; +keep taking subsequent upstream security updates. The package's `engines >=18` +is an ABI compatibility floor, not security support for EOL Node 18/20. The +Node 20 CI lane checks legacy compatibility only. Bashkit does not bundle Node. + ## Quick Start ### Sync Execution diff --git a/examples/hyperlight/Cargo.lock b/examples/hyperlight/Cargo.lock index e4e41f948..77ab496a0 100644 --- a/examples/hyperlight/Cargo.lock +++ b/examples/hyperlight/Cargo.lock @@ -1175,9 +1175,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.53.1" +version = "1.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" dependencies = [ "bytes", "pin-project-lite", diff --git a/examples/hyperlight/build-and-run.sh b/examples/hyperlight/build-and-run.sh index 0469b789f..825592c33 100755 --- a/examples/hyperlight/build-and-run.sh +++ b/examples/hyperlight/build-and-run.sh @@ -12,7 +12,7 @@ component="target/bashkit-sandbox.component.wasm" echo "==> building guest (wasm32-unknown-unknown, no JS, no WASI)" RUSTFLAGS='--cfg getrandom_backend="custom"' \ - cargo build --release --target wasm32-unknown-unknown + cargo build --locked --release --target wasm32-unknown-unknown echo "==> wrapping core module as a component" wasm-tools component new "$core" -o "$component" @@ -26,7 +26,7 @@ if grep -qv '"bashkit:sandbox/host"' <<<"$imports"; then fi echo "==> running under wasmtime" -actual=$(cd host && cargo run --release --quiet -- "../$component" "$(cat "$here/smoke.sh")") +actual=$(cd host && cargo run --locked --release --quiet -- "../$component" "$(cat "$here/smoke.sh")") expected=$'beta gamma alpha \n6\nslept 1s' echo "$actual" diff --git a/knowledge/log.md b/knowledge/log.md index 76f007c2d..71aa5a707 100644 --- a/knowledge/log.md +++ b/knowledge/log.md @@ -1,5 +1,11 @@ # Bashkit Knowledge Update Log +## 2026-10-24 + +* **Runtime security completion**: Pin CI/release Node lanes to patched 22.23.4/24.22.1; document legacy Node20 as compatibility-only. Align standalone Hyperlight Tokio with patched root/fuzz 1.53.3 for RUSTSEC-2026-0386. Cargo lockfile audits now fail on unsound warnings, with a regression preserving coverage of other ecosystems. +* **Windows dependency**: Replace yanked clipboard-win5.4.1 with 5.4.3 via Rustyline; reviewed the HTML range/copy safety delta and recorded a precise cargo-vet delta audit without new exemptions or publisher trust. Rustyline calls Unicode reads, so exploitation through the changed HTML API is not established. +* **Advisory retirement**: Remove the npm braces suppression after GitHub withdrew GHSA-vfj7-8cjw-p6xm on 2026-10-23; braces3.0.3 remains in AVA's dev-only graph, not patched code. RSA Marvin remains accepted only for opt-in SSH: the latest published RSA prerelease still warns of exposure, and russh pins rc.18. See [Threat Model](security/threat-model.md). + ## 2026-10-16 * **Browser live runtime callbacks**: Integrated the runtime dispatcher with live byte sinks, bounded capture and command pipelines after the streaming API landed. Both execution paths retain request-local leases; tests cover async sink completion/deadlines, abort/reuse, reset and caller context. See [Browser Package](runtimes/browser-package.md). diff --git a/knowledge/runtimes/node-package.md b/knowledge/runtimes/node-package.md index 04b07b82b..84bdbd9f8 100644 --- a/knowledge/runtimes/node-package.md +++ b/knowledge/runtimes/node-package.md @@ -56,6 +56,15 @@ queued calls, worker threads, adapters, reentry, bad values and snapshot/reset. `runtime-compat/runtime-functions.test.mjs` runs in Node, Bun and Deno. Dedicated macOS and Windows CI runs the callback suite as well as Linux's runtime matrix. +## Host runtime security + +Build/release jobs use Node 24.22.1 and general CI uses 22.23.4, the patched +October 2026 releases. The runtime matrix explicitly tests both versions. +Node 20 remains an EOL compatibility-only lane; its green result does not imply +security support. The npm `engines >=18` field is the native ABI compatibility +floor, not a security-supported runtime list. Consumers supply Node themselves +and must use a currently patched supported runtime. No Node executable is bundled. + ## See also - [Python Package](python-package.md) diff --git a/knowledge/runtimes/non-js-wasm.md b/knowledge/runtimes/non-js-wasm.md index 35bcf42dc..166381922 100644 --- a/knowledge/runtimes/non-js-wasm.md +++ b/knowledge/runtimes/non-js-wasm.md @@ -15,6 +15,8 @@ tags: Experiment, proven end-to-end under wasmtime. See `examples/hyperlight/`. Not published as a package; no micro-VM boot test yet (needs KVM/WHP hardware). +The end-to-end smoke builds both standalone guest and host with `--locked`, so +their audited dependency graphs cannot silently change during verification. ## Abstract diff --git a/knowledge/security/threat-model.md b/knowledge/security/threat-model.md index 557d570bb..c90ef357e 100644 --- a/knowledge/security/threat-model.md +++ b/knowledge/security/threat-model.md @@ -1853,7 +1853,6 @@ checks. |----------|-------|----------------|-------------| | RUSTSEC-2023-0071 | `rsa` | Marvin timing sidechannel (TM-CRY-002). No patched version exists; reachable only via the opt-in `ssh` feature | `rsa` ships a constant-time release | | RUSTSEC-2023-0089 | `atomic-polyfill` | Unmaintained, no known vulnerability; transitive via `monty` → `postcard` → `heapless` (pinned at 0.7, which still requires it) | Upstream drops the dependency | -| GHSA-vfj7-8cjw-p6xm | `braces` (npm) | Stack-exhaustion DoS through deeply nested patterns. No patched version exists — 3.0.3 is both the latest release and the last affected one. Reached only as `ava` → `globby` → `fast-glob` → `micromatch` → `braces`, a devDependency of `crates/bashkit-js`, globbing test-file patterns committed to this repo; it is not in the published package's `files` list, so no consumer installs it | `braces` ships a release above 3.0.3 | A suppression that stops matching is not evidence that it can be dropped. `cargo deny check advisories` currently warns `advisory-not-detected` for @@ -1861,8 +1860,18 @@ RUSTSEC-2023-0071, because `ssh-key` resolves `rsa` to a pre-release (0.10.0-rc.18) and RustSec version ranges do not match pre-release versions. The advisory still carries `patched = []`, so the exposure is unchanged and matching resumes as soon as `rsa` ships a stable release. Before removing any -entry above, check the advisory's own `patched` list rather than the scanner's -silence. +entry above, verify the advisory's own patched release, dependency removal, or +explicit upstream withdrawal rather than the scanner's silence. + +Retired: [GHSA-vfj7-8cjw-p6xm](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm) +(`braces`, npm), withdrawn by GitHub on 2026-10-23 at 12:53:02 UTC because it +was not a practically exploitable vulnerability. The AVA dev-only chain still +resolves braces 3.0.3; withdrawal is not evidence of patched parser code. The +npm suppression list is now empty, so future findings fail without that exception. + +Cargo scans use `--deny unsound` across every discovered lockfile. Informational +soundness findings such as RUSTSEC-2026-0386 must fail the required audit gate, +not merely print a warning. Other documented risk decisions remain unchanged. Retired: RUSTSEC-2026-0173 (`proc-macro-error2`, unmaintained). Its recorded removal condition — "`tabled` releases a version without it" — was met by diff --git a/scripts/audit-lockfiles.sh b/scripts/audit-lockfiles.sh index 57c1239a8..d36e56dbd 100755 --- a/scripts/audit-lockfiles.sh +++ b/scripts/audit-lockfiles.sh @@ -53,31 +53,11 @@ IGNORED_ADVISORIES=( RUSTSEC-2023-0071 ) -# npm advisories, by GHSA (or CVE) id. A patched release is almost always -# reachable by a bump, because the npm dependencies are all dev- or -# example-only; the entry below is the exception the mechanism exists for. It -# landed on 2026-10-02 against the newest published version of the package, -# with no fixed release to move to, so the choice is an explicit, documented -# acceptance or a red scan that stays red — not a pinned floor nobody records. -# -# It is a `last_affected` advisory in OSV, not a `fixed` one: there is no -# patched version, so re-check the advisory's own fixed-range before dropping -# the entry. A sibling entry for GHSA-ch52-4w7c-c8xp -# (`http-cache-semantics`) was dropped this way once 4.3.0 published above its -# `last_affected` 4.2.0; `site/package.json` now pins that floor as an -# override. The package below reaches no consumer of a published artifact. -# -# GHSA-vfj7-8cjw-p6xm — stack-exhaustion DoS in `braces` from deeply nested -# patterns. Reached only as ava → globby → fast-glob → micromatch → braces, a -# devDependency of crates/bashkit-js: the test runner globbing test-file -# patterns that live in this repo. `braces` 3.0.3 is both the latest release -# and the last affected one. The published package ships the files listed in -# its package.json `files` array, so no consumer installs this chain. Drop -# once `braces` ships a release above 3.0.3. -# -IGNORED_NPM_ADVISORIES=( - GHSA-vfj7-8cjw-p6xm -) +# npm advisories, by GHSA/CVE id. No active exceptions. The previous +# GHSA-vfj7-8cjw-p6xm (braces) exception was retired after GitHub withdrew +# the advisory on 2026-10-23 as not practically exploitable. This is not a +# patched-code claim; a newly published finding must fail normally. +IGNORED_NPM_ADVISORIES=() # PyPI advisories, by OSV (or GHSA) id. Empty: nothing has yet needed # suppressing here. Same bar as the npm list above — an entry needs a rationale @@ -125,7 +105,7 @@ found=0 while IFS= read -r lock; do found=$((found + 1)) echo "::group::cargo audit $lock" - cargo audit ${cargo_ignore_flags[@]+"${cargo_ignore_flags[@]}"} -f "$lock" || status=1 + cargo audit --deny unsound ${cargo_ignore_flags[@]+"${cargo_ignore_flags[@]}"} -f "$lock" || status=1 echo "::endgroup::" done < <(discover Cargo.lock) require_found "$found" Cargo.lock diff --git a/scripts/tests/test_audit_lockfiles.py b/scripts/tests/test_audit_lockfiles.py index 1f70a3af6..62d61d6e1 100644 --- a/scripts/tests/test_audit_lockfiles.py +++ b/scripts/tests/test_audit_lockfiles.py @@ -55,7 +55,7 @@ def suppressions(name): class AuditLockfilesTests(unittest.TestCase): - def run_script(self, locks): + def run_script(self, locks, cargo_stub=None): """Run the script against a temp tree containing `locks`, return (result, calls).""" with tempfile.TemporaryDirectory() as directory: root = Path(directory) @@ -77,7 +77,7 @@ def run_script(self, locks): (root / 'scripts/lib/npm_audit.py', 'npm'), (root / 'scripts/lib/pypi_audit.py', 'pypi'), ): - target.write_text(STUB.format(label=label)) + target.write_text(cargo_stub if label == "cargo" and cargo_stub else STUB.format(label=label)) target.chmod(target.stat().st_mode | stat.S_IEXEC) calls_file = root / 'calls.txt' @@ -157,6 +157,25 @@ def test_one_failing_lockfile_fails_the_scan(self): self.assertEqual(result.returncode, 1) self.assertEqual(len(self.by_label(calls, 'cargo')), 3, calls) + def test_unsound_warning_fails_scan_without_hiding_other_ecosystems(self): + # cargo-audit normally returns success for informational unsoundness. + # Simulate a finding in a standalone workspace, honoring its deny flag. + cargo_stub = """#!/bin/sh +echo "cargo $@" >> "$AUDIT_CALLS" +case "$*" in + *hyperlight*) + case "$*" in *"--deny unsound"*) exit 1 ;; esac ;; +esac +exit 0 +""" + result, calls = self.run_script( + ALL_LOCKS + ['examples/hyperlight/Cargo.lock'], cargo_stub=cargo_stub + ) + self.assertEqual(result.returncode, 1) + self.assertEqual(len(self.by_label(calls, 'cargo')), 3, calls) + self.assertEqual(len(self.by_label(calls, 'npm')), 2, calls) + self.assertEqual(len(self.by_label(calls, 'pypi')), 1, calls) + def test_a_failure_in_one_ecosystem_still_audits_the_others(self): # An npm advisory must not stop the cargo or PyPI scan: each ecosystem # is reported separately so one never masks another. diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index a02b8c1af..65712ce43 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -96,6 +96,12 @@ criteria = "safe-to-deploy" delta = "4.6.4 -> 4.6.7" notes = "Reviewed defer attribute codegen (Defer magic attr, bool-lit helper, initial/final method ordering); proc-macro output only, no new unsafe or ambient access." +[[audits.clipboard-win]] +who = "Mykhailo Chalyi " +criteria = "safe-to-deploy" +delta = "5.4.1 -> 5.4.3" +notes = "Reviewed the complete source delta against the existing exempted baseline. HTML fragment offsets use checked subtraction followed by str::get and Vec::extend_from_slice, replacing unchecked pointer copying/set_len; malformed ranges or UTF-8 boundaries return invalid_data. The sum equals the parsed end offset after checked subtraction. Clipboard-open retries now sleep 5ms per bounded attempt. No dependency, feature, license, build-script or external-capability changes. Existing Windows FFI baseline was not re-audited. Rustyline uses Unicode clipboard reads, not the changed HTML API." + [[audits.combine]] who = "Mykhailo Chalyi " criteria = "safe-to-deploy"