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
Owner

cargo audit over all 47 workspace lockfiles reported 142 vulnerability instances. This closes the reachable ones and records, with evidence, why each survivor survives.

The one that mattered

The S2 (WASM) executor was pinned to wasmtime 43.0.2, carrying eight advisories:

ID Sev What
RUSTSEC-2026-0379 9.9 Cranelift aarch64 can jump to an incorrect target on a large br_table
RUSTSEC-2026-0327 9.3 async-lifted callback result count unvalidated → native stack buffer overflow
RUSTSEC-2026-0269 8.8 Filesystem sandbox escape via trailing slashes in paths/symlinks
RUSTSEC-2026-0316 1.0 Dynamic record lifting allocates beyond the hostcall fuel limit

Three of these land squarely on the confinement claim rather than beside it: -0269 is a sandbox escape in the component whose only job is confinement, -0316 defeats the fuel metering the S2 tier attests, and -0379 is an aarch64 miscompile — the entire fleet is Apple Silicon, so that is the fleet's actual codegen path, not a remote arch.

Moved to 49.0.4, the single version every one of the eight names as patched. 50.0.0 exists but no advisory lists it as fixed, so it is not yet the safe choice.

WASMTIME_VERSION moves with it: the constant is attested into the mechanism commitment under SPEC §7.9, so a stale value would attest confinement to a runtime that did not run. The crate's own wasmtime_version_matches_the_lockfile test enforces the pairing.

Verified: builds clean and all 17 tests pass, including every confinement test (fuel exhaustion, egress allowlist, storage globs, taint propagation) — no API changes were needed across the six majors.

Reachable DoS on a payment path

quick-xml RUSTSEC-2026-0194 (quadratic run time checking a start tag for duplicate attributes) is reachable: openpay/crates/op-iso20022/src/codec.rs calls quick_xml::de::from_str on ISO 20022 messages, which arrive from payment counterparties. Attacker-shaped XML is the normal case there. Bumped 0.36 → 0.41.0 in the four lockfiles where the version is ours to choose.

-0195 (unbounded namespace allocation in NsReader) is not reachable — NsReader appears nowhere in the tree.

The advisory with no fix

rsa RUSTSEC-2023-0071 (Marvin timing sidechannel) has no fixed version. It needed neither mitigation nor risk acceptance: the dependency was unused. rsa::, use rsa and extern crate rsa appear in none of op-3ds2's 20 source files nor anywhere in openpay. The declaration described an intent never implemented; only P-256 ECDH/ECDSA is actually present. Dropped, removing rsa and pkcs1 from the graph.

Semver-compatible bumps, 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 (-0340/-0343, both 7.5) · tokio 1.54.1 (-0384, Interval race) · crossbeam-epoch 0.9.20 · rtrb 0.3.5

Not fixed, deliberately, with reasons

  • zip (9.1 -0369, 7.3 -0367, -0368) — not reachable. All three are extract()-only (SUID/SGID preservation, symlink escape, permissions through symlinks). We never extract an archive: there are no ZipArchive/ZipStreamReader call sites. It arrives transitively via candle-core.
  • hpke-rs 0.2.0 (9.3 -0071, 8.2 -0070, -0069) — never shipped. The production MLS path (joule-moq-mls) already resolves the patched 0.7.0. The vulnerable 0.2.0 is reached only through a [dev-dependencies] edge of joule-code-collab, which still pins the older openmls 0.6/0.3 stack. Worth aligning to the 0.9.1/0.6.0 stack already used in-repo, but it is test-graph only.
  • libcrux-kem 0.0.9 (8.7 -0331, 5.6 -0330) — blocked upstream. hpke-rs-libcrux =0.7.0 constrains it, so 0.0.10 needs an openmls/hpke-rs release. Both are panics (DoS), not memory corruption.
  • quick-xml 0.37.5 in openpay — blocked upstream, and this one is NOT closed. It resolves through open-payments-iso20022 v1.0.10, the same ISO 20022 surface. This PR hardens our own deserializer but not that library's internal parsing.
  • quick-xml 0.39.4 in smart-byte — lower exposure. Via plist → netdev → netwatch → iroh; parses macOS plists, not counterparty XML.

