Skip to content

Nil06/cargo-attest

cargo-attest

Verify published Rust binaries against the source they claim, without rebuilding them.

CI MSRV License Version

cargo-attest is a Cargo subcommand for consumer-side supply-chain checks. It downloads a published artifact, gathers the evidence that links it to source, and returns a verdict you can use in a terminal or CI job.

It is built for a simple question:

Does this binary line up with the source and release metadata it points to?

Why

Rust has strong source-level tooling, but many users consume compiled artifacts from GitHub Releases, package managers, Docker images, and CI pipelines. Rebuilding every binary bit-for-bit is valuable, but it is expensive and rare in everyday workflows.

cargo-attest takes a pragmatic path: verify the evidence publishers already provide, then make the result explicit.

Verdict Meaning
TRUSTED At least one load-bearing proof matched (checksum, attestation, or crate repo match).
UNVERIFIED Nothing was proven wrong, but evidence was missing.
MISMATCH Published evidence exists and does not match the artifact.
ERROR The command could not complete.

What Works Today

GitHub Release Verification (cargo attest release)

  • resolves the release tag to a commit;
  • selects assets with --asset;
  • downloads the artifact locally;
  • checks downloaded size against GitHub metadata;
  • verifies SHA-256 from the release body or checksum sidecars;
  • looks up GitHub artifact attestation bundles for the computed SHA-256;
  • cryptographically verifies attestation bundles:
    • DSSE envelope signature verification (ECDSA P-384);
    • Fulcio X.509 certificate chain validation (intermediate → bundled root);
    • in-toto subject digest matching against the artifact;
  • when attestations pass, verdict upgrades to TRUSTED;
  • detects and verifies cosign detached signatures (see Cosign Signature Verification);
  • Rekor SET verification — verifies the Signed Entry Timestamp against the bundled Rekor public key, proving the attestation was observed by the transparency log;
  • --require-rekor flag — fail if Rekor SET is missing or invalid;
  • prints human output or JSON;
  • exits with stable CI-friendly codes.

Rekor Transparency Log Verification

When GitHub attestation bundles include tlogEntries, cargo-attest verifies the Rekor Signed Entry Timestamp (SET) against the bundled Rekor ECDSA P-256 public key. This proves:

  • The attestation existed at a specific point in time (non-repudiation);
  • The attestation was observed by the public Rekor transparency log.

Rekor verification is graceful: missing tlogEntries produce a Skip check, not a failure. Use --require-rekor to hard-require a valid Rekor SET.

The Rekor public key is bundled at compile time (src/roots/rekor-root.pem), following the same pattern as the Fulcio root CA.

crates.io Verification (cargo attest crate)

  • downloads the .crate file from crates.io;
  • extracts Cargo.toml.orig and reads the declared package.repository;
  • compares the declared repository against an expected value;
  • auto-detects expected repo from the local Cargo.toml or accepts --repo;
  • auto-detects crate name and version from the local Cargo.toml (overridable via --name and --version);
  • supports owner/repo shorthand for the --repo argument;
  • prints human output or JSON;
  • exits with stable CI-friendly codes.

Local Cache

cargo-attest maintains a local, content-addressed cache for downloaded artifacts and API responses:

  • Location: $CARGO_HOME/attest/cache/ (or $CARGO_ATTEST_CACHE override)
  • Artifact cache: Content-addressed by SHA-256, no TTL (immutable by definition)
  • Metadata cache: TTL-based (10 minutes default), with ETag support
  • Max size: 500 MiB default, configurable via CARGO_ATTEST_CACHE_MAX_SIZE
  • Eviction: LRU by modification time, triggered when limit exceeded
  • Permissions: 0700 (owner-only) on cache directories

Cache commands:

# Show cache status (location, size, entry count)
cargo attest cache status

# Clear all cached data
cargo attest cache clear

Cache failures (corrupt metadata, permission errors, disk full) are never fatal — the tool falls back to network. Use --no-cache to bypass the cache entirely for a single run.

Quick Start

Not published to crates.io yet. From a checkout:

cargo install --path .

Hash a local file:

cargo attest hash ./my-binary

Verify a GitHub Release asset (checksum only):

cargo attest release BurntSushi/ripgrep 14.1.1 \
  --asset ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz

Verify a GitHub Release asset with attestation (requires GH_TOKEN or GITHUB_TOKEN):

cargo attest release owner/repo v1.0.0

Verify a crates.io artifact:

cargo attest crate --name serde --version 1.0.210 --repo serde-rs/serde

Auto-detect crate name, version, and repo from the local Cargo.toml:

cargo attest crate

Cosign Signature Verification

