sec: close the cargo-audit backlog — move the WASM sandbox off a runtime with a filesystem escape, an aarch64 miscompile and a fuel-limit bypass #443

Merged
dcharlot merged 5 commits from sec/advisory-sweep-2026-10 into main 2026-10-22 12:49:07 -04:00

2026-10-22

Transaction Science
ci: gate on cargo-audit, so an advisory has to be read rather than accumulated
All checks were successful
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Successful in 21s
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Successful in 20s
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 23s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m31s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 3m33s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m16s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m21s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m56s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 5m11s
jouleclaw fabrics / metal (macOS, Apple GPU) (pull_request) Successful in 53s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 5m57s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 12m24s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 17s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 39s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 3m51s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 2m11s
wai-pack / wai-pack · macos (pull_request) Successful in 58s
wai-quantum command line / command line · macos (pull_request) Successful in 27s
wai-quantum dataflow member / member · dataflow device absent (macos) (pull_request) Successful in 12s
wai-quantum FPGA member / member · FPGA datapath, simulated (macos) (pull_request) Successful in 1m4s
wai-quantum Metal member / member · Metal (macos) (pull_request) Successful in 24s
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Successful in 18s
wai-quantum neural-processor member / member · neural processor (macos) (pull_request) Successful in 5m30s
wai-quantum service / service · macos (pull_request) Successful in 36s
wai-quantum matrix-extension member / member · matrix extension absent (macos) (pull_request) Successful in 8s
wai-quantum studio and desktop / desktop app · macos (pull_request) Successful in 1m58s
wai-quantum portable tensor program / tensor program · Metal and the arm64 CPU (macos) (pull_request) Successful in 16s
wai-quantum CPU vector units / member · vector unit absent (macos) (pull_request) Successful in 11s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 2m16s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 1m31s
The 142 vulnerability instances this branch started from were not the result of
a decision. They accumulated because a vulnerable crate usually arrives
transitively, in a lockfile nobody edited, and nothing in CI was looking. The
fixes in this branch are worth little if the backlog can rebuild quietly.

`scripts/check-advisories.py` runs `cargo audit --json` over every tracked
Cargo.lock and fails on any advisory not named in its ALLOW table. It reads the
advisory database and the lockfiles; it builds nothing.

ALLOW is the substance. An advisory may be left in place — upstream has no
fixed release, the vulnerable call is unreachable, it is dev-only — but only in
writing, with the reason beside the id. Two rules keep that from rotting:

  - an unlisted advisory fails, so a new one gets looked at;
  - a listed advisory that no longer appears anywhere ALSO fails, so an excuse
    cannot outlive the exposure it was written for. An allow list that is never
    pruned stops describing the tree and starts hiding it.

It currently allows 18, each with its reason: zip and rsa as unreachable,
hpke-rs 0.2.0 as dev-only, and quick-xml/libcrux-kem/h2/hickory-proto/pyo3 as
pinned by dependents that need an upstream release. The quick-xml 0.37.5 entry
is marked as a known remaining exposure on the ISO 20022 ingestion path rather
than a dismissal, so it reads as debt and not as a verdict.

Warnings (unmaintained, unsound, yanked — 67 of them) are counted and printed
but do not fail the run: gating on maintenance advice would bury the reachable
defects in noise. Printing the count keeps the number visible instead of
unknown.

A lockfile that cannot be audited at all fails under its own heading, so an
error is never read as "clean" — the same failure-closed shape as
check-lockfiles.sh, and the mistake this sweep made by hand when a broken
output parser reported "0 vulnerabilities" while criticals were printing.

