Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ updates:
fuzz-dependencies:
patterns:
- "*"
ignore:
# The fuzz lockfile resolves the root workspace, so a bump here rewrites
# the root `wasmtime = "=48.0.5"` pin — and `ignore` is per-entry, so the
# "/" entry's wasmtime block above does not cover this one. That gap
# opened #2708, where every rust job failed at resolution with
# "wasmtime@49.0.2 requires rustc 1.96.0" against the pinned 1.95.0.
# Keep in step with the "/" entry; see the rationale there.
- dependency-name: "wasmtime"

# The remaining cargo workspaces. Each has its own lockfile, so neither the
# "/" entry nor the fuzz entry above reaches them.
Expand Down Expand Up @@ -158,6 +166,12 @@ updates:
example-cargo:
patterns:
- "*"
ignore:
# `examples/hyperlight/host` pins wasmtime "48" and is built by required
# CI (the "WASM component (no JS, no WASI)" job) on the same pinned
# 1.95.0 toolchain, so a 49.x bump here fails resolution exactly as it
# did in #2708. Keep in step with the "/" entry; see the rationale there.
- dependency-name: "wasmtime"

# npm / pnpm.
#
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,16 @@ jobs:

- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2

# Built from source against the pinned toolchain, so the tool version is
# pinned too: an unpinned `tool:` resolves to the newest release, and
# cargo-geiger 0.14.0 raised its MSRV to 1.97 on the day it shipped,
# which broke this job on a tree that had not changed. Bump deliberately
# alongside rust-toolchain.toml; 0.13.0 builds on 1.85+.
- name: Install cargo-geiger
uses: taiki-e/cache-cargo-install-action@9ee83daaa7b96a6fab930949ecf1122bba04a389 # v3
with:
tool: cargo-geiger
version: 0.13.0
locked: true