Separately checked: the current PHP and Rust headlines

  • PHP CVE-2026-109618 / -109619 (CVSS 9.2, unauth RCE): not applicable. No PHP interpreter on any reachable host; no php_fastcgi/fastcgi directive in the Caddyfile (canonical and live agree, 3492 lines); 0 .php files across all 313 deployed site directories. The sites are static Astro builds behind file_server.
  • crates.io supply-chain compromise (CVE-2026-77649 / RUSTSEC-2026-0266, -0260): not exposed, three ways — the malicious proc-macro1 was never fetched into any cargo registry cache on dev or the build host; arrayref resolves to 0.3.9 everywhere (only 0.3.10 was compromised); internment to 0.8.6 (only 0.8.7). Zero compromised pins across all 13,241 lockfiles in the tree.
  • Toolchain is rustc/cargo 1.98.0, so CVE-2026-5222 (1.68–1.96) and CVE-2026-5223 (fixed 1.96.0) are both already patched.

🤖 Generated with Claude Code


Update — follow-up work on this branch

The quick-xml bump turned out to hide a silent data-corruption bug, which is the most important thing found after the first pass. 0.41 does not merely rename BytesText::unescape: it reports every entity reference (&, ', &) 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:

  • op-screening parses the OFAC/EU/UN sanctions lists. An entry published as "Smith & Co" was stored as "Smith Co" — a name that no longer matches the party it names. A screening miss reads exactly like a clean screen.
  • jouleclaw-transform-trx carries JSON through TRX XML; dropping the references turned {"k":1} into {k:1}, which fails to parse and silently falls back to a bare string.

Both readers now accumulate an element's character data across events and resolve each reference (numeric via resolve_char_ref, named via resolve_predefined_entity). TRX refuses an unresolvable name; the sanctions readers keep it in literal &name; form so the anomaly stays visible in the record. trim_text(true) had to go on all four readers too — trimming is per text event, so once a run is split the trim eats the spaces around the reference ("Smith & Co" → "Smith&Co"). The assembled value is trimmed once where it is consumed.

The fixtures contained no entity anywhere, which is exactly why a silent corruption bug could pass CI. Added, failing first: a sanctioned name with & and one with & (a named predefine and a numeric reference take different code paths), TRX entity references asserted against hand-written XML rather than our own writer's escaping, and a refusal for an unresolvable name.

CI now gates on cargo-audit. The 142 instances this branch started from were not a decision — they accumulated because nothing was looking. scripts/check-advisories.py runs cargo audit --json over every tracked lockfile and fails on any advisory not named in its ALLOW table with a reason. Two rules keep that honest: an unlisted advisory fails, and a listed advisory that no longer appears also fails, so an excuse cannot outlive the exposure. Warnings (67, maintenance advice) are counted and printed but not gated. A lockfile that cannot be audited fails under its own heading — an error is never read as "clean", which is the exact mistake this sweep made by hand when a broken output parser reported "0 vulnerabilities" while criticals were printing. The self-test proves all four behaviours.

hpke-rs 0.2.0 is gone. Rather than leave it excused as dev-only, the joule-code-collab dev-deps were pinned to the generation joule-moq-mls already runs (openmls 0.9.1 / 0.6.0). That removes hpke-rs 0.2.0, hpke-rs-crypto, hpke-rs-rust-crypto and openmls 0.6.0 outright, and with them RUSTSEC-2026-0069/-0070/-0071 (9.3)/-0072. One API change: 0.9 takes the crypto backend, not the provider, in export_secret. tests/mls_keying.rs passes.

Two corrections to what this PR said earlier

  • The openpay quick-xml path is not a live exposure. I recorded it as "NOT closed". open-payments-iso20022 1.0.10 declares quick-xml but never calls it — it ships serde-annotated ISO 20022 types (3 files, ~2.2k lines, no quick_xml reference anywhere), and the parsing is ours in op-iso20022's codec, which binds the workspace 0.41. The old copy is compiled and never executed.
  • wai-quantum-desktop fails to compile on unmodified origin/main, not from these bumps: tauri::generate_context!() validates frontendDist at compile time and ../quantum-studio/dist does not exist. Confirmed identical at 72a8ddda5. It is the only one of 23 workspaces that fails to check, which means CI does not build that binary — a separate gap, untouched here.

Advisory count: 142 → 36 → 32 instances, 14 allowed and documented.

`cargo audit` over all 47 workspace lockfiles reported **142 vulnerability instances**. This closes the reachable ones and records, with evidence, why each survivor survives. ### The one that mattered The S2 (WASM) executor was pinned to **wasmtime 43.0.2**, carrying eight advisories: | ID | Sev | What | |---|---|---| | RUSTSEC-2026-0379 | **9.9** | Cranelift **aarch64** can jump to an incorrect target on a large `br_table` | | RUSTSEC-2026-0327 | **9.3** | async-lifted callback result count unvalidated → native stack buffer overflow | | RUSTSEC-2026-0269 | **8.8** | **Filesystem sandbox escape** via trailing slashes in paths/symlinks | | RUSTSEC-2026-0316 | 1.0 | Dynamic record lifting allocates **beyond the hostcall fuel limit** | Three of these land squarely on the confinement claim rather than beside it: `-0269` is a sandbox escape in the component whose only job is confinement, `-0316` defeats the fuel metering the S2 tier attests, and `-0379` is an aarch64 miscompile — the entire fleet is Apple Silicon, so that is the fleet's actual codegen path, not a remote arch. Moved to **49.0.4**, the single version every one of the eight names as patched. 50.0.0 exists but no advisory lists it as fixed, so it is not yet the safe choice. `WASMTIME_VERSION` moves with it: the constant is attested into the mechanism commitment under SPEC §7.9, so a stale value would attest confinement to a runtime that did not run. The crate's own `wasmtime_version_matches_the_lockfile` test enforces the pairing. **Verified:** builds clean and all **17 tests pass**, including every confinement test (fuel exhaustion, egress allowlist, storage globs, taint propagation) — no API changes were needed across the six majors. ### Reachable DoS on a payment path `quick-xml` **RUSTSEC-2026-0194** (quadratic run time checking a start tag for duplicate attributes) is reachable: `openpay/crates/op-iso20022/src/codec.rs` calls `quick_xml::de::from_str` on ISO 20022 messages, which arrive from payment counterparties. Attacker-shaped XML is the normal case there. Bumped 0.36 → 0.41.0 in the four lockfiles where the version is ours to choose. `-0195` (unbounded namespace allocation in `NsReader`) is **not** reachable — `NsReader` appears nowhere in the tree. ### The advisory with no fix `rsa` **RUSTSEC-2023-0071** (Marvin timing sidechannel) has no fixed version. It needed neither mitigation nor risk acceptance: the dependency was **unused**. `rsa::`, `use rsa` and `extern crate rsa` appear in none of op-3ds2's 20 source files nor anywhere in openpay. The declaration described an intent never implemented; only P-256 ECDH/ECDSA is actually present. Dropped, removing `rsa` and `pkcs1` from the graph. ### Semver-compatible bumps, 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 (`-0340`/`-0343`, both 7.5) · tokio 1.54.1 (`-0384`, `Interval` race) · crossbeam-epoch 0.9.20 · rtrb 0.3.5 ### Not fixed, deliberately, with reasons - **`zip` (9.1 `-0369`, 7.3 `-0367`, `-0368`) — not reachable.** All three are `extract()`-only (SUID/SGID preservation, symlink escape, permissions through symlinks). We never extract an archive: there are no `ZipArchive`/`ZipStreamReader` call sites. It arrives transitively via `candle-core`. - **`hpke-rs` 0.2.0 (9.3 `-0071`, 8.2 `-0070`, `-0069`) — never shipped.** The production MLS path (`joule-moq-mls`) already resolves the **patched 0.7.0**. The vulnerable 0.2.0 is reached only through a `[dev-dependencies]` edge of `joule-code-collab`, which still pins the older openmls 0.6/0.3 stack. Worth aligning to the 0.9.1/0.6.0 stack already used in-repo, but it is test-graph only. - **`libcrux-kem` 0.0.9 (8.7 `-0331`, 5.6 `-0330`) — blocked upstream.** `hpke-rs-libcrux =0.7.0` constrains it, so 0.0.10 needs an openmls/hpke-rs release. Both are panics (DoS), not memory corruption. - **`quick-xml` 0.37.5 in openpay — blocked upstream, and this one is NOT closed.** It resolves through `open-payments-iso20022 v1.0.10`, the same ISO 20022 surface. This PR hardens our own deserializer but not that library's internal parsing. - **`quick-xml` 0.39.4 in smart-byte — lower exposure.** Via plist → netdev → netwatch → iroh; parses macOS plists, not counterparty XML. ### Separately checked: the current PHP and Rust headlines - **PHP CVE-2026-109618 / -109619** (CVSS 9.2, unauth RCE): **not applicable.** No PHP interpreter on any reachable host; no `php_fastcgi`/`fastcgi` directive in the Caddyfile (canonical and live agree, 3492 lines); **0 `.php` files across all 313 deployed site directories**. The sites are static Astro builds behind `file_server`. - **crates.io supply-chain compromise (CVE-2026-77649 / RUSTSEC-2026-0266, -0260):** **not exposed**, three ways — the malicious `proc-macro1` was never fetched into any cargo registry cache on dev or the build host; `arrayref` resolves to 0.3.9 everywhere (only 0.3.10 was compromised); `internment` to 0.8.6 (only 0.8.7). Zero compromised pins across all 13,241 lockfiles in the tree. - Toolchain is rustc/cargo 1.98.0, so CVE-2026-5222 (1.68–1.96) and CVE-2026-5223 (fixed 1.96.0) are both already patched. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- ## Update — follow-up work on this branch **The quick-xml bump turned out to hide a silent data-corruption bug**, which is the most important thing found after the first pass. 0.41 does not merely rename `BytesText::unescape`: it reports every entity reference (`&`, `'`, `&`) 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: - `op-screening` parses the OFAC/EU/UN sanctions lists. An entry published as "Smith & Co" was stored as "Smith Co" — a name that no longer matches the party it names. A screening miss reads exactly like a clean screen. - `jouleclaw-transform-trx` carries JSON through TRX XML; dropping the references turned `{"k":1}` into `{k:1}`, which fails to parse and silently falls back to a bare string. Both readers now accumulate an element's character data across events and resolve each reference (numeric via `resolve_char_ref`, named via `resolve_predefined_entity`). TRX refuses an unresolvable name; the sanctions readers keep it in literal `&name;` form so the anomaly stays visible in the record. `trim_text(true)` had to go on all four readers too — trimming is per text event, so once a run is split the trim eats the spaces around the reference ("Smith & Co" → "Smith&Co"). The assembled value is trimmed once where it is consumed. **The fixtures contained no entity anywhere**, which is exactly why a silent corruption bug could pass CI. Added, failing first: a sanctioned name with `&` and one with `&` (a named predefine and a numeric reference take different code paths), TRX entity references asserted against hand-written XML rather than our own writer's escaping, and a refusal for an unresolvable name. **CI now gates on cargo-audit.** The 142 instances this branch started from were not a decision — they accumulated because nothing was looking. `scripts/check-advisories.py` runs `cargo audit --json` over every tracked lockfile and fails on any advisory not named in its ALLOW table with a reason. Two rules keep that honest: an unlisted advisory fails, and **a listed advisory that no longer appears also fails**, so an excuse cannot outlive the exposure. Warnings (67, maintenance advice) are counted and printed but not gated. A lockfile that cannot be audited fails under its own heading — an error is never read as "clean", which is the exact mistake this sweep made by hand when a broken output parser reported "0 vulnerabilities" while criticals were printing. The self-test proves all four behaviours. **hpke-rs 0.2.0 is gone.** Rather than leave it excused as dev-only, the `joule-code-collab` dev-deps were pinned to the generation `joule-moq-mls` already runs (openmls 0.9.1 / 0.6.0). That removes hpke-rs 0.2.0, hpke-rs-crypto, hpke-rs-rust-crypto and openmls 0.6.0 outright, and with them RUSTSEC-2026-0069/-0070/-0071 (9.3)/-0072. One API change: 0.9 takes the crypto backend, not the provider, in `export_secret`. `tests/mls_keying.rs` passes. ### Two corrections to what this PR said earlier - **The openpay quick-xml path is not a live exposure.** I recorded it as "NOT closed". `open-payments-iso20022 1.0.10` declares quick-xml but never calls it — it ships serde-annotated ISO 20022 types (3 files, ~2.2k lines, no `quick_xml` reference anywhere), and the parsing is ours in `op-iso20022`'s codec, which binds the workspace 0.41. The old copy is compiled and never executed. - **`wai-quantum-desktop` fails to compile on unmodified `origin/main`**, not from these bumps: `tauri::generate_context!()` validates `frontendDist` at compile time and `../quantum-studio/dist` does not exist. Confirmed identical at 72a8ddda5. It is the only one of 23 workspaces that fails to check, which means CI does not build that binary — a separate gap, untouched here. Advisory count: **142 → 36 → 32 instances, 14 allowed and documented.**
`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]>
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]>
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
f42cb4fae1
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]>
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]>
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
4e7fa646ed
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]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!443
No description provided.