The self-test asserts the check passes a reviewed tree and fails each way it
should: an unlisted advisory, an allowance matching nothing, and a document
that does not parse.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-22 12:49:04 -04:00
Transaction Science
trx, screening: resolve XML entity references, which quick-xml 0.41 now reports as their own event
The quick-xml 0.41 bump did not just rename `BytesText::unescape`. 0.41 reports
every entity reference — `&amp;`, `&apos;`, `&#38;` — as its own
`Event::GeneralRef` instead of folding it into the surrounding text. Both XML
readers here matched only `Event::Text`, so after the bump each reference was
dropped **silently**: the document still parsed, every existing test still
passed, and the values were wrong.

That is the dangerous shape of this change, and it is why it needed more than a
compile fix:

  - `op-screening` parses the OFAC/EU/UN sanctions lists. An entry published as
    "Smith & Co" was being stored as "Smith  Co" — a name that no longer matches
    the party it names, with nothing downstream able to tell. A screening miss
    reads exactly like a clean screen.
  - `jouleclaw-transform-trx` carries JSON property values through TRX XML. The
    quotes arrive escaped, so dropping the references turned `{"k":1}` into
    `{k:1}`, which fails to parse and falls back to being stored as a bare
    string. One round-trip test caught this; nothing else did.

Both readers now accumulate an element's character data across events and
resolve each reference: a numeric reference through `resolve_char_ref`, a named
one through `resolve_predefined_entity`. TRX carries no DTD, so an unknown name
there is refused rather than dropped. The sanctions readers keep an unknown
reference in its literal `&name;` form, so the anomaly stays visible in the
record instead of silently altering a name.

`trim_text(true)` had to go with it, on all four readers. Trimming is applied
per text event, so once a run is split by a reference the trim eats the spaces
on either side of it — "Smith & Co" became "Smith&Co", which the new tests
caught. The assembled value is trimmed once where it is consumed instead; the
screening readers already did exactly that at every consumer, making the
reader-level trim redundant there even before this change.