- name: Run cargo-geiger (unsafe code audit)
Expand Down
16 changes: 11 additions & 5 deletions crates/bashkit/tests/integration/dd_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,17 @@ async fn dd_normal_copies_match_gnu() {
.stderr(Stdio::piped())
.spawn()
.unwrap();
host.stdin
.take()
.unwrap()
.write_all(input.as_bytes())
.unwrap();
// GNU dd with count=0 exits without reading a byte, so this write
// races the child's exit and fails with EPIPE whenever the child
// wins. That is the case being exercised, not an error: the
// assertions below still compare stdout, stderr and exit code.
let mut stdin = host.stdin.take().unwrap();
match stdin.write_all(input.as_bytes()) {
Ok(()) => {}
Err(error) if error.kind() == std::io::ErrorKind::BrokenPipe => {}
Err(error) => panic!("writing dd stdin for {args}: {error}"),
}
drop(stdin); // close before wait_with_output, as the temporary did
let expected = host.wait_with_output().unwrap();
let actual = Bash::new()
.exec_with_options(&format!("dd {args}"), ExecOptions::new().stdin(input))
Expand Down
4 changes: 4 additions & 0 deletions knowledge/log.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Bashkit Knowledge Update Log

## 2026-10-13

* **Operations**: Two CI holes with one cause — something was free to move to a version the pinned rustc cannot build, so an upstream release broke a tree nobody had touched. Nightly's `cargo-geiger` was installed with no version and built from source against the pinned toolchain; 0.14.0 shipped with its MSRV raised to 1.97 and run 259 went red the next morning, skipping the unsafe-code audit entirely. It is now pinned to 0.13.0 (MSRV 1.85), bumped deliberately with the toolchain, like `cargo-audit` before it. Separately, `wasmtime` is ignored in the `/` dependabot entry because the pin is exactly `=48.0.5`, but `ignore` is per-entry: the fuzz entry had none, its lockfile resolves the root workspace, and dependabot rewrote the root pin to 49.x (MSRV 1.96) in #2708, failing every rust job at resolution. `examples/hyperlight/host` had the same exposure from the example-workspaces entry. Both now carry the ignore, and `scripts/tests/test_dependabot_coverage.py` fails if any cargo entry can propose a wasmtime bump, since which entry reaches which manifest is not worth re-deriving each time one is added. The general rule: an unpinned tool against a pinned compiler defeats the point of pinning the compiler. Also corrected the [threat model](security/threat-model.md) tooling table, which listed `cargo-geiger` as running on every PR; it has only ever run nightly.

## 2026-10-12

* **Test**: GPT-6.1 Sol (76/88, generate 14/15) and GPT-6 Luna (71/88, 14/15) added to the 2026-10-09 tagged `bashkit_bash` and `bashkit_generate` runs via `mira run --resume`; all seven models now on the homepage table. A resume writes `report.json` for the resumed targets only, so re-render with `mira report <run_id>` before publishing.
Expand Down
2 changes: 1 addition & 1 deletion knowledge/security/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -1645,7 +1645,7 @@ This section documents the security tools used to detect and prevent vulnerabili
| **OSV** | CVE scanning for PyPI dependencies | ✅ Required | Every PR + nightly |
| **cargo-deny** | License + advisory checks | ✅ Required | Every PR |
| **cargo-clippy** | Lint with security-focused warnings | ✅ Required | Every PR |
| **cargo-geiger** | Count unsafe code blocks | ✅ Informational | Every PR |
| **cargo-geiger** | Count unsafe code blocks | ✅ Informational | Nightly |

**cargo-audit** scans every `Cargo.lock` in the tree against the RustSec Advisory
Database, **pnpm audit** every `pnpm-lock.yaml` against the GitHub Advisory
Expand Down
41 changes: 41 additions & 0 deletions scripts/tests/test_dependabot_coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,47 @@ def test_the_two_random_fs_fixtures_are_both_covered(self):
with self.subTest(fixture=fixture):
self.assertIn(fixture, covered)

def test_no_cargo_entry_can_propose_a_wasmtime_bump(self):
# wasmtime is held at =48.0.5 for two independent reasons: the
# `cpython` feature loads a module precompiled by build.rs, which only
# loads into the identical wasmtime version, and 48.x is the newest
# line whose MSRV the pinned rustc satisfies. 49.x needs 1.96.
#
# The "/" entry carried that ignore; the fuzz and example entries did
# not, and the ignore is per-entry. The fuzz lockfile resolves the root
# workspace, so dependabot edited the root `=48.0.5` pin straight
# through the gap and opened #2708, where every rust job died at
# resolution with "wasmtime@49.0.2 requires rustc 1.96.0". The
# hyperlight host pins its own wasmtime "48" and is built by required
# CI on the same pinned toolchain, so it had the same exposure.
#
# Which entry happens to reach which manifest is not a useful thing to
# re-derive each time one is added, so the invariant is the blunt one:
# no cargo entry proposes a wasmtime bump. Lift it everywhere at once
# when the toolchain moves.
config = yaml.safe_load(CONFIG.read_text())
cargo_entries = [
u for u in config['updates'] if u['package-ecosystem'] == 'cargo'
]
self.assertGreaterEqual(len(cargo_entries), 3, cargo_entries)
for entry in cargo_entries:
scope = entry.get('directory') or entry.get('directories')
with self.subTest(scope=scope):
ignored = {
i.get('dependency-name')
for i in entry.get('ignore') or []
# An ignore narrowed to update-types still lets other
# bump kinds through, so it does not count here.
if not i.get('update-types')
}
self.assertIn(
'wasmtime',
ignored,
f'cargo entry {scope} can propose a wasmtime bump; '
'it is pinned to =48.0.5 by MSRV and by precompiled-module '
'compatibility, so every cargo entry must ignore it',
)

def test_config_is_a_well_formed_dependabot_v2_file(self):
config = yaml.safe_load(CONFIG.read_text())
self.assertEqual(config['version'], 2)
Expand Down
Loading