Run cargo metadata with a pinned format version. It prints machine readable JSON describing the workspace, its packages and the resolved dependency graph. Add the locked option so it uses Cargo.lock as is. That output is a solid starting point for an inventory, an audit script or an SBOM.
What the output contains
The Cargo manual describes cargo metadata as outputting JSON about the current package. It includes all feature enabled dependencies unless the no deps flag is used, and the no deps output leaves the resolve data null. The manual recommends including the format version flag to future proof your code.
You get package names, versions, sources, features, targets and a resolved graph of who depends on whom.
Flags worth knowing
| Option | What it does |
|---|---|
| format version | Pins the JSON schema so scripts do not break |
| no deps | Only workspace members, no resolved graph |
| locked | Requires Cargo.lock to be up to date, errors otherwise |
| offline | Does not touch the network |
| frozen | Equivalent to locked plus offline, per the manual |
A simple recipe
cargo metadata --format-version 1 --locked > metadata.json
From there, a short script can list each package with its version and source. The source field tells you whether a package came from a registry, a git repository or a local path. Anything that is not the registry you expect deserves a look, which links to blocking unknown registries and git dependencies with cargo deny.
Questions an inventory answers
- Which crates are in the build, including transitive ones?
- Are two versions of the same crate present? See cargo deny bans.
- Which packages come from git or a path?
- Which features are on for a given crate?
Limits to keep in mind
The metadata describes the graph for a given set of features and targets, so a different feature set can give a different list. It does not know what is compiled into a final binary, or about native libraries that a build script links. For those see what a -sys crate means for security and scanning a compiled Rust binary.
It also lists what is declared, not whether a vulnerable function is reachable. Treat the inventory as the first step, then add advisory data and reachability.
Where Safeguard fits
Safeguard's dependency scanning and SBOM generation produce a Rust inventory from your manifests without a custom script. You can try it with the free scan at gold.safeguard.sh/scan.
Try it yourself
Click through it at your own pace, or open it full screen.
Open the inventory and compare direct and transitive entries, the same split cargo metadata gives you.
Quick check
Does cargo metadata need the network?
Not if you use the offline option and the packages are already available. The frozen option combines locked and offline.
Why pass a format version?
The manual recommends it so your code keeps working if the JSON format changes.
Sources
Last reviewed: 19 October 2026.