Skip to content

cargo-deny: unsafe-libyaml unmaintained (RUSTSEC-2024-0449) — interim ignore, then serde_yaml swap #5205

Open

Description

@noahgift

What

cargo deny check advisories fails on the merge path (guard-cargo m70/m71) since the advisory db picked up RUSTSEC-2024-0449: unsafe-libyaml 0.2.11 is unmaintained (repo archived 2024-03-25; no patched version exists).

It enters the lock three ways:

  • serde_yaml 0.9.34+deprecated: workspace dep (Cargo.toml serde_yaml = "0.9"), direct in 13 crates; 861 references in 179 .rs files.
  • serde_yaml_ng 0.10.0: workspace dep, direct in 15 crates (apr-cli aliases it as serde_yaml); 164 references in 49 files.
  • bashrs 6.66.1 (registry; also 7.4.2, as a build dependency): declared by aprender-compute-xtask and as the optional transpilers feature of aprender-orchestrate. Neither crate calls into it.

Interim

One deny.toml ignore entry for RUSTSEC-2024-0449, same form as the existing unmaintained-transitive entries, so every other advisory still blocks.

Close condition

Delete the ignore entry after the serde-saphyr swap and a bashrs release without serde_yaml_ng (or dropping the unused bashrs deps) leave unsafe-libyaml out of Cargo.lock. This issue closes when deny.toml no longer names RUSTSEC-2024-0449 and cargo deny check advisories is green on main.

Staged swap continues under this ticket, one PR per crate group.

