How to Use cargo-geiger to Find Unsafe Code in Dependencies

Use `cargo-geiger` to scan your project's dependency tree for `unsafe` blocks, generating a report that identifies which crates contain unsafe code and how many times it appears.

When trust needs a signal

You merge a pull request that adds a new dependency. The build passes. The tests pass. The crate looks popular on crates.io. You trust the Rust ecosystem. Trust is good. Verification is better. You cannot read every line of every transitive dependency. You need a way to see where the safety guarantees end and the programmer's responsibility begins before you ship.

cargo-geiger gives you that signal. It scans the source code of your entire dependency tree and counts every occurrence of the unsafe keyword. It produces a report listing which crates contain unsafe code, how many times it appears, and exactly which files hold it. The tool does not analyze whether the code is sound. It does not prove correctness. It simply tells you where the radiation is. You decide if you are comfortable working in that zone.

The Geiger counter for Rust code

Rust enforces memory safety through the borrow checker and ownership rules. Safe code is guaranteed by the compiler to be free of data races, null pointer dereferences, and buffer overflows. That guarantee is the core value of the language.

Sometimes you need to step outside those rules. You might be calling a C library, implementing a custom allocator, or optimizing a hot path where the compiler cannot prove safety. In those cases, you use unsafe. The unsafe block tells the compiler, "I know what I am doing. Do not check me."

That power comes with risk. If the programmer makes a mistake inside an unsafe block, the compiler will not catch it. You get undefined behavior. The program might crash, corrupt memory, or silently produce wrong results.

cargo-geiger treats unsafe like a Geiger counter treats radiation. It detects the presence of the signal. It does not tell you if the signal is lethal. A crate with zero unsafe blocks is shielded. A crate with fifty unsafe blocks is a hot zone. The number alone is not the verdict. A crate with one unsafe block might be unsound. A crate with fifty might be perfectly safe because those blocks are well-audited wrappers around system calls. The tool turns "I hope this is safe" into "Here is exactly where the unsafe code lives, so I can decide if I trust it."

Minimal example

Install the tool and run a scan. The output shows you the unsafe surface area of your project.

# Install cargo-geiger globally. This downloads the binary and places it in your cargo bin directory.
# You only need to do this once per machine. The binary matches your Rust toolchain version.
cargo install cargo-geiger

# Run the scan in your project root. This reads Cargo.toml and parses the source of all dependencies.
# The output is a table with crate names, unsafe counts, and file paths.
cargo geiger

The output lists every dependency. Crates with zero unsafe blocks appear with a count of 0. Crates with unsafe code show the count and the files involved. If a file contains multiple unsafe blocks, the count reflects the total number of unsafe keywords found.

The tool scans source code, not compiled artifacts. It does not compile your project. It looks for the text unsafe in the Rust files of your dependencies. This means it works even if a dependency fails to compile, though a failing dependency usually indicates other problems.

Convention aside: The community installs tools like cargo-geiger via cargo install rather than system package managers. This ensures the tool version aligns with your Rust toolchain and avoids conflicts with system libraries. When sharing results in issues or pull requests, paste the text output or attach the JSON file. Screenshots of tables are hard to search and parse.

The number is a starting point for investigation, not a verdict.

What the count actually measures

Understanding what cargo-geiger counts helps you interpret the results. The tool counts every occurrence of the unsafe keyword. This includes:

  • unsafe blocks that allow dereferencing raw pointers, calling unsafe functions, or accessing mutable statics.
  • unsafe function declarations that require callers to wrap the call in an unsafe block.
  • unsafe trait implementations that mark a trait as requiring manual safety invariants.
  • unsafe impl blocks for traits like Send or Sync.

If a macro expands to code containing unsafe, the tool counts it. This is correct behavior. The unsafe code exists in the expanded output, even if the macro definition looks safe. The count might be higher than the number of explicit blocks you see in the source file.

A high count does not automatically mean a crate is dangerous. Low-level libraries often use unsafe extensively to wrap C code, manage memory, or interact with hardware. The key is whether the unsafe blocks are isolated behind a safe API. If a crate exposes unsafe functions to users, the risk propagates to your code. If a crate keeps unsafe internal and provides a safe interface, the risk is contained within the crate's implementation.

Convention aside: When reviewing a crate with unsafe code, check if the crate documents its safety invariants. Good crates include // SAFETY: comments that explain the preconditions for each unsafe block. If the comments are missing or vague, treat the crate with caution. Treat the SAFETY comment as a proof. If you can't write it, you don't have one.

