cargo-frisk
Secret scanners check what git tracks. cargo frisk checks what cargo ships — and those are not the same files.
cargo package includes untracked-but-not-ignored files by design. A stray
.env, a credentials.toml, a .pem you copied in to debug something — none
of them are in git, so none of them are visible to a scanner that reads your
repository. All of them go into the tarball, and the tarball is what lands on
crates.io forever.
cargo frisk diffs the packaged .crate against git ls-files, then annotates
the difference with secret-detection rules.
$ cargo frisk
frisk my-crate v0.3.1 14 files, 48.2 KB
shipped, not tracked by git (1)
.env 412 B
shipped and tracked (12)
(pass --verbose for the full listing)
generated by cargo: Cargo.toml.orig, .cargo_vcs_info.json
findings (3)
critical shipped-not-tracked .env
packaged but not tracked by git, and the name indicates credentials
critical env-file .env
environment file packaged into the crate
critical aws-access-key-id .env:1
AWS access key ID
AKIA************ (20 chars)
3 finding(s): 3 critical · fail-on = critical · FAILInstall
cargo install cargo-friskOr grab a prebuilt binary — no Rust toolchain, no compile:
cargo binstall cargo-friskThen, from any package:
cargo friskThat is the whole setup. Nothing to configure.
What it checks
The diff — the product.
| Bucket | Meaning |
|---|---|
| shipped, not tracked | In the tarball, not in git. The dangerous one, reported first. |
| tracked, not shipped | In git, not in the tarball. Usually intentional; occasionally an include list that drops a source file and ships a crate that will not build. |
| shipped and tracked | The boring, expected majority. |
Files cargo synthesises (Cargo.toml.orig, .cargo_vcs_info.json,
Cargo.lock) are listed separately rather than counted as untracked noise.
Any file over 10% of the package size is called out.
The annotation — content and path rules over the packaged files. Provider
tokens, private keys, .env files, credentials.toml, editor backups. Patterns
are adapted from gitleaks (MIT; see
NOTICE) plus rules specific to Cargo packaging.
Exit codes
Distinct, so CI can tell "found something" from "the tool broke":
| Code | Meaning |
|---|---|
0 | nothing unexpected |
1 | findings at or above the fail threshold |
2 | tool error — cargo package failed, malformed archive, bad config |
False positives
Roughly two thirds of naive secret-scanner hits are test data or placeholders. Four mitigations, all on by default:
-
fail-on = "critical". Everything below that reports without failing. -
Entropy gate. Generic patterns below ~3.5 bits/char are downgraded.
password = "xxxxxxxxxxxxxxxx"will not fail your build. -
Path context. Hits under
tests/,fixtures/,examples/and friends drop one severity level — still reported, no longer blocking. -
Inline suppression. Put this on or above the offending line:
// frisk:allow(aws-access-key-id) const EXAMPLE_KEY: &str = "AKIAIOSFODNN7EXAMPLE";frisk:allow(*)allows everything on that line. The comment marker does not matter —#,//,--and anything else all work.
Findings are always reported even when downgraded. Nothing is silently dropped:
files too large to scan or detected as binary are listed under not scanned
rather than quietly skipped.
Configuration
In Cargo.toml, not a dotfile:
[package.metadata.frisk]
fail-on = "critical" # low | medium | high | critical | none
ignore-paths = ["tests/fixtures/**"]
ignore-rules = ["backup-file"]
max-file-size = 5242880 # per-file scan cap, bytes[workspace.metadata.frisk] works too: it is the base that package-level keys
override, and the list-valued keys accumulate across both.
CI
GitHub Actions
jobs:
frisk:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # required by upload-sarif
steps:
- uses: actions/checkout@v5
- uses: HaidarJbeily7/cargo-frisk@v1
with:
upload-sarif: trueFindings appear inline on the PR diff via GitHub Code Scanning. See
action.yml for every input.
security-events: write is not optional when upload-sarif: true. A
composite action cannot grant itself permissions, so without it the upload
fails with Resource not accessible by integration. Drop the block entirely if
you only want the human report and the exit code.
Pull requests from forks always receive a read-only token, whatever the workflow declares. The action detects this and skips the upload with a notice rather than failing the job.
pre-commit
repos:
- repo: https://github.com/HaidarJbeily7/cargo-frisk
rev: v0.1.0
hooks:
- id: cargo-friskAnything else
cargo frisk --sarif -o frisk.sarif
cargo frisk --json | jq '.packages[].findings[]'Usage
cargo frisk [OPTIONS]
--manifest-path <PATH> Path to Cargo.toml
-p, --package <NAME> Frisk only this package (repeatable)
--workspace Frisk every workspace member
--fail-on <SEVERITY> low | medium | high | critical | none
--format <FORMAT> human | json | sarif
--json Shorthand for --format json
--sarif Shorthand for --format sarif
-o, --output <PATH> Write the report to a file
--no-scan Report the diff only; skip content rules
--gitleaks-config <P> Load extra rules from a gitleaks-format TOML
--ignore-rule <ID> Disable a rule (repeatable)
--ignore-path <GLOB> Ignore findings under a glob (repeatable)
--max-file-size <N> Per-file scan cap in bytes
--color <WHEN> auto | always | never
-v, --verbose List every packaged file
--gitleaks-config accepts the upstream gitleaks.toml if you want all ~150
rules. Imported rules default to high — below the default gate — because we
have not calibrated them.
What it deliberately does not do
- It is not a secret-detection engine. gitleaks owns that problem. We consume its ruleset.
- It does not reimplement Cargo's packaging logic.
include/exclude/.gitignoreresolution is subtle, and a file set that diverged from the real upload would make this tool actively misleading. It always shells out tocargo package --no-verify --allow-dirty. - It does not scan git history. That is gitleaks'
gitmode. - It does not extract the tarball to disk. Writing a suspected secret to a new location is not something a security tool should do. Everything is streamed in memory.
- It does not fix anything. Report and exit. Fixing is your job.
Library
lib.rs is public from v0.1:
use cargo_frisk::{run, Options};
let outcome = run(&Options::default())?;
for finding in outcome.findings() {
println!("{} {} {}", finding.severity, finding.rule, finding.path);
}
# Ok::<(), anyhow::Error>(())Development
MSRV is 1.85. rust-toolchain.toml pins the repo to stable.
cargo test
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo run -- frisk # dogfoodFixtures live in tests/fixtures/. Every "secret" in them is AWS's own
documentation example: structurally valid enough to match, obviously fake
enough that nobody panics.
Licence
MIT OR Apache-2.0, at your option. Vendored gitleaks patterns are MIT; see
NOTICE.