When a GitHub Release includes .sig + .pem (or .cert) sidecar assets for a binary, cargo-attest automatically detects and cryptographically verifies them:

  1. Sidecar auto-detection<asset>.sig and <asset>.pem/<asset>.cert are discovered automatically alongside the release asset.
  2. ECDSA P-256 and P-384 — curve is auto-detected from the X.509 certificate's SubjectPublicKeyInfo.
  3. Fulcio certificate chain — the signing certificate is validated against the bundled Fulcio root CA (same chain as attestation bundles).
  4. Load-bearing — a verified cosign signature independently promotes the verdict to TRUSTED, even without GitHub attestation bundles.

Example cosign output:

TRUSTED
  ✓ tag-resolves-to-commit — tag v0.5.0 → 4649aa970061
  ✓ asset-size:cargo-attest-linux-amd64 — downloaded 4234567 bytes, matching GitHub metadata
  ✓ sha256-body-checksum:cargo-attest-linux-amd64 — declared via release body and computed SHA-256 match (a1b2c3d4e5f6a7b8)
  ✓ cosign-signature:cargo-attest-linux-amd64 — P-256 ECDSA signature verified, Fulcio certificate chain validated
✓ Cosign signature verified (1/1 assets)

Rekor Verification

When attestation bundles include transparency log entries, cargo-attest verifies the Rekor SET:

TRUSTED
  ✓ tag-resolves-to-commit — tag 14.1.1 → 4649aa970061
  ✓ sha256-body-checksum:ripgrep.tar.gz — declared via sidecar and computed SHA-256 match (4cf9f274)
  ✓ github-artifact-attestation:ripgrep.tar.gz — verified 1/1 attestation bundle(s) for sha256:4cf9f274
  ✓ rekor:ripgrep.tar.gz — Rekor SET verified (entry: 24296fb24b8ad77a)
✓ Artifact provenance verified via GitHub attestations (1/1 assets)

Example Output

GitHub Release with attestation verification passing:

TRUSTED
  ✓ tag-resolves-to-commit — tag 14.1.1 → 4649aa970061
  ✓ asset-size:ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz — downloaded 2566310 bytes, matching GitHub metadata
  ✓ sha256-body-checksum:ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz — declared via sidecar asset ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz.sha256 and computed SHA-256 match (4cf9f2741e6c465f)
  ✓ github-artifact-attestation:ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz — verified 1/1 attestation bundle(s) for sha256:4cf9f2741e6c465f, subjects: [ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz]
✓ Artifact provenance verified via GitHub attestations (1/1 assets)

crates.io artifact with repo match:

TRUSTED
  ✓ crate-download — downloaded serde.crate (84231 bytes)
  ✓ crate-name-matches — declared name serde matches requested
  ✓ crate-version-matches — declared version 1.0.210 matches requested
  ✓ crate-repo-declared — declared repository: https://github.com/serde-rs/serde
  ✓ crate-repo-matches — declared repository https://github.com/serde-rs/serde matches expected https://github.com/serde-rs/serde

Stable JSON Output

Set CARGO_ATTEST_JSON=1:

CARGO_ATTEST_JSON=1 cargo attest release BurntSushi/ripgrep 14.1.1 \
  --asset ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz
CARGO_ATTEST_JSON=1 cargo attest crate --name serde --version 1.0.210 --repo serde-rs/serde

The JSON output is a versioned envelope:

{
  "schema_version": "1.0",
  "cli_version": "1.0.0",
  "status": "trusted",
  "subject": {
    "kind": "github_release",
    "repo": "BurntSushi/ripgrep",
    "tag": "14.1.1",
    "asset": "ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz"
  },
  "checks": [
    { "name": "tag-resolves-to-commit", "outcome": "pass", "detail": "tag 14.1.1 → 4649aa970061" },
    { "name": "sha256-body-checksum:ripgrep-...", "outcome": "pass", "detail": "..." },
    { "name": "github-artifact-attestation:ripgrep-...", "outcome": "pass", "detail": "..." },
    { "name": "rekor:ripgrep-...", "outcome": "pass", "detail": "..." }
  ],
  "timing": { "total_ms": 1234 }
}

Key properties:

  • schema_version — Semver string ("1.0"). Consumers MUST check this before parsing. Breaking field changes will bump this version.
  • cli_version — The exact cargo-attest binary version that produced the output.
  • status — One of trusted, unverified, mismatch, error. Mirrors the exit code.
  • checks — Array of per-check results (pass/fail/skip + detail). Present for all verdicts except error.
  • timing — Wall-clock duration in milliseconds.

For the full field reference, see SCHEMA.md. The machine-parseable JSON Schema is at .schema/v1.0.json.

Exit codes:

Code Verdict
0 TRUSTED
1 UNVERIFIED
2 MISMATCH
3 ERROR

Verdict Model

A verdict carries a subject (the artifact being attested) and, for non-error verdicts, a list of checks with Pass / Fail / Skip outcomes.

Subjects