Tests: the fixtures had no entity in them anywhere, which is precisely why a
silent data-corruption bug could pass CI. Added, failing first: a sanctioned
name carrying `&amp;` and one carrying `&#38;` (different code paths — a named
predefine and a numeric reference), entity references in TRX text asserted
against hand-written XML rather than our own writer's escaping, and a refusal
for an unresolvable name.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-22 12:45:52 -04:00
Transaction Science
sec: drop the unused rsa dependency from op-3ds2, removing the one advisory with no fix
Some checks failed
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Successful in 18s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 2m45s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 5m35s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m22s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 4m19s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m50s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m37s
jouleclaw fabrics / metal (macOS, Apple GPU) (pull_request) Successful in 56s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 5m53s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 5m12s
standards workspaces build + test / jouledesk-rs (jouledesk/jouledesk-rs, jouledesk-rs, macos) (pull_request) Successful in 3m11s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 13m54s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 32s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m56s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 9m15s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Successful in 2m54s
wai-quantum command line / command line · macos (pull_request) Has been cancelled
wai-quantum dataflow member / member · dataflow device absent (macos) (pull_request) Has been cancelled
wai-quantum FPGA member / member · FPGA datapath, simulated (macos) (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Successful in 1m5s
wai-quantum Metal member / member · Metal (macos) (pull_request) Has been cancelled
wai-quantum neural-processor member / member · neural processor (macos) (pull_request) Has been cancelled
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Has been cancelled
wai-quantum matrix-extension member / member · matrix extension absent (macos) (pull_request) Has been cancelled
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been cancelled
wai-quantum portable tensor program / tensor program · Metal and the arm64 CPU (macos) (pull_request) Has been cancelled
wai-quantum CPU vector units / member · vector unit absent (macos) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Has been cancelled
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Has been cancelled
wai-quantum service / service · macos (pull_request) Has been cancelled
RUSTSEC-2023-0071 (Marvin attack: key recovery through timing sidechannels in
RSA decryption) is the only finding in this sweep with **no fixed version
available** — there is nothing to upgrade to.

It needed neither a mitigation nor a risk acceptance, because the dependency
was never used. `rsa::`, `use rsa` and `extern crate rsa` appear in none of
op-3ds2's 20 source files, nor anywhere else in the openpay workspace. The
declaration described an intent ("RSA for legacy issuer ACS public keys") that
was never implemented; the only asymmetric crypto actually present is P-256
ECDH/ECDSA. Removing the line drops rsa 0.9.10 and pkcs1 0.7.5 from the graph.

The adjacent comment still advertised RSA support, which would now mislead a
reader into thinking it was dropped rather than never built. It is rewritten to
say so, and to record that the crate cannot simply be re-added: the Marvin
defect is unfixed upstream, so a future legacy-ACS path needs a constant-time
implementation, not a version bump.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-22 09:50:04 -04:00
Transaction Science
sec: move quick-xml to 0.41 where we control it — the ISO 20022 deserializer parses counterparty XML
RUSTSEC-2026-0194 (quadratic run time checking a start tag for duplicate
attribute names) and -0195 (unbounded namespace-declaration allocation in
`NsReader`) are both 7.5 denial-of-service defects in quick-xml.

-0195 is not reachable: `NsReader` appears nowhere in the tree. -0194 is,
and on a path that matters — `openpay/crates/op-iso20022/src/codec.rs` calls
`quick_xml:🇩🇪:from_str` on ISO 20022 messages, which arrive from payment
counterparties. Attacker-shaped XML is the normal case there, not the
exception, so a quadratic parse is a real availability vector on payment
ingestion.

Bumped 0.36 -> 0.41.0 in the four lockfiles where the version is ours to
choose: openpay, jouleclaw, mesh, and the l3-jouleclaw example.

Two remain pinned by dependents and cannot be fixed from here:
  - openpay also resolves quick-xml 0.37.5 through `open-payments-iso20022
    v1.0.10`. That is the same ISO 20022 surface, so this bump hardens our own
    deserializer but NOT that library's internal parsing. Closing it needs an
    upstream release; it is not closed by this commit.
  - smart-byte resolves 0.39.4 through plist -> netdev -> netwatch -> iroh.
    That path parses macOS plists, not counterparty XML, so the exposure is
    materially lower.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-22 09:48:55 -04:00
Transaction Science
sec: close the advisory backlog across every workspace, and move the WASM sandbox off a runtime with three critical escapes
`cargo audit` over all 47 workspace lockfiles reported 142 vulnerability
instances. The dangerous ones were not exotic: the S2 executor was pinned to
wasmtime 43.0.2, which carries eight advisories including RUSTSEC-2026-0379
(9.9), -0327 (9.3) and -0269 (8.8). That crate is the one component whose whole
job is to execute untrusted guest code, so a sandbox-escape class defect there
is not a dependency hygiene matter — it is the confinement claim itself.

wasmtime moves 43.0.2 -> 49.0.4, the single version every one of the eight
advisories names as patched (50.0.0 exists but no advisory lists it as fixed,
so it is not yet the safe choice). WASMTIME_VERSION is updated with it: the
constant is attested into the mechanism commitment under SPEC §7.9, so leaving
it stale would attest confinement to a runtime that did not run. The crate's
own `wasmtime_version_matches_the_lockfile` test enforces that pairing.

The rest are semver-compatible bumps carrying no API change: rustls 0.23.46,
rustls-webpki 0.103.16, webpki-roots/webpki-root-certs 1.0.10, quinn-proto
0.11.18 (RUSTSEC-2026-0340/-0343, both 7.5), tokio 1.54.1, crossbeam-epoch
0.9.20, rtrb 0.3.5.

Not fixed here, and deliberately so rather than silently: zip (>=9.0.0),
hpke-rs (>=0.6.0), quick-xml (>=0.41.0) and libcrux-kem (>=0.0.10) are all
semver-major moves, three of them transitive, so each needs its dependent
assessed on its own. rsa RUSTSEC-2023-0071 has no fixed version at all.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-22 09:44:59 -04:00