Activity

  1. noahgift commented on Oct 12, 2026

    @noahgift
    ContributorAuthor

    Notes for the swap. All figures were read from main 8c12fca; nothing here is built yet.

    • bashrs is never called. aprender-compute-xtask declares bashrs = "6.35", and nothing in its src/ uses it. In aprender-orchestrate it is only the optional transpilers = ["bashrs", "native"] feature; the one bashrs:: occurrence there is inside a string template (oracle/knowledge_graph/integrations.rs:135), and no cfg(feature = "transpilers") exists. Dropping both deps removes the bashrs path without waiting for an upstream release. bashrs 7.4.2 still has serde_yaml_ng ^0.10 as a build dependency.
    • A cheaper candidate than serde-saphyr: serde_norway 0.9.42, a serde_yaml 0.9 fork on unsafe-libyaml-norway (no RustSec advisory today). It is API-compatible with serde_yaml 0.9 (Value, Mapping, from_str, to_string), so a dependency-level rename (serde_yaml = { package = "serde_norway", ... }) needs no .rs edits for the serde_yaml crates. serde-saphyr has no Value type, and 92 files use serde_yaml{,_ng}::Value. Risk: both norway crates were last released 2024-12, so they could draw the same advisory later. Measure it before choosing.
    • Declaration sites to rewrite: 2 workspace entries plus about 36 per-crate lines, several of which pin "0.9" or "0.10" directly instead of using workspace = true.
  2. noahgift commented on Oct 12, 2026

    @noahgift
    ContributorAuthor

    Swap plan: serde_yaml / serde_yaml_ng → serde-saphyr

    Measured size (main 8c12fca, 2026-10-12 12:05Z)

    git grep counts of serde_yaml:: / serde_yaml_ng:: paths per crate, plus the files that use Value/Mapping:

    Group Crates (refs; Value files) Refs Value files
    S1 bashrs deps dropped (xtask, orchestrate transpilers); compute 1, db 2, rag 1, viz-ttop 1, present-cli 1, present-core 2, present-terminal 3, qa-cli 2, qa-report 1, test 1, train-distill 3, data 2;1, mcp 6;1, cgp 6;1, facades 1, root tests 2, qa-gen 8, test-cli 6, contracts-staging 7 56 4
    S2 present 7;2, present-yaml 31;4, test-lib 27;2, serve 17;2, qa-runner 30;1, core 18;6 130 17
    S3 decide 30;6, simulate 85;8, orchestrate 65;11 180 25
    S4 apr-cli 169;8, contracts-cli 16;6, train 199;2 384 16
    S5 contracts 357;34; delete the RUSTSEC-2024-0449 entry from deny.toml 357 34

    Total: about 1,107 path references and 96 Value/Mapping files.

    What makes it more than a rename

    serde-saphyr has no Value or Mapping type. Untyped YAML goes into serde_json::Value instead. Every Value site has to be checked for things serde_json cannot represent: non-string mapping keys, YAML tags, !!binary, and key-order assumptions (serde_json needs preserve_order wherever order is printed or hashed). The error types also change (serde_yaml::Error becomes serde_saphyr DeserializeError/SerializeError), and so does serialized output formatting. Any test that compares emitted YAML byte for byte has to be re-baselined, and each re-baseline is reviewed. S1 to S3 are mostly typed from_str/to_string. S4 and S5 hold most of the Value sites.

    Owners

    One owner per stage, and each owner opens a child issue under #5205 for that stage. S1 and S5 (contracts, plus deleting the deny.toml entry) stay with this issue's owner, who also holds the merge order. S2, S3 and S4 each have their own owner, tracked on that stage's child issue. Every stage carries the same proof: old = new on the real YAML corpus (all three parsers into serde_json::Value, with a planted diff that must turn the check red). S5 also proves that pv parse output is identical across all of contracts/.

    Parser options, required at every serde-saphyr call in every stage:

    serde_saphyr::options! { strict_booleans: true, merge_keys: serde_saphyr::MergeKeyPolicy::AsOrdinary, emit_comments: false }

    serde-saphyr defaults to YAML 1.1 inference: no/n parse as false and << merges are expanded. serde_yaml 0.9 read both literally, and S1's old = new run caught 20 files that changed. serde-saphyr also keeps comments as budgeted events, which fails .github/workflows/ci.yml ("too many consecutive comments"); the old parsers discard comments, hence emit_comments: false. Over the whole tree, serde-saphyr also rejects 6 files that the old parsers accept: docs/roadmaps/entries/PMAT-3699.yaml, -3731, -3732, -3740, -3741 and the aggregated docs/roadmaps/roadmap.yaml. No Rust code in the workspace parses them (git grep finds only a path string in a test; make roadmap-aggregate builds the aggregate outside the crates), so no stage reads them through serde-saphyr. They are recorded here, not fixed in this swap. The details are in the S1 result comment below. In S5, a value that still changes (leading-zero 048 becoming 48.0, 5_000_000 becoming an integer) gets a quoted YAML literal or a typed field. A diff is never ignored.

    PRs and dates

    One PR per group, in order. Each PR goes through CI, two seats and the queue, with cargo deny check advisories run on its head.

    Dated finish: 2026-10-17. A red on main or a queue stall moves the date, and each move gets a cause line here.

    bashrs

    bashrs 7.4.2 still has serde_yaml_ng ^0.10 as a build dependency. In this repo, neither crate that declares bashrs calls it, so S1 drops both declarations, which takes bashrs out of aprender's lock without waiting on upstream. Separately, a bashrs PR will replace its serde_yaml_ng build dependency so the next bashrs release is clean for its other consumers. That PR goes through bashrs's own owner and is not on aprender's critical path.

  3. noahgift commented on Oct 12, 2026

    @noahgift
    ContributorAuthor

    S1 result: old = new needs two serde-saphyr options, and every stage uses them.

    I parsed all 2,881 YAML files under contracts/ and crates/ with serde_yaml 0.9, serde_yaml_ng 0.10 and serde-saphyr 1.3, each into serde_json::Value. A planted change fires as expected. With default options, 20 files differ, because serde-saphyr defaults to YAML 1.1 inference:

    • no and n become false (compute-parity-v1.yaml tolerance_table.dot_product.n, apr-format-leaf-sovereignty-v1.yaml formal: no).
    • << merge keys are expanded (both docker-compose.test.yml files and a pv fixture).

    Every call now passes:

    serde_saphyr::options! { strict_booleans: true, merge_keys: serde_saphyr::MergeKeyPolicy::AsOrdinary }

    With those options, 13 files still differ, and all of them are in contracts/ or its staging copy:

    • a leading-zero kaizen: 048 parses as 48.0 where it used to be the string "048";
    • max_files: 5_000_000 parses as an integer where it used to be a string.

    No option restores the old reading. These files are S5's scope. S5 settles them with its pv parse-output identity check, by quoting the values or by typed fields.

  4. added this to the 0.71.0 milestone on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions