Skip to content

fix(security): wasmtime 43 -> 47.0.4 -- the real fix for RUSTSEC-2026-0269, and it needed ZERO source changes - #2807

Open
noahgift wants to merge 1 commit into
mainfrom
fix/wasmtime-47-rustsec-2026-0269
Open

fix(security): wasmtime 43 -> 47.0.4 -- the real fix for RUSTSEC-2026-0269, and it needed ZERO source changes#2807
noahgift wants to merge 1 commit into
mainfrom
fix/wasmtime-47-rustsec-2026-0269

Conversation

@noahgift

Copy link
Copy Markdown
Contributor

Closes #2806. Follows #2805 (the containment) with the actual fix.

What this is

#2805 stopped the bleeding by adding RUSTSEC-2026-0269 to .cargo/audit.toml. That was
the right andon call. This PR is the reason it stays a containment instead of becoming
the twelfth permanent exemption: wasmtime moves 43 -> 47.0.4 and all ten wasmtime
exemptions are deleted.

The same bump was already deferred once, in writing, in the RUSTSEC-2026-0222 note:

The fix is a real upgrade, not this line: 43 -> >=46.0.2,<47 or >=47.0.3, a four-major
jump on a test-only dep. Tracked separately; revisit before anything starts enabling
runtime.

That premise does not survive contact. crates/aprender-test-lib/src/runtime.rs is the
only file in the workspace that touches wasmtime — a bare Linker with four host functions
— and every API it uses is unchanged in 47. There is no API work. The diff is one manifest
line plus the lock.

Verification

Baseline reproduced before anything changed, using the reusable workflow's exact
command shape (sed the RUSTSEC ids out of .cargo/audit.toml into --ignore flags):

state result
origin/main audit.toml + wasmtime 43 lock exit 1error: 1 vulnerability found!, 9 allowed warnings, RUSTSEC-2026-0269 present
this PR (trimmed audit.toml + wasmtime 47 lock) exit 0 — 0 wasmtime/cranelift advisories, 5 allowed warnings

Compile and test, with the feature actually enabled (a cargo check --workspace never
builds an optional feature and would have proved nothing):

step result
cargo check -p aprender-test-lib --features runtime @ 43 (baseline) exit 0
cargo +1.95 check -p aprender-test-lib --features runtime @ 46.0.3 exit 0, no source edits
cargo +1.95 check -p aprender-test-lib --features runtime @ 47.0.4 exit 0, no source edits
cargo +1.95 test -p aprender-test-lib --features runtime --lib -- runtime 73 passed, 0 failed

Mutation-verified rather than asserted — the exemptions were proved obsolete by deleting
them
, not by reasoning:

wasmtime 43 lock + trimmed audit.toml -> exit 1, "2 vulnerabilities" (-0269 and -0222)
wasmtime 47 lock + trimmed audit.toml -> exit 0

Deleted as obsolete: RUSTSEC-2026-0085 / -0086 / -0088 / -0089 / -0091 / -0092 / -0094 / -0096 / -0114 / -0222.

Allowed warnings drop 9 -> 5: bitmaps, im-rc and sized-chunks (x2) entered solely
through cranelift 0.130's tree and are gone at 0.134. No warning became an error, and
nothing new appeared.

The one real tradeoff, stated plainly

wasmtime >=46 requires Rust 1.94.0; rust-toolchain.toml pins 1.93.0. Both fixed
lines (46.0.3 and 47.0.4) need 1.94.

CI stays green anyway, and this was checked rather than assumed:
cargo check -p aprender-test-lib (default features, rustc 1.93.0, wasmtime 47 in the
lock) exits 0. runtime is optional and nothing in the workspace,
.github/workflows/, scripts/ or the Makefile enables it, so cargo never builds
wasmtime and the MSRV check never fires.

