Rust makes it easy to add dependencies to your project: edit your Cargo.toml
(or use cargo add
to have it changed for you from the command line), and you get to use the crate. But do you know what you’ve added to your project? Are you sure you can trust those lines of code?
Of course, some developers won’t bother to find out. After all, if a crate is in wide use, what are the chances it’s doing something fishy? As it turns out, there’s ample precedent for that.
In the JavaScript world, popular npm libraries can be hijacked from the original maintainer and subverted for presumably criminal purposes. Nowadays, researchers count the number of compromised or otherwise malicious packages in the thousands.
Even when there’s no ill will, libraries can contain weaknesses — the recent Log4Shell debacle gave many Java system maintainers sleepless nights.
But what can we do as developers? Sticking our collective heads in the sand is a recipe for disaster. Checking your entire dependency with your own eyes is too costly for all but the most security-sensitive projects.
In this post, we’ll look at tools that will give us a modicum of security for minimal effort, focus our attention where it is likely needed, or even amplify our review efforts for the improvement of our community’s supply chain security.
Jump ahead:
cargo outdated
and cargo duplicates
cargo-audit is built by the Rust Secure Code working group and is the canonical end-user interface to their RustSec Advisory Database. You can install it with cargo install cargo-audit
.
Once installed, you can run it by starting it from the base of your cargo projects by calling cargo audit
. It will look into your Cargo.toml
and scan the RustSec database for all your dependencies, printing all advisories it finds.
If you care about both your and your user’s security (and you should!), this is the absolute minimum amount of effort you can expend to ensure at least no known problems lurk in your dependency tree.
For example, here I run cargo audit
on my mutagen crate:
$ cargo audit Fetching advisory database from `https://github.com/RustSec/advisory-db.git` Loaded 394 security advisories (from /home/andre/.cargo/advisory-db) Updating crates.io index Scanning Cargo.lock for vulnerabilities (60 crate dependencies) Crate: failure Version: 0.1.8 Warning: unmaintained Title: failure is officially deprecated/unmaintained Date: 2020-05-02 ID: RUSTSEC-2020-0036 URL: https://rustsec.org/advisories/RUSTSEC-2020-0036 Dependency tree: failure 0.1.8 ├── mutagen-core 0.2.0 │ ├── mutagen-transform 0.2.0 │ │ └── mutagen 0.2.0 │ │ ├── mutagen-selftest 0.2.0 │ │ ├── feature-gated 0.2.0 │ │ ├── example-with-integration-tests-v2 0.2.0 │ │ ├── example-with-integration-tests-v1 0.2.0 │ │ └── example-simple 0.2.0 │ ├── mutagen-selftest 0.2.0 │ ├── mutagen 0.2.0 │ └── cargo-mutagen 0.2.1 └── cargo-mutagen 0.2.1
I should really switch to anyhow or something similar. Can someone please push a PR? ☺️
Built by Embark, cargo-deny goes a few steps further than cargo-audit and checks the sources and licenses of dependencies. Use it if you intend to publish your crate with the intention of people using it — there’s nothing like an incompatible license to sour the day of a Rustacean!
On the other hand, cargo-deny needs some information about your crate, so, to use it, you need to configure it. Luckily, you don’t have to manually write anything — a simple cargo deny init
will do just that for you.
Once you’ve run that, you’ll find a fresh new file called deny.toml
that you can edit to configure and customize your checks. You can read more about this in the GitHub repo.
(Interestingly, cargo-deny showed me I lost all license information on all Cargo.toml
files at some point.)
Once correctly configured, you’ll get a reassuring one-line result (depending on your configuration, as some checks may be missing in your output):
advisories ok, bans ok, licenses ok, sources ok
However, this assumes that your dependencies are up to date, otherwise, you may get a bunch of wordy warnings. In my compact_arena
crate, for example, I used an older version of compiletest, which had a few dependencies in search of a maintainer, so I received some warnings like:
warning[A003]: term is looking for a new maintainer ┌─ /home/andre/projects/compact_arena/Cargo.lock:33:1 │ 33 │ term 0.6.1 registry+https://github.com/rust-lang/crates.io-index │ ---------------------------------------------------------------- unmaintained advisory detected │ = ID: RUSTSEC-2018-0015 = Advisory: https://rustsec.org/advisories/RUSTSEC-2018-0015 = The author of the `term` crate does not have time to maintain it and is looking for a new maintainer. Some maintained alternatives you can switch to instead, depending on your needs: - \[`crossterm`\](https://github.com/crossterm-rs/crossterm) - \[`termcolor`\](https://crates.io/crates/termcolor) - \[`yansi`\](https://crates.io/crates/yansi) = Announcement: https://github.com/Stebalien/term/issues/93 = Solution: No safe upgrade is available! = term v0.6.1 └── tester v0.7.0 └── compiletest_rs v0.5.0 └── (dev) compact_arena v0.4.1
Getting several pages of these warnings may be disheartening, but there are some tools to deal with this situation.
cargo outdated
and cargo duplicates
The canonical cargo command to update dependencies is cargo update
. Still, those automatically applied updates won’t change major versions, as they may be incompatible.
So, you can call cargo outdated
(after installing it with cargo install cargo-outdated
) to find out what dependencies can be updated even further.
On the latest version of flame
, for example, I get:
$ cargo outdated Name Project Compat Latest Kind Platform ---- ------- ------ ------ ---- -------- proc-macro2->unicode-xid 0.2.1 0.2.2 0.2.2 Normal --- quote->proc-macro2 1.0.24 1.0.36 1.0.36 Normal --- serde 1.0.118 1.0.136 1.0.136 Normal --- serde_derive 1.0.118 1.0.136 1.0.136 Normal --- serde_derive->proc-macro2 1.0.24 1.0.36 1.0.36 Normal --- serde_derive->quote 1.0.7 1.0.15 1.0.15 Normal --- serde_derive->syn 1.0.54 1.0.86 1.0.86 Normal --- serde_json 1.0.60 1.0.79 1.0.79 Normal --- serde_json->itoa 0.4.6 1.0.1 1.0.1 Normal --- serde_json->ryu 1.0.5 1.0.9 1.0.9 Normal --- serde_json->serde 1.0.118 1.0.136 1.0.136 Normal --- syn->proc-macro2 1.0.24 1.0.36 1.0.36 Normal --- syn->quote 1.0.7 1.0.15 1.0.15 Normal --- syn->unicode-xid 0.2.1 0.2.2 0.2.2 Normal --- thread-id->libc 0.2.81 0.2.119 0.2.119 Normal cfg(unix)
The latest version may be incompatible, so we may need to fix problems that arise from an update. There’s no single correct way to decide when to update, so you should always consider the costs of updating compared to the cost of keeping the current version.
This won’t solve our duplicate dependency problem, though. The reason for this problem is that cargo will often try to use the newest compatible version if possible.
However, as the dependency tree grows, so does the probability that a certain dependency appears multiple times — albeit with incompatible versions. Cargo’s solution is to simply build both versions and make them different name spaces.
If you use the type Foo
from foo-0.3.2
and have a dependency that uses a Foo
from foo-0.1.5
, those Foo
s won’t be compatible. Worse, compiling your project will pull in both versions and compile them separately.
Installing cargo duplicates
via cargo install cargo-duplicates
will allow you to call cargo duplicates
to at least find the parts of the dependency tree to either up- or downgrade. For example, on the current version of bytecount, I found two versions of cfg-if
:
Package Versions ------- -------- cfg-if 1.0.0 0.1.10 cfg-if 1.0.0: - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => rayon-core 1.9.0 => crossbeam-channel 0.5.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => crossbeam-deque 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => rayon-core 1.9.0 => crossbeam-deque 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => crossbeam-deque 0.8.0 => crossbeam-epoch 0.9.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => rayon-core 1.9.0 => crossbeam-deque 0.8.0 => crossbeam-epoch 0.9.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => rayon-core 1.9.0 => crossbeam-channel 0.5.0 => crossbeam-utils 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => crossbeam-deque 0.8.0 => crossbeam-utils 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => rayon-core 1.9.0 => crossbeam-deque 0.8.0 => crossbeam-utils 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => crossbeam-deque 0.8.0 => crossbeam-epoch 0.9.0 => crossbeam-utils 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => rayon-core 1.9.0 => crossbeam-deque 0.8.0 => crossbeam-epoch 0.9.0 => crossbeam-utils 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => rayon 1.5.0 => rayon-core 1.9.0 => crossbeam-utils 0.8.0 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => rand 0.8.4 => rand_core 0.6.3 => getrandom 0.2.3 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => quickcheck 1.0.3 => rand 0.8.4 => rand_core 0.6.3 => getrandom 0.2.3 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => rand 0.8.4 => rand_chacha 0.3.1 => rand_core 0.6.3 => getrandom 0.2.3 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => quickcheck 1.0.3 => rand 0.8.4 => rand_chacha 0.3.1 => rand_core 0.6.3 => getrandom 0.2.3 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => rand 0.8.4 => rand_hc 0.3.1 => rand_core 0.6.3 => getrandom 0.2.3 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => quickcheck 1.0.3 => rand 0.8.4 => rand_hc 0.3.1 => rand_core 0.6.3 => getrandom 0.2.3 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => js-sys 0.3.46 => wasm-bindgen 0.2.69 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => web-sys 0.3.46 => js-sys 0.3.46 => wasm-bindgen 0.2.69 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => wasm-bindgen 0.2.69 => cfg-if 1.0.0 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => web-sys 0.3.46 => wasm-bindgen 0.2.69 => cfg-if 1.0.0 cfg-if 0.1.10: - Because of bytecount 0.6.2 => quickcheck 1.0.3 => env_logger 0.8.4 => log 0.4.11 => cfg-if 0.1.10 - Because of bytecount 0.6.2 => quickcheck 1.0.3 => log 0.4.11 => cfg-if 0.1.10 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => js-sys 0.3.46 => wasm-bindgen 0.2.69 => wasm-bindgen-macro 0.2.69 => wasm-bindgen-macro-support 0.2.69 => wasm-bindgen-backend 0.2.69 => log 0.4.11 => cfg-if 0.1.10 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => web-sys 0.3.46 => js-sys 0.3.46 => wasm-bindgen 0.2.69 => wasm-bindgen-macro 0.2.69 => wasm-bindgen-macro-support 0.2.69 => wasm-bindgen-backend 0.2.69 => log 0.4.11 => cfg-if 0.1.10 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => wasm-bindgen 0.2.69 => wasm-bindgen-macro 0.2.69 => wasm-bindgen-macro-support 0.2.69 => wasm-bindgen-backend 0.2.69 => log 0.4.11 => cfg-if 0.1.10 - Because of bytecount 0.6.2 => criterion 0.3.3 => plotters 0.2.15 => web-sys 0.3.46 => wasm-bindgen 0.2.69 => wasm-bindgen-macro 0.2.69 => wasm-bindgen-macro-support 0.2.69 => wasm-bindgen-backend 0.2.69 => log 0.4.11 => cfg-if 0.1.10 - Because of bytecount 0.6.2 => packed_simd_2 0.3.4 => cfg-if 0.1.10
Someone should really fix this and make our build a tiny bit faster 🙂 .
Rust prides itself on safety. Its guarantees around memory safety are unmatched. However, it can only do that by also rejecting valid programs.
To be able to function as a systems language, it had to include a trap door to avoid those restrictions in cases where the programmer can ensure the safety of the code but cannot convince the compiler of that. That’s where unsafe
comes in (I’ve previously written about this topic).
When a Rust program fails to live up to the memory safety we require, the lines of code that must be audited are within modules that contain unsafe code. Yet not all of this code needs to be your own. You may have pulled in unsafe code throughout the dependency tree without even knowing it.
cargo-geiger — installed with cargo install cargo-geiger
— will give you an overview of all the packages you use that include unsafe code. For example, in flame
, I get the following:
Metric output format: x/y x = unsafe code used by the build y = total unsafe code found in the crate Symbols: :) = No `unsafe` usage found, declares #![forbid(unsafe_code)] ? = No `unsafe` usage found, missing #![forbid(unsafe_code)] ! = `unsafe` usage found Functions Expressions Impls Traits Methods Dependency 0/0 0/0 0/0 0/0 0/0 ? flame 0.2.1-pre 0/0 7/7 1/1 0/0 0/0 ! ├── lazy_static 1.4.0 0/0 0/0 0/0 0/0 0/0 :) ├── serde 1.0.118 0/0 0/0 0/0 0/0 0/0 ? │ └── serde_derive 1.0.118 0/0 0/0 0/0 0/0 0/0 ? │ ├── proc-macro2 1.0.24 0/0 0/0 0/0 0/0 0/0 :) │ │ └── unicode-xid 0.2.1 0/0 0/0 0/0 0/0 0/0 :) │ ├── quote 1.0.7 0/0 0/0 0/0 0/0 0/0 ? │ │ └── proc-macro2 1.0.24 0/0 45/45 3/3 0/0 2/2 ! │ └── syn 1.0.54 0/0 0/0 0/0 0/0 0/0 ? │ ├── proc-macro2 1.0.24 0/0 0/0 0/0 0/0 0/0 :) │ ├── quote 1.0.7 0/0 0/0 0/0 0/0 0/0 :) │ └── unicode-xid 0.2.1 0/0 0/0 0/0 0/0 0/0 ? ├── serde_derive 1.0.118 0/0 3/5 0/0 0/0 0/0 ! ├── serde_json 1.0.60 0/0 1/1 0/0 0/0 0/0 ! │ ├── itoa 0.4.6 8/12 674/921 0/0 0/0 2/2 ! │ ├── ryu 1.0.5 0/0 0/0 0/0 0/0 0/0 :) │ └── serde 1.0.118 0/0 4/4 0/0 0/0 0/0 ! └── thread-id 4.0.0 0/5 12/259 0/0 0/0 2/22 ! └── libc 0.2.81 8/17 746/1242 4/4 0/0 6/26
This is all nice and well, but at some point, someone is going to have to pull up their sleeves and perform a proper code review. Wouldn’t it be nice to share the load of reviewing code with others we can trust and that’s secured by cryptographic signatures so that we can trust that the reviewers are who they claim to be?
Look no further than cargo-crev, which allows us to create a digital fingerprint and directly start entering reviews. Here’s how to set it up:
$ cargo install cargo-crev $ # Note that hereby we trust the author of crev: $ cargo crev trust --level high https://github.com/dpc/crev-proofs $ cargo crev repo fetch all $ cargo crev id set-url <your proof repo> $ # as this exhorts you to set your password $ cargo crev id passwd
Regarding <your proof repo>
: you need to fork https://github.com/crev-dev/crev-proofs to set this up. If you have a GitHub account, you can go ahead and fork, otherwise, you can clone the repo and push it into whatever git service you use.
Now we are ready to review:
$ cargo crev open <crate> $ cargo crev review <crate>
I have no idea why that isn’t one command, but the first command will prepare things and the second will open your $EDITOR
with a review template that will contain a few fields. I’ve just submitted the review for my compact_arena
crate.
kind: package review version: -1 date: "2022-04-03T15:53:31.338910703+02:00" from: id-type: crev id: FJuAGAaomkJL8JJojZdS_jVpqHIW__7QSXcWSnjAB6U package: source: "https://crates.io"f name: compact_arena version: 0.4.1 revision: b66f8a2f000cd91ce18f67f4c9f9974fe6e19ded digest: 6_pE4YUU_SbeJmBUty7B8buurO68FNBNvR3j6iAn9Kk review: thoroughness: medium understanding: high rating: positive alternatives: - source: "https://crates.io" name: generational-arena comment: |- The idea behind this crate is that one can use small index types and use the lifetime tokens to ensure the validity of the indices, so that the actual indexing can be unchecked. Earlier versions had problems getting the compiler to check the lifetimes for all types, including those that did not implement Drop. For those, we implemented a Guard type that ensures the arena lifetime is actually observed. Basically the lifetime tokens bind the indices returned by *Arena::add to the arena. Neither through threads nor recursion can one mix up arenas, and as Arenas don't allow deleting items, the index lifetime can only live as long as the arena it refers to. I have done my due diligence and filed for a CVE as well as yanking all previous unsound versions.
Hopefully, that’ll set the record straight – this crate is safe to use! 😇
If you want to opt to trust my reviews, you can do so by typing cargo crev trust --level high https://github.com/llogiq/crev-proofs
into your terminal. Perhaps I’ll get around to reviewing more crates in the future.
Whether you want to learn about your dependency tree, look for unsafe code, reduce your build times (and implied attack surface), or help improve the state of code review for us all, you now have the tools at your disposal to do so. Go forth and stay safe! 👋
Debugging Rust applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking the performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Rust application. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Modernize how you debug your Rust apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
One Reply to "Comparing Rust supply chain safety tools"
The link “a popular npm library” appears to be invalid. It links to an article about testing, not about Javascript supply of chain attack.