Rust's crates.io registry has grown past 150,000 published crates, and nearly every one that ends up in a production build resolves through a single generated file: Cargo.lock. When a team runs snyk test against a Rust project, or connects the repo to Snyk Open Source for continuous monitoring, the scanner doesn't guess at what's actually compiled into the binary -- it reads the exact, checksum-pinned dependency graph that Cargo itself already resolved. That distinction matters because Cargo.toml only declares version ranges (serde = "1.0"), and two builds of the same manifest, run months apart, can legally pull different patch releases. This post walks through, mechanically, how Snyk Open Source parses Cargo.lock, cross-references it against Rust-specific vulnerability sources, and surfaces findings -- based on Snyk's publicly documented behavior, not unpublished internals.
How does Snyk Open Source read a Rust project's dependency tree?
Snyk reads the resolved graph rather than the declared one, because Cargo.lock is what actually shipped. The lockfile is a TOML document made up of repeated [[package]] tables, each carrying a crate's name, exact version, source (typically registry+https://github.com/rust-lang/crates.io-index), a checksum, and a dependencies array listing the other locked packages it depends on. That structure is already a flattened dependency graph -- Cargo did the SAT-style version resolution once, at cargo build or cargo update time, and wrote the result down. Snyk's Rust integration parses this file (and, per Snyk's documentation, expects the Cargo toolchain to be present locally so it can invoke cargo metadata for workspace and manifest context rather than relying on static TOML parsing alone) to reconstruct which crate versions are present and how they connect. Since Rust moved to lockfile format version = 3 by default in Rust 1.53 (2021) and introduced format version = 4 in Rust 1.78 (May 2024) for smaller merge-conflict footprints, Snyk's parser has to tolerate both encodings to keep working across projects pinned to different toolchains.
Where does Snyk's Rust vulnerability intelligence come from?
Snyk's Rust coverage leans heavily on the RustSec Advisory Database, the community-maintained vulnerability feed that also powers cargo audit. RustSec was founded in 2016 by the Rust Secure Code working group and, by the early 2020s, had catalogued several hundred advisories covering both memory-safety CVEs and Rust-specific "unsoundness" issues -- API designs that can be misused to trigger undefined behavior even without a classic CVE. Snyk's Intel Vulnerability Database ingests RustSec entries alongside its own security research team's findings and public CVE/NVD records, then normalizes them into Snyk's internal schema with severity scoring, affected version ranges, and remediation notes. This matters for Rust specifically because a meaningful share of RustSec entries are "unsound" or "unmaintained crate" advisories rather than traditional CVEs, so a scanner that only queries NVD would miss a large slice of what the Rust community itself flags as risky.
How does Snyk match a locked crate version to a known advisory?
Matching happens by comparing the exact version pinned in Cargo.lock against the affected-version ranges published in each advisory, the same semver-range comparison model Cargo itself uses for dependency resolution. Because Cargo.lock records a single resolved version per crate (e.g., time 0.1.45) rather than a range, Snyk can do a direct, deterministic lookup: is 0.1.45 inside the vulnerable range for RUSTSEC-2020-0071 (the time crate's segfault issue affecting versions prior to 0.2.23)? If yes, it's flagged; if the lockfile already shows 0.2.23 or later, it isn't. This is a meaningful advantage over manifest-only scanning: two repos with an identical Cargo.toml line can have different Cargo.lock results, and only the lockfile tells you which one is actually exposed. Snyk surfaces this in its output as the specific vulnerable version found, the fixed version(s) available, and the advisory identifier(s) it matched against.
Does Snyk account for Cargo workspaces and multiple lockfiles?
Yes -- Snyk's Rust support is built around workspaces because a large share of real-world Rust codebases use them. A Cargo workspace shares one top-level Cargo.lock across multiple member crates, and Snyk's CLI documentation describes scanning at the manifest/workspace level with Cargo installed so it can resolve member crates and their individual Cargo.toml metadata correctly rather than treating the workspace as one flat package. For monorepos with several independent Rust projects (each with its own Cargo.lock), Snyk scans each lockfile as a separate target, which is why teams with many Rust services typically see one Snyk "project" per lockfile in their dashboard rather than a single merged view.
What remediation guidance does Snyk give for a vulnerable crate?
Snyk reports the minimum fixed version for each flagged crate and, where the fix is a direct dependency, suggests the version bump; Rust's ecosystem doesn't have an equivalent to npm's automatic lockfile-patching pull requests, so remediation in practice usually means running cargo update -p <crate> --precise <version> or bumping the Cargo.toml constraint and re-running cargo generate-lockfile. When the vulnerable crate is transitive -- pulled in by a direct dependency rather than declared directly -- Snyk shows the dependency path so the team can see which top-level crate needs to be upgraded (or which upstream maintainer needs to bump their own dependency) to pull the fix through. For advisories where no fixed version exists yet, or the crate is marked "unmaintained" in RustSec, Snyk reports the finding without a version-bump fix, since none is available upstream.
What are the limits of lockfile-based Rust scanning?
The main limit is that Cargo.lock tells you what's resolved, not what's reachable -- it can't tell you whether the vulnerable function inside a flagged crate is ever actually called by your code. Rust's unsafe blocks, conditional compilation via cfg() attributes, and optional dependencies gated behind Cargo features can all mean a listed dependency compiles into the binary in name only, without its vulnerable code path ever executing. Snyk's public documentation does not describe function-level reachability analysis or call-graph tracing for Rust the way some tooling offers for JVM or JavaScript ecosystems, so severity scoring for Rust findings is based on the advisory's stated impact rather than confirmed exploitability in your specific build. Scans also depend on the lockfile being present and current -- a repo committed without Cargo.lock, or one where it's stale relative to Cargo.toml, will produce results that don't match what actually got built.
How Safeguard Helps
Cargo.lock-based scanning like Snyk's is a solid first layer: it tells you precisely which crate versions are locked into a build and whether any match a known advisory. Safeguard is built to extend past that layer for teams that need to know not just what's in the dependency graph, but where it came from and whether it can be trusted. Safeguard continuously monitors the provenance of crates and other open-source packages pulled into your builds -- flagging registry takeovers, maintainer account changes, and anomalous release patterns that predate a CVE ever being filed against RustSec or NVD. For organizations running Rust services alongside npm, PyPI, Maven, and container images, Safeguard correlates supply-chain signals across all of those ecosystems in one place, so a compromised build pipeline or a suspicious dependency update doesn't get lost between separate point tools. Pairing lockfile vulnerability matching with upstream provenance monitoring closes the gap between "this version has a known CVE" and "this dependency's publishing chain itself looks compromised" -- the two questions a mature software supply chain security program has to answer together.