Realistic example: filtering and CI integration

In a real project, you will encounter dependencies that use unsafe for legitimate reasons. You may have already audited certain crates and accept their risk profile. You can exclude those crates from the scan to focus on new or unknown dependencies.

# Exclude specific crates from the report. Use this for dependencies you have already audited.
# Common exclusions include low-level libraries like `ring` or `openssl` that wrap C code.
# The flag accepts multiple values. Exclude only what you have explicitly reviewed.
cargo geiger --exclude ring --exclude openssl

# Generate a JSON report for CI pipelines. This outputs structured data instead of a table.
# CI scripts can parse this to enforce policies, such as failing if a new dependency introduces unsafe code.
cargo geiger --json > unsafe_report.json

The --exclude flag removes specified crates from the output. This is useful when you have established dependencies that inherently require unsafe and you want to monitor only the rest of the tree. Do not exclude everything. If you exclude all crates with unsafe, the tool provides no value. Exclude only what you have reviewed and trust.

The --json flag produces machine-readable output. This is essential for continuous integration. You can write a script that parses the JSON and fails the build if the total unsafe count exceeds a threshold, or if a specific crate appears with unsafe code. This enforces a safety policy automatically.

{
  "unsafe_code_path": [
    {
      "name": "example-crate",
      "version": "1.0.0",
      "unsafe_count": 5,
      "unsafe_files": [
        "src/lib.rs",
        "src/ffi.rs"
      ]
    }
  ]
}

The JSON structure lists each crate with unsafe code, the version, the count, and the files. CI scripts can iterate over this list and apply rules. For example, you might allow unsafe in crates from a trusted registry but reject it in new crates.

Convention aside: In CI, the common pattern is to allow a baseline of unsafe for known dependencies and fail on regressions. This prevents new dependencies from introducing unsafe code without review. Some projects enforce a strict zero-unsafe policy for all dependencies. That approach works for pure application code but breaks for projects that require low-level libraries. Choose a policy that matches your threat model.

Counter-intuitive but true: the more you exclude, the less the tool protects you.

Pitfalls and limitations

cargo-geiger is a static analysis tool. It has limits. Understanding those limits prevents false confidence.

A zero count does not mean the crate is safe. A crate can have logic bugs, denial-of-service vulnerabilities, or incorrect API usage that causes crashes, all without a single unsafe block. cargo-geiger only measures the presence of unsafe, not the quality of the code. A crate with zero unsafe can still be buggy or insecure.

The tool does not analyze soundness. It counts unsafe keywords. It does not check if the unsafe code is correct. A crate with one unsafe block might have a soundness bug that allows memory corruption. A crate with fifty unsafe blocks might be perfectly sound because each block is carefully audited. The number alone never proves safety.

Dev dependencies are scanned by default. If your test suite uses crates with unsafe code, the report will include them. This can inflate the count and distract from runtime dependencies. Check the output carefully to distinguish between runtime and dev dependencies. You can exclude dev dependencies manually if they are not relevant to your safety audit.

The tool does not check for known vulnerabilities. It does not look up CVEs or security advisories. It only scans for unsafe code. A crate might have a known vulnerability that does not involve unsafe, such as a logic flaw or a dependency on a vulnerable sub-crate. You need a different tool for that.

Convention aside: When reporting issues about unsafe code, include the cargo-geiger output to show the context. Maintainers appreciate concrete data. If you find a potential soundness bug, file an issue with the specific unsafe block and explain why you think it is wrong. Do not just point to the count. The count is not the bug. The implementation is.

A zero count is a badge of purity, not a guarantee of correctness.

Decision: when to use this vs alternatives

Use cargo-geiger when you need to scan dependencies for unsafe blocks to assess the raw surface area of unsafety. Use cargo-geiger --exclude when you have established, audited dependencies that inherently require unsafe and you want to focus on new or unknown crates. Use cargo-geiger --json when you are integrating safety checks into a CI/CD pipeline and need machine-readable output for automated policy enforcement. Use cargo-audit when you need to check for known security vulnerabilities and CVEs in your dependency graph. Use cargo deny when you want a comprehensive policy engine that checks licenses, sources, and advisories alongside safety metrics. Use manual review when a dependency has a low unsafe count but performs critical operations, because the number alone never proves soundness.

Where to go next