Rust developers often assume that if their code compiles, it is safe. The borrow checker prevents memory safety bugs, so what else is there to worry about? Quite a lot, actually. Logic errors, insecure API usage, cryptographic mistakes, and unsafe code patterns all compile perfectly fine. That is where Clippy comes in.
Clippy is Rust's official linting tool, shipping with every Rust toolchain via rustup. It has over 700 lints organized into categories. Several of those lints have direct security implications.
Security-Relevant Lint Categories
clippy::correctness lints catch bugs that are almost certainly wrong. These are deny-by-default, meaning they cause errors rather than warnings. From a security perspective, correctness bugs in input validation or authentication logic are exploitable.
clippy::suspicious lints flag code that is technically valid but probably not what the developer intended. Suspicious patterns in security-critical code paths often indicate a misunderstanding that leads to a vulnerability.
clippy::pedantic lints enforce stricter coding standards. While not all are security-relevant, several catch patterns that can lead to security issues in edge cases.
Key Security Lints
transmute_ptr_to_ref catches uses of std::mem::transmute to convert raw pointers to references. This is inherently unsafe and can create invalid references that violate Rust's aliasing rules. While this requires unsafe, it is a common mistake in FFI code.
cast_ptr_alignment warns when casting a pointer to a type with stricter alignment requirements. Misaligned pointer access is undefined behavior and can cause crashes or memory corruption on architectures that enforce alignment.
integer_arithmetic (from clippy::restriction) flags all integer arithmetic operations that could overflow. While Rust panics on overflow in debug mode, release mode wraps silently. Integer overflows in size calculations can lead to buffer overflows when the result is used for memory allocation.
indexing_slicing (from clippy::restriction) flags array/slice indexing that could panic. In server applications, a panic from an out-of-bounds index can be a denial-of-service vector.
unwrap_used and expect_used flag calls to .unwrap() and .expect() on Option and Result types. In production code, especially code handling untrusted input, unwrap panics are denial-of-service vulnerabilities.
lossy_conversions catches numeric type conversions that can lose data. A u64 to u32 conversion that silently truncates could turn a large allocation size into a small one, leading to a buffer overflow.
Unsafe Code Lints
Clippy has several lints specifically for unsafe code blocks:
undocumented_unsafe_blocks requires a safety comment before every unsafe block explaining why the unsafe code is sound. This does not prevent bugs, but it forces the developer to articulate their safety argument, which often reveals flawed assumptions.
unsafe_derive_deserialize warns when Deserialize is derived on a type that has unsafe invariants. Deserialization creates instances of a type from untrusted data, and if the type has invariants that must be maintained for safety, the deserializer might violate them.
ptr_as_ptr suggests using pointer::cast() instead of as casts for pointer type changes. The cast() method is less error-prone because it does not silently allow changes between pointer and integer types.
Configuration
Configure Clippy in your clippy.toml or Cargo.toml:
[lints.clippy]
unwrap_used = "deny"
expect_used = "warn"
indexing_slicing = "warn"
integer_arithmetic = "warn"
undocumented_unsafe_blocks = "deny"
For security-critical projects, consider enabling the clippy::restriction group selectively. The entire group is too noisy for most projects, but individual lints like unwrap_used and indexing_slicing provide high-value security checks.
CI Integration
Run Clippy in CI with specific lint levels that match your security requirements:
cargo clippy -- -D clippy::correctness -D clippy::suspicious -W clippy::pedantic
The -D flag denies (errors on) the specified lint group. -W warns. This configuration fails the build on correctness and suspicious issues while reporting pedantic issues as warnings.
For projects with existing Clippy warnings, adopt incrementally. Start by denying correctness lints (which are already deny-by-default), then progressively enable more groups.
Beyond Clippy
Clippy complements other Rust security tools:
cargo-audit checks your Cargo.lock against the RustSec Advisory Database for known vulnerabilities in dependencies. This covers supply chain risks that Clippy does not address.
cargo-deny provides dependency policy enforcement: license checking, duplicate dependency detection, and advisory database checking.
cargo-geiger measures the unsafe code footprint of your entire dependency tree.
Miri is an interpreter for Rust's Mid-level Intermediate Representation that can detect undefined behavior in unsafe code. It is slower than running tests normally but catches bugs that no static analysis can find.
The Ecosystem Gap
One notable gap is that Clippy does not have a comprehensive "security" lint group. Security-relevant lints are spread across multiple categories, and identifying them requires knowledge of which lints matter for security. The Rust security community has discussed creating a dedicated security lint set, but as of now, you need to curate your own list.
The cargo-careful tool fills part of this gap by enabling additional runtime checks that catch undefined behavior. Running your test suite with cargo careful test is a good complement to Clippy's static analysis.
How Safeguard.sh Helps
Safeguard.sh monitors your Rust project's dependency tree for vulnerabilities reported in the RustSec Advisory Database and other sources. While Clippy catches security anti-patterns in your own code, Safeguard.sh watches for security issues in the crates you depend on. Together, they provide coverage across both your code and your supply chain -- the full scope of what an attacker might target.