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?
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. |
- 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-rekorflag — fail if Rekor SET is missing or invalid;- prints human output or JSON;
- exits with stable CI-friendly codes.
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.
- downloads the
.cratefile from crates.io; - extracts
Cargo.toml.origand reads the declaredpackage.repository; - compares the declared repository against an expected value;
- auto-detects expected repo from the local
Cargo.tomlor accepts--repo; - auto-detects crate name and version from the local
Cargo.toml(overridable via--nameand--version); - supports
owner/reposhorthand for the--repoargument; - prints human output or JSON;
- exits with stable CI-friendly codes.
cargo-attest maintains a local, content-addressed cache for downloaded artifacts and API responses:
- Location:
$CARGO_HOME/attest/cache/(or$CARGO_ATTEST_CACHEoverride) - 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 clearCache 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.
Not published to crates.io yet. From a checkout:
cargo install --path .Hash a local file:
cargo attest hash ./my-binaryVerify 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.gzVerify a GitHub Release asset with attestation (requires GH_TOKEN or GITHUB_TOKEN):
cargo attest release owner/repo v1.0.0Verify a crates.io artifact:
cargo attest crate --name serde --version 1.0.210 --repo serde-rs/serdeAuto-detect crate name, version, and repo from the local Cargo.toml:
cargo attest crateWhen a GitHub Release includes .sig + .pem (or .cert) sidecar assets for a binary, cargo-attest automatically detects and cryptographically verifies them:
- Sidecar auto-detection —
<asset>.sigand<asset>.pem/<asset>.certare discovered automatically alongside the release asset. - ECDSA P-256 and P-384 — curve is auto-detected from the X.509 certificate's SubjectPublicKeyInfo.
- Fulcio certificate chain — the signing certificate is validated against the bundled Fulcio root CA (same chain as attestation bundles).
- 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)
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)
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
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.gzCARGO_ATTEST_JSON=1 cargo attest crate --name serde --version 1.0.210 --repo serde-rs/serdeThe 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 exactcargo-attestbinary version that produced the output.status— One oftrusted,unverified,mismatch,error. Mirrors the exit code.checks— Array of per-check results (pass/fail/skip+ detail). Present for all verdicts excepterror.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 |
A verdict carries a subject (the artifact being attested) and, for non-error verdicts, a list of checks with Pass / Fail / Skip outcomes.
| Subject kind | Fields |
|---|---|
github_release |
repo, tag, asset (optional) |
crate |
name, version, repo (optional) |
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.repositoryinCargo.toml.origmatches the expected repository.
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.
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:
- 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.
- 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.
- Subject digest match — the in-toto statement's
subjectarray is checked for an entry whosesha256digest matches the computed artifact hash. - Rekor SET verification — when
tlogEntriesare 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).
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
.cratedownload andCargo.toml.origrepository comparison.
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.
- 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.
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.
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.
MSRV: Rust 1.86.
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo testRun the real-network GitHub release test:
cargo test --test release_real -- --ignoredSee ROADMAP.md.
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.