The honest cost: --features runtime now needs Rust 1.94+ until the pin moves. The
feature is already dark, and cargo fails with an explicit MSRV message rather than
silently — but it is a real regression and is documented at the dep declaration, not
buried. The toolchain bump is deliberately not bundled here; measured separately,
cargo +1.95 check --workspace is clean (0 errors) but clippy surfaces ~56 new code lints
across ~11 classes that would fail under -D warnings. That belongs in its own PR (#2806).

Reachability, re-verified from scratch

Not inherited from the audit.toml note. The check needed a positive control first: an early
pattern matched the worktree path /home/noah/src/aprender-wasmtime-fix/ itself and made
every tree look full of wasmtime. Pattern validated against a known-true case
(-p aprender-test-lib --features runtime -> 35 matches) before any zero was trusted, and
every tree below is non-empty.

invocation tree lines wasmtime
cargo tree -p aprender 1808 0
cargo tree -p aprender --no-default-features 140 0
cargo tree -p aprender --all-features 2051 0
cargo tree -p apr-cli 1904 0
cargo tree -p apr-cli --all-features 2155 0
cargo tree -e normal -p aprender 1713 0
cargo tree --workspace 3539 0
cargo tree --workspace --all-features 5492 35

No CI lane runs --workspace --all-features: make coverage (coverage-nightly.yml) is
--workspace --exclude aprender-gpu --lib, and cargo mutants (ci.yml) is -- --lib. The
--all-features targets that would pull it in (test-full, coverage-full) are not
CI-wired.

Second, independent reason it was never exploitable: per GHSA-vqjp-4c8c-hfgg the vulnerable
code lives in wasmtime-wasi, which is not in Cargo.lock at all. The vulnerable code
was not merely unreachable — it was not compiled.

Also in here

contracts/wasmtime-upgrade-v1.yaml -> 2.0.0. Its advisory_elimination equation ("zero
wasmtime entries in .cargo/audit.toml") was false for the entire life of the 43.x
cluster and nothing noticed
— none of its FALSIFY-WASM tests are wired into CI
(grep -rn "wasmtime-upgrade\|FALSIFY-WASM" .github/ scripts/ Makefile returns nothing).
This PR makes the equation true. Wiring the falsifiers so it can fail again is noted in
#2806 as the dark-target class.

pv validate contracts/wasmtime-upgrade-v1.yaml -> 0 errors, 0 warnings.

Merge order

Land after #2805. This branch is cut from origin/main and will conflict with it in
.cargo/audit.toml; resolve by taking this side and additionally deleting #2805's
RUSTSEC-2026-0269 line, which this bump makes obsolete. cargo audit must come out
exit 0 with zero wasmtime ids in the file.

@noahgift

Copy link
Copy Markdown
Contributor Author

pr-review v2.0.0 — automated adversarial review (PR-REVIEW-SKILL-002-v2)

DEGRADED: pmat MCP unreachable (CLI fallback used) and its index is stale — index d6c6c6f8f is NOT an ancestor of head 3dcc33b93 (71 commits behind main); guard-scope mutation set is empty (attempted=0 on a contracts/*.yaml falsifier diff) | pr-review v2.0.0 | verdict=DEGRADED | consultations: pmat=consulted cuda=not-triggered crux=consulted mutation=consulted | findings=5 (cited=0 measured=5 asserted=0) | index=d6c6c6f ancestor=false | CORE SECURITY CLAIM INDEPENDENTLY REPRODUCED: origin/main lock+audit.toml -> cargo audit exit 1 with RUSTSEC-2026-0269 on crate wasmtime ("Filesystem sandbox escape when paths or symlinks contain trailing slashes"); PR head with CI's exact sed-derived --ignore list (20 ids) -> exit 0, 5 allowed warnings, zero wasmtime/cranelift; cargo deny check advisories -> exit 0 "advisories ok"; lockfile diff confined to the wasm tree (added mach2/object; removed bitmaps/im-rc/sized-chunks/rand_xoshiro/wasm-encoder/wasmparser/winch-codegen/wasmtime-internal-winch), no unrelated version drift; 0 .rs files changed, 0 SATD added; the audit.toml comment's mechanism claim ("CI greps every RUSTSEC token out of this file, comments included") is CORRECT — sovereign-ci.yml runs `sed -n 's/.*\(RUSTSEC-[0-9]*-[0-9]*\).*/\1/p' .cargo/audit.toml` | cuda not-triggered: 0 of 4 changed paths match the GPU trigger set, 0 cuda/ptx/cublas/fp8/nvrtc/sm_N tokens in the non-lock diff, 0 GPU crate names in the lock diff, no device-semantics claim made | crux: no CLI/HTTP/MCP/output-format surface changed; comparative-claim scan found 0 "Nx competitor" claims in diff or PR body so 3.C.1 is not engaged and nothing is unverified_comparative_claim; crux_coverage=none for the advisory-exemption config surface (no crux-*.yaml carries it as category) — recorded, not escalated on a manifest-only diff | no blocking class fired: blocking a reproduced CVSS 8.8 fix over contract text would be the "guard that blocks work instead of defects" failure the spec names

5 findings raised, 2 survived adversarial refutation (three independent verifiers per finding — correctness / scope / evidence-quality lenses, refute-by-default). Refuted findings are dropped, not shown.

DEGRADED. The pmat MCP server was unreachable for this run (ConnectionRefused). Per spec 3.0 an unreachable source is reported, never silently treated as clean. Consultations that fell back to the pmat CLI are recorded as such; no-authority-found means the source was reached and had nothing.


1. Cargo.toml's new MSRV comment asserts a coverage claim that files in the same commit falsify — six --all-features call sites DO build wasmtime, and they now hard-fail on the pinned toolchain

Cargo.toml:360 · grounding: measured · blocking class: none

Evidence

Claim introduced by this diff (Cargo.toml:358-364):
  "# MSRV: wasmtime >=46 requires Rust 1.94.0, and rust-toolchain.toml pins 1.93.0.
   # That is fine for CI and for every shipped artifact, because `wasmtime` is
   # optional behind aprender-test-lib's `runtime` feature and NOTHING in the
   # workspace, .github/workflows/, scripts/, or the Makefile enables it -- cargo
   # never builds it, so the MSRV check never fires."

MEASURED, `grep -rn -- "--all-features" .github/workflows/ Makefile scripts/` at PR HEAD:
  Makefile:112:		time PROPTEST_CASES=100 QUICKCHECK_TESTS=100 cargo nextest run --workspace --all-features;   [target: test-full]
  Makefile:114:		time PROPTEST_CASES=100 QUICKCHEC

Why it matters — The comment is the only place a future reader looks before touching the toolchain pin or the wasmtime version, and it tells them a falsehood about scripts/ and Makefile — the exact 'a guard that does not scan the surface where the DECISION is made' class in CLAUDE.md, with make run-ci / scripts/release.sh being the release surface that was never enumerated. Concrete failure: a developer runs make test-full or make coverage-full on this branch, gets 'rustc 1.93.0 is not supported by the following packages', and the manifest comment tells them cargo never builds wasmtime. Fix: narrow the claim to '.github/workflows/' (which is what was actually verified), or bump/gate so the --all-features targets survive.

Why this survived refutation

Finding survives all five refutation gates; every claim reproduced independently. (1) INTRODUCED BY THIS PR, not main: git diff 745fa8588 pr2807 -- Cargo.toml shows the entire MSRV comment block is added alongside wasmtime = "43" -> "47.0.4". On main, wasmtime-43.0.2/Cargo.toml:14: rust-version = "1.91.0" <= the 1.93.0 pin, so the --all-features targets build fine there; the PR's wasmtime-47.0.4/Cargo.toml:14: rust-version = "1.94.0" breaks them. Both the false claim and the concrete breakage are new. (2) EVIDENCE REPRODUCES EXACTLY: git show pr2807:Makefile | grep -n -- "--all-features" gives 112, 114 (test-full), 633 (coverage-full), 930, 946 (mutants), all workspace-scoped; plus scripts/ci.sh:63: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info, live inside step 6, not commented out = the claimed six. Makefile:473 is prose and was correctly not counted. Makefile and scripts/ci.sh are byte-identical between main and pr2807, so the call sites are not something the reviewer imagined. (3) THE FEATURE IS REACHABLE: crates/aprender-test-lib is a workspace MEMBER (Cargo.toml Phase 3c block, not in exclude); its manifest declares runtime = ["wasmtime", "async-trait"] with wasmtime = { workspace = true, optional = true } under [target.'cfg(not(target_arch = "wasm32"))'.dependencies], i.e. present on every native build, so --workspace --all-features does enable it. (4) MECHANISM PROVEN, NOT INFERRED: isolated scratchpad crate depending on wasmtime = "=47.0.4" with a 1.93.0 rust-toolchain.toml produced `error: rustc 1.93.0 is no

---

2. The contract now ships two falsification tests whose test: command is RED at HEAD, with an if_fails that misdiagnoses the failure

contracts/wasmtime-upgrade-v1.yaml:59 · grounding: measured · blocking class: none

Evidence

At PR HEAD (contracts/wasmtime-upgrade-v1.yaml):
  - id: FALSIFY-WASM-001
    prediction: "cargo check -p aprender-test-lib --features runtime compiles (needs Rust >=1.94; wasmtime 47 MSRV)"
    test: "cargo check -p aprender-test-lib --features runtime"
    if_fails: "API signature changed — check compiler errors for specific methods"
  - id: FALSIFY-WASM-004
    prediction: "wasm_reference_types works with gc feature"
    test: "cargo check -p aprender-test-lib --features runtime"
    if_fails: "gc feature not enabled or wasm_reference_types removed in the pinned version"

I ran that exact `test:` command at PR HEAD under the repo's pinned rust-toolchain.toml (channel = "1.93.0"):
  $ carg

Why it matters#2806 explicitly proposes wiring the FALSIFY-WASM tests into CI. If that happens against the current toolchain pin, FALSIFY-WASM-001 and -004 go RED on correct code and their if_fails sends the operator hunting for 'API signature changed — check compiler errors for specific methods', which is the wrong diagnosis for an MSRV refusal. A falsifier that fails for a reason other than the property it tests is worse than one that cannot fail. Either the test: needs cargo +1.94 / a documented precondition, or the toolchain pin moves first.

Why this survived refutation

Survives the scope lens. The RED-ness is introduced by THIS diff, verified from both trees plus the registry sources: origin/main resolves wasmtime 43.0.2 whose declared rust-version is 1.91.0, while PR HEAD (3dcc33b) resolves wasmtime 47.0.4 whose declared rust-version is 1.94.0 (read directly from ~/.cargo/registry/src/index.crates.io-*/wasmtime-{43.0.2,47.0.4}/Cargo.toml). rust-toolchain.toml is channel = "1.93.0" on BOTH main and HEAD, untouched by the diff. So cargo check -p aprender-test-lib --features runtime passes on main (1.91<=1.93) and hard-fails at HEAD (1.94>1.93) — the exact cargo MSRV-refusal the finding pasted, whose cited cranelift-assembler-x64@0.134.4 is in HEAD's lock and absent from main's. The PR author confirms the mechanism in the diff's own Cargo.toml comment ("--features runtime needs Rust 1.94+ until the toolchain pin moves"). Not a negative control: the HEAD contract has no "MUST fail"/"SELF-TEST PASSED" marker and its falsification_tests are predicted to pass ("prediction: cargo check ... compiles"), so RED = falsified. Grounding measured is legitimate (real output pasted, and I reproduced its cause independently). blocking: none, so there is no blocking_class to validate against spec 7. The file is genuinely edited by the diff (version 1.0.0->2.0.0, -001's prediction and -004's if_fails both changed), so spec 3.D guard scope applies as claimed. Two nits that do not refute: -004's if_fails WAS touched (v43 -> "the pinned version"), just not to address MSRV, and the finding omits FALSIFY-WASM-002, which goes RED for the identical reason — th


Reviewer actor is distinct from the author actor (spec 5). This review is advisory: no finding here landed in a spec-7 blocking class.

…-0269, and it needed ZERO source changes

RUSTSEC-2026-0269 (CVSS 8.8, filesystem sandbox escape, published 2026-08-20)
took the required ci / security check red on every open PR at once. #2805
contained it by adding the id to .cargo/audit.toml. This is the fix that makes
the containment temporary rather than permanent.

The same bump had already been deferred once, in writing, in the
RUSTSEC-2026-0222 note in that same file:

  The fix is a real upgrade, not this line: 43 -> >=46.0.2,<47 or >=47.0.3,
  a four-major jump on a test-only dep. Tracked separately; revisit before
  anything starts enabling runtime.

That premise is wrong. crates/aprender-test-lib/src/runtime.rs is the only file
in the workspace that touches wasmtime -- a bare Linker with four host functions
-- and every API it uses is unchanged in 47. The bump is one manifest line:

  cargo check -p aprender-test-lib --features runtime @ 43     -> exit 0 (baseline)
  cargo check -p aprender-test-lib --features runtime @ 46.0.3 -> exit 0, no edits
  cargo check -p aprender-test-lib --features runtime @ 47.0.4 -> exit 0, no edits
  cargo test  -p aprender-test-lib --features runtime -- runtime -> 73 passed, 0 failed

Ten wasmtime/cranelift exemptions are now deletable, verified by REMOVING them
rather than by assertion. Reproduced with the reusable workflow's exact command
shape (sed the RUSTSEC ids out of .cargo/audit.toml into --ignore flags):

  origin/main audit.toml + wasmtime 43 lock -> exit 1, 1 vulnerability, 9 warnings
  trimmed     audit.toml + wasmtime 47 lock -> exit 0, 0 wasmtime/cranelift, 5 warnings

Mutation-verified: revert the lock to 43 with the trimmed list and it turns RED
again (exit 1, RUSTSEC-2026-0269 and RUSTSEC-2026-0222 both firing); restore 47
and it goes green. Allowed warnings drop 9 -> 5 because bitmaps, im-rc and
sized-chunks (x2) entered solely through cranelift 0.130 and are gone at 0.134.
No warning became an error and nothing new appeared.

MSRV, stated plainly rather than buried: wasmtime >=46 requires Rust 1.94.0 and
rust-toolchain.toml pins 1.93.0. CI stays green regardless, because runtime is
optional and NOTHING in the workspace, .github/workflows/, scripts/ or the
Makefile enables it, so cargo never builds wasmtime and the MSRV check never
fires (cargo check -p aprender-test-lib under 1.93 with the 47 lock: exit 0).
The honest cost is that --features runtime now needs Rust 1.94+ until the pin
moves; cargo fails with an explicit MSRV message, not silently. The toolchain
bump is deliberately NOT bundled here -- measured separately, cargo +1.95 check
--workspace is clean but clippy surfaces ~56 new code lints that would fail
under -D warnings. Tracked in #2806.

Reachability was re-verified from scratch, not inherited. The check needed a
positive control first: an early pattern matched the worktree path itself and
made every tree look full of wasmtime. Validated against a known-true case
(-p aprender-test-lib --features runtime -> 35 matches) before any zero was
trusted; all trees non-empty. 0 wasmtime in -p aprender, --no-default-features,
--all-features, -p apr-cli, -e normal, and --workspace; 35 only under
--workspace --all-features, which no CI lane runs.

Second, independent reason it was never exploitable: per GHSA-vqjp-4c8c-hfgg the
vulnerable code is in wasmtime-wasi, which is not in Cargo.lock at all. The
vulnerable code was not merely unreachable, it was not compiled.

contracts/wasmtime-upgrade-v1.yaml goes to 2.0.0. Its advisory_elimination
equation ("zero wasmtime entries in .cargo/audit.toml") was FALSE for the entire
life of the 43.x cluster and nothing noticed -- none of its FALSIFY-WASM tests
are wired into CI. This commit makes the equation true; wiring the falsifiers is
noted in #2806.

Refs #2806, #2805, RUSTSEC-2026-0269, GHSA-vqjp-4c8c-hfgg

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

REBASED onto main at 75b178a (2026-08-31). The conflict was semantic, not
textual: #2805 landed its containment line for the sandbox escape INSIDE the
exemption block this commit deletes. Resolved in this commit's favour --
eleven ids go, not ten, because the containment entry is obsolete for exactly
the same reason as the 43.x cluster it was appended to. Re-verified after the
rebase with the reusable workflow's own command shape, not by assertion:

  main 75b178a as-is (43 lock, 31 ignores)          -> exit 0, 9 allowed warnings
  main 75b178a lock + this commit's audit.toml      -> exit 1, 2 vulnerabilities
      RUSTSEC-2026-0269 8.8 high AND RUSTSEC-2026-0222 3.8 low both fire
  this commit (47.0.4 lock, 20 ignores)               -> exit 0, 5 allowed warnings

The middle line is the mutation: hold the trimmed ignore list fixed and revert
only the lockfile, and the gate turns RED. So the green on the third line is
the bump doing the work, not a shorter list hiding it. Reported-advisory sets
diffed rather than eyeballed: nothing new appeared, and four went away
(bitmaps, im-rc, sized-chunks x2 -- all cranelift 0.130-only; 0.130.2 -> 0.134.4).

Both inherited claims were re-checked rather than trusted. No CI lane builds
the runtime feature: `all-features` occurs ZERO times in all of
.github/workflows/, ci.yml's mutants job runs `cargo mutants --in-diff -- --lib`,
`make coverage` is `--workspace --exclude aprender-gpu --lib`, and no workflow
invokes test-full / coverage-full / mutants. Reachability re-measured with a
positive control FIRST (`-p aprender-test-lib --features runtime` -> non-zero,
so a zero elsewhere means something): 0 wasmtime for -p aprender, -p aprender
--no-default-features, -p apr-cli, --workspace, --workspace -e normal, and
-p aprender-test-lib at its defaults. And the MSRV pin does not need to move:
under the pinned rustc 1.93.0, `cargo metadata --locked` and
`cargo check --locked -p aprender-test-lib --lib` both exit 0 against the 47
lock, so rust-version = "1.91" stays put too.

deny.toml carried a stale "wasmtime 43 + cranelift ... Upgrade to >=43.0.2 when
available" comment. It is deleted here, which makes this contract's
advisory_elimination invariant ("deny.toml has zero wasmtime entries") true on
purpose rather than by accident -- cargo-deny never read that line either way.
`cargo deny check` -> advisories ok, bans ok, licenses ok, sources ok.
@noahgift
noahgift force-pushed the fix/wasmtime-47-rustsec-2026-0269 branch from 5cf61b4 to 7104c4e Compare September 5, 2026 16:57
@noahgift

noahgift commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

PP-066 triage (epic #2873, ticket #2986): MERGE-NOW (security advisories first; linked issue #2806 sits in C0-1's list, whose cargo-deny job stays red until this lands). Rebased onto origin/main @ cdc0acb and force-pushed with --force-with-lease (5cf61b47104c4e). The only conflict was Cargo.lock; it was regenerated, not hand-merged: main's lockfile + cargo update -p wasmtime --precise 47.0.4 (43 lock lines, wasmtime/cranelift/wasmparser cluster only), then cargo metadata --locked rc=0 and cargo deny check advisories rc=0 ("advisories ok"). Manifest, deny.toml, audit.toml and the contract are byte-identical to the PR's. Auto-merge is armed only once ci / gate and workspace-test are green on this head.

@noahgift noahgift added the pp-066/merge triage 0.66 — MERGE disposition label Sep 5, 2026
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

§13.11 rung 1 — quorum shadow verdict

S13-SHADOW pr=2807 head=7104c4e4568c0fdfbbab7d7b6e48970e635308ec verdict=REFUSE class=Q1 arm_rc=1

Shadow mode: this records a verdict and merges nothing. A refusal
to arm is not a block (§13 adds zero rows to §7) — the pull request is
exactly as green as it was.

@noahgift
noahgift disabled auto-merge September 7, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pp-066/merge triage 0.66 — MERGE disposition

Projects

None yet

Development

Successfully merging this pull request may close these issues.

wasmtime 43 -> 47: the bump is ZERO source changes; the only blocker is the 1.93 toolchain pin (RUSTSEC-2026-0269, second deferral)

1 participant