fix(deps)!: bump wasmtime off the unpatched 45.x line (RUSTSEC-2026-0222) - #35
Merged
Conversation
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
A module's wasm start function runs inside Linker::instantiate. With fuel consumption enabled engine-wide the store holds ZERO fuel until set_fuel, so under wasmtime 47 every module with a start function trapped with 'all fuel consumed' at instantiation time; under 45 the same code left start functions outside the sandbox budget entirely. Arm fuel + the epoch deadline immediately before instantiate so instantiation is bounded like any export call (SPEC 18.2). Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
MichaelTaylor3d
marked this pull request as ready for review
August 1, 2026 10:29
MichaelTaylor3d
commented
Aug 1, 2026
MichaelTaylor3d
left a comment
Contributor
Author
There was a problem hiding this comment.
Correctness gate: PASS (head 0b120050b2d0cc8c0327d2489493e627e6f6f334)
Independently verified in a scratch worktree (C:/tmp/worktrees/digs-rev35), not from the PR description. No shared checkout was mutated.
Test-vacuity probe (the load-bearing question) — PASSES, two-sided
I committed nothing and reverted by file-copy, then restored (git status --porcelain empty afterwards).
- Mutation A — move the arming back after
instantiate(the exact fix, reverted):
benign_start_function_instantiates_and_runsgoes RED for the right reason:
Wasmtime("wasm trap: all fuel consumed by WebAssembly")atinstantiation_bounds.rs:40. - Mutation B — arm a token budget (
set_fuel(64)before instantiate, the nearest wrong fix):
same test goes RED:error while executing at wasm backtrace: 0: 0x7c - <unknown>!start.
So start_benign.wat is genuinely two-sided: it fails under zero fuel AND under a token budget, and the assert_eq!(get_store_id(), vec![0xAB; 32]) names the property (the start function ran to completion inside a real budget), not merely an outcome. This is the correct shape.
At head, cargo test -p digstore-host --test instantiation_bounds = 2 passed.
Other checks
- §5.1 back-compat: no emit-path source changed;
Cargo.lockdiff touches onlywasmtime-internal-winch+winch-codegen(removed) —wasm-encoder/wasmparserunmoved.data_section_golden3/3 (incl. the older-golden read),digstore-compiler+digstore-host+digstore-remotesuites fully green locally. No section id, field meaning, or guest export signature changed. - Advisory really gone, not silenced:
deny.tomlis byte-unchanged in this PR; noRUSTSEC-2026-0222string anywhere in the tree;cargo deny check advisories→advisories oklocally, and CIsupply-chain auditis green. - SemVer:
digstore-hostpublicly exposes wasmtime types (RuntimeState { pub limits: StoreLimits }atruntime.rs:74-77,imports::register(&mut Linker<RuntimeState>)), so the wasmtime major bump is a public-API break →0.1.0 → 0.2.0is the right 0.x breaking position. Workspace0.19.3 → 0.20.0likewise. Nopackage.jsonexists in this repo, so no two-file disagreement is possible; theCheck version incrementgate is green. - Blast radius (gitnexus disabled per §2.0 — done by
cargo tree/grep): wasmtime is declared only bydigstore-host(real dep) anddigstore-compiler(dev-dep).HostRuntime::newhas exactly two production callers —crates/digstore-cli/src/ops/serve.rs:133andcrates/digstore-host/src/serve_blind.rs:229— and its signature is unchanged in the diff. Claim confirmed. - SPEC §7.1 is normative and factually matches the code: 5 s epoch budget, 384 MiB /
MAX_MEMORY_BYTES = 6144 * 64 KiB, fuel5_000_000_000(crates/digstore-host/src/config.rs:22-28), per-export-call arming (arm_bounds,runtime.rs:208-214), and the instantiation-is-guest-execution rule. - §2.5: the new comment block states WHY (start runs inside
instantiate; engine-wide fuel means an un-armed store is at zero), names are intent-revealing,let _ = set_fuel(..)matches the existingarm_boundsidiom. - All 10 required checks green; zero unresolved review threads.
Two non-gating notes posted inline and resolved by me so they do not block merge.
Co-Authored-By: Claude <noreply@anthropic.com>
…orrect execution bounds Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the digstore (release-first) half of DIG-Network/dig_ecosystem#1898.
Why
cargo deny check advisoriesfails onmainwith RUSTSEC-2026-0222 ("Stores can mix up type indices between engines", GHSA-hgjw-h833-99q9) against wasmtime 45.0.3. The patched ranges are>=24.0.12<25,>=36.0.13<37,>=46.0.2<47,>=47.0.3— there is no patch in the 45.x line, socargo update -p wasmtimeis a no-op and this needs a real major move. The CVE is currently blocking every open PR in hub.dig.net, which linksdigstore-host.What changed
1. wasmtime 45 → 47.0.3 in
crates/digstore-hostandcrates/digstore-compiler(dev-dep).Chose 47.0.3 over 46.0.2: both are patched, but 47 is the current maintained line (46 is one major from EOL, so adopting it would buy another forced bump shortly), and 47.0.3 resolved with zero source churn to the embedding API — so it is simultaneously the smaller diff and the longer-lived choice.
2. A real regression the bump exposed —
HostRuntime::newarmed its sandbox budget in the wrong place.crates/digstore-host/src/runtime.rscalledstore.set_fuel(...)/store.set_epoch_deadline(...)afterLinker::instantiate. But a module's wasmstartfunction runs insideinstantiate, so instantiation is guest execution. Fuel consumption is enabled engine-wide (wcfg.consume_fuel(true)), which means an un-armed store holds zero fuel:startfunction died at instantiation withwasm trap: all fuel consumed by WebAssembly. Twodigstore-compilerauth_policytests went red on the bare dep bump.startfunctions outside the sandbox budget entirely — a latent §18.2 gap, not merely a 47 incompatibility.Fix: arm fuel + the epoch deadline immediately before
instantiate. Two lines, plus a WHY comment. Behaviour is a strict tightening — instantiation is now bounded by exactly the same budget an export call gets.3. Regression test + spec. New
crates/digstore-host/tests/instantiation_bounds.rswith two fixtures, and a normative SPEC.md §7.1 "Serving-module execution bounds" stating that instantiation counts as guest execution and MUST be inside the budget.docs/superpowers/DRIFT-ANALYSIS*.mdupdated 45 → 47.Blast radius checked
cargo tree -i wasmtime(not memory) — wasmtime is reached only viadigstore-host(real dep) anddigstore-compiler(dev-dep only; its single use istests/obfuscation.rs). Consumers:digstore-remote,digstore-stage,digstore-cli. No other path.HostRuntime::new(the edited symbol) — grep across the tree: two production callers,crates/digstore-cli/src/ops/serve.rs:133andcrates/digstore-host/src/serve_blind.rs:229, plus 25 test call sites. Signature unchanged. Risk MEDIUM, not high: the only behavioural delta is that astartfunction is now metered, with the full default budget (5e9 fuel / 2 epoch ticks), and the entire workspace suite exercises both callers.gitnexusis disabled ecosystem-wide (CLAUDE.md §2.0 temp override — it hangs in worktrees), so the blast radius above was derived fromcargo tree -i+ a whole-tree grep + reading both call sites.Backwards compatibility (§5.1)
This is a runtime-only move. The compiler's own
wasm-encoderstays pinned at 0.221.3 (only wasmtime's internal copy moved 0.248 → 0.252), so emitted.digmodule bytes are unchanged, andwatis 1.253.0 on both sides. Old-format read proven green under 47:digstore-compiler::data_section_golden—new_reader_accepts_older_golden_without_public_manifest,data_section_matches_golden_vector,structure_is_independently_valid(3 passed)digstore-remote::golden_read_proof—committed_golden_verifies_then_decrypts,golden_bytes_are_byte_stable,tampered_golden_fails_the_gate,rpc_tier_read_proof+ 2 (6 passed, 2 pre-existing#[ignore])No section id, field encoding, or guest-wasm export signature is touched.
SemVer
Workspace 0.19.3 → 0.20.0;
digstore-host0.1.0 → 0.2.0.digstore-hostre-exports wasmtime types across its public API —imports::register(&mut wasmtime::Linker<RuntimeState>)andRuntimeState { pub limits: wasmtime::StoreLimits }— so a wasmtime major is a compatibility break for any crate that linksdigstore-hostdirectly (which hub.dig.net does). Under Cargo's semver rules for a0.xcrate the compatibility-breaking position is the minor field, so0.19.3 → 0.20.0is the major-equivalent bump the ticket asks for; it forces every consumer to opt in explicitly. Jumping to1.0.0would be a product statement unrelated to a CVE fix, so it is deliberately not made here.digstore-compilerstays 1.0.0 — its wasmtime dep is dev-only and no public signature moved.How verified
cargo deny check advisoriesonmain's lockerror[vulnerability] ... wasmtime 45.0.3 ... RUSTSEC-2026-0222→ advisories FAILEDcargo deny check advisories bans sourceson this branchcargo test --workspacecargo clippy --workspace --all-targets -- -D warningscargo fmt --all --checkcargo install --path crates/digstore-cli --force --locked→dig-store 0.20.0, theninit→add→commit(real 128 MB.dig, rootb1a6d014…) →catreturned the exact plaintext through the wasmtimeHostRuntimeread pathRUSTSEC-2026-0222 is not in
deny.toml's ignore list — the green is a genuine upgrade, not an allowlist. No advisory exception was added.The new test is load-bearing
Reverting only the two-line fix (from a committed state, via a file copy) re-fails
benign_start_function_instantiates_and_runswhile the rest of the diff stays in place.Fixture design, deliberately two-sided so neither test can pass for the wrong reason:
start_benign.wat— the truthful control. A legitimatestartwrites 32 bytes thatget_store_idreads back, so the assertion proves the start function ran to completion. The nearest wrong fixes (leave the store at zero fuel; arm a token budget) both fail it. A hostile-only test could not see this at all.start_spin.wat— the hostile counterpart: a non-terminatingstartmust be rejected promptly (bounded, not hung), guarding against a regression to no bound at all.Baseline control: both tests' failing case passes on
main's lock, confirming the regression is genuinely 47-introduced rather than a pre-existing red.Co-Authored-By: Claude noreply@anthropic.com