Subject kind Fields
github_release repo, tag, asset (optional)
crate name, version, repo (optional)

TRUSTED paths

A TRUSTED verdict is reached when at least one load-bearing proof matches:

  • Checksum match — the artifact's SHA-256 matches a checksum declared in the release body or a sidecar asset.
  • Attestation verification — at least one GitHub artifact attestation bundle cryptographically verifies and its in-toto subject digest matches the artifact.
  • Cosign signature — a detached cosign signature (.sig + .pem/.cert) verifies against the bundled Fulcio root CA.
  • Crate repo match — the crate's declared package.repository in Cargo.toml.orig matches the expected repository.

Checksum Discovery

cargo-attest accepts common SHA-256 publication patterns:

  • a 64-character hex token on the same line as the asset name in the release body;
  • a sidecar asset named <asset>.sha256;
  • a sidecar asset named <asset>.sha256sum;
  • a sidecar asset named <asset>.sha256.txt.

If no usable checksum is found, the artifact is UNVERIFIED, not TRUSTED.

Attestation Verification

When a GH_TOKEN or GITHUB_TOKEN is set, cargo-attest queries the GitHub Attestation API for Sigstore bundles keyed by the artifact's SHA-256. Each bundle is cryptographically verified:

  1. DSSE envelope signature — the signed payload is verified using the ECDSA P-384 public key extracted from the Fulcio-issued signing certificate embedded in the bundle.
  2. Fulcio certificate chain — the signing certificate is chained up through the Fulcio intermediate CA to the bundled Fulcio root CA. Issuer/subject DN matching and signature verification are performed at each link.
  3. Subject digest match — the in-toto statement's subject array is checked for an entry whose sha256 digest matches the computed artifact hash.
  4. Rekor SET verification — when tlogEntries are present in the bundle, the Signed Entry Timestamp is verified against the bundled Rekor ECDSA P-256 public key.

If at least one bundle passes all steps, the verdict is TRUSTED. Bundles that do not verify are non-fatal: one bad bundle does not prevent verification of others from the same release.

The Fulcio root CA certificate chain and Rekor public key are bundled at compile time (src/roots/fulcio-root.pem, src/roots/rekor-root.pem).

Security Model

cargo-attest does not execute downloaded artifacts. It downloads bytes, hashes them, and compares them to public release evidence.

Current checks:

  • GitHub release metadata;
  • release tag resolution;
  • GitHub asset size;
  • SHA-256 checksums;
  • GitHub artifact attestation bundle lookup and cryptographic verification (DSSE ECDSA P-384, Fulcio X.509 chain, in-toto subject digest matching);
  • Rekor SET verification against bundled Rekor public key (ECDSA P-256);
  • Cosign detached signature verification (ECDSA P-256 and P-384);
  • crates.io .crate download and Cargo.toml.orig repository comparison.

Rekor Trust Model

Rekor verification uses a public key bundled at compile time (src/roots/rekor-root.pem), sourced from the Sigstore TUF repository. The SET proves:

  • The attestation was observed by Rekor at a specific time (integrated time in the entry);
  • The attestation existed before any Fulcio cert revocation or key rotation.

Without Rekor, a compromised Fulcio key could forge attestations retroactively. Rekor SET verification closes this gap.

Cache Security

  • Content-addressed by SHA-256 — cache keys are cryptographic hashes of artifact content. Substituting a different artifact changes the key, preventing cache poisoning.
  • Permissions — cache directories are created with 0700 (owner-only) on Unix.
  • No secrets stored — cache contains only public artifacts and API responses. No tokens, keys, or credentials are cached.
  • Atomic writes — files are written to temp files then renamed, preventing torn reads from concurrent processes.

--require-rekor Flag

When --require-rekor is passed, missing or invalid Rekor SETs produce a Mismatch verdict (exit code 2) instead of a Skip check. This is useful for CI pipelines that require Rekor inclusion as a policy gate.

--strict Mode

The --strict flag promotes any Skip check to Fail, causing exit code 2. This ensures all verification paths executed and nothing was skipped. Useful when you want to guarantee maximum verification coverage.

It does not yet verify SLSA workflow provenance or Merkle inclusion proofs. Treat TRUSTED as "the implemented checks passed", not as a complete supply-chain guarantee.

GitHub attestation lookup uses public API access when available. If GitHub denies an unauthenticated request, set GH_TOKEN or GITHUB_TOKEN and rerun the command.

Development

MSRV: Rust 1.86.

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test

Run the real-network GitHub release test:

cargo test --test release_real -- --ignored

Roadmap

See ROADMAP.md.

License

Licensed under either of:

at your option.

About

Consumer-side attestation for published Rust binaries

Resources

License

Unknown, MIT licenses found

Licenses found

Unknown
LICENSE-APACHE
MIT
LICENSE-MIT

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages