crates.io supports OIDC-based trusted publishing from GitHub Actions (and GitLab CI), which means a generated Rust SDK can release without a long-lived CARGO_REGISTRY_TOKEN sitting in your CI secrets. The catch: a trusted publisher configuration can only be created after an initial manual publish of the crate — per the RFC that defines the feature, there's no way to bootstrap a brand-new crate name through trusted publishing alone. So the real sequence is: publish version 0.1.0 by hand with a token, configure trusted publishing in your crates.io crate settings, then every release after that runs through CI using rust-lang/crates-io-auth-action@v1 and no stored token at all.
How trusted publishing actually works
The OIDC exchange, per the RFC:
- Your GitHub Actions workflow requests an ID token from GitHub's OIDC provider.
- GitHub returns a signed ID token identifying the exact workflow, repo, and (optionally) environment that's running.
- The workflow sends that token to crates.io.
- crates.io validates the signature against GitHub's public keys.
- crates.io issues a short-lived access token — "typically valid for less than an hour" per the RFC — scoped to that one publish.
- The workflow uses the token with
cargo publish. - The workflow requests the token be revoked once the publish completes.
No long-lived secret exists at any point in that flow. If your CI environment is compromised, there's no standing credential to steal — the token that existed is already expired or revoked.
Step 1: publish the first version manually
Trusted publishing configuration is tied to a crate that already exists, so version 0.1.0 (or whatever your starting version is) has to go up the traditional way:
cargo login
cargo publish
This requires a crates.io account and, for that first publish, an API token entered via cargo login. This is the only time in the whole workflow a token touches your machine or CI.
Step 2: configure the trusted publisher
In your crate's settings on crates.io, add a trusted publisher configuration. Per the RFC, the required fields are:
- GitHub username or organization
- Repository name
- Workflow filename (must live under
.github/workflows/) - GitHub Actions environment name (optional, but recommended if you use deployment environments)
The RFC is specific about why the workflow filename is required and not just the repo: it "limit[s] the attack surface" — an attacker would need to compromise an action used specifically within that one workflow file, not just get code merged anywhere in the repo.
Step 3: release from CI with no stored token
A minimal GitHub Actions workflow using the official action:
name: Publish crate
on:
push:
tags:
- "v*"
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: rust-lang/crates-io-auth-action@v1
id: auth
- run: cargo publish
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
rust-lang/crates-io-auth-action@v1 performs the OIDC exchange and outputs a short-lived token that cargo publish reads from CARGO_REGISTRY_TOKEN. Nothing about cargo publish itself changes — the only difference from a token-based setup is where the token comes from and how long it lives.
Optional: require trusted publishing only
Once you've verified the CI workflow publishes correctly, crates.io lets a crate owner enable "Trusted Publishing Only" mode, which disables traditional API-token publishing for that crate entirely — per the January 2026 crates.io development update, this mode also blocks publishing from workflows triggered by riskier GitHub Actions events like pull_request_target and workflow_run. This is worth turning on once trusted publishing is your only release path in practice — it removes the fallback that a leaked long-lived token would otherwise still work against.
Metadata to get right in Cargo.toml before your first publish
| Field | Why it matters |
|---|---|
name |
Must be globally unique on crates.io; check availability before generating your final SDK build |
version |
Follows SemVer; the version you publish manually in Step 1 is what your trusted-publisher config attaches to going forward |
license or license-file |
Required by crates.io for publish to succeed |
repository |
Should point at the exact GitHub repo your trusted publisher config references |
description |
Required, shown on the crates.io listing |
Where a generated Rust SDK fits
This post assumes you already have a Rust client to publish. If you're generating one from an OpenAPI spec, Generate a Rust SDK from OpenAPI compares OpenAPI Generator's rust target, progenitor, and paperclip, with a recommended default. Sourced also generates a preview Rust SDK directly — it's a Scale-plan feature: add Rust in your project's language picker and Sourced runs the generation and packaging step, no local Rust toolchain required. What Sourced doesn't do yet is the crates.io publish step above — that's a manual, one-time trusted-publisher setup you run yourself, same as for a Rust client generated any other way. TypeScript and Python remain the two languages with fully managed, automated registry publishing today.
What to do this week
- If you don't have a Rust SDK yet, generate one — see the Rust generation guide or add Rust as a Sourced preview target.
- Check your intended crate name is available on crates.io before your first manual publish; renaming later means a new crate.
- Publish version 0.1.0 manually with
cargo login+cargo publish, then immediately configure the trusted publisher in your crate settings so every release after that goes through CI. - Once the CI workflow has published successfully at least once, consider enabling Trusted Publishing Only mode to remove the token fallback entirely.
FAQ
Can I skip the manual first publish and go straight to trusted publishing?
No. Per RFC 3691, a trusted publisher configuration can only be created for a crate that already exists on crates.io, which means the first version always goes up via a traditional token-based cargo publish.
Does crates.io trusted publishing work with GitLab CI, not just GitHub Actions?
Yes — the January 2026 crates.io development update confirms trusted publishing supports GitLab CI/CD in addition to GitHub Actions, both using the same OIDC-based flow.
How long does the short-lived token from crates-io-auth-action last?
Per the RFC, the token issued through the OIDC exchange is "typically valid for less than an hour," and the workflow requests revocation once the publish step completes — so in practice the exposure window is however long your publish job actually takes.
What does "Trusted Publishing Only" mode actually block?
It disables traditional API-token publishing for that specific crate and also blocks publishing from workflows triggered by higher-risk GitHub Actions events, specifically pull_request_target and workflow_run, per the crates.io team's January 2026 update.
Does Sourced publish my generated Rust SDK to crates.io automatically?
Not yet. Sourced generates the Rust SDK as a Scale-plan preview download; you run the trusted-publishing setup in this post yourself. TypeScript and Python are the two languages with automated, fully managed registry publishing today.
Create hosted docs from your repo or start free — validate the same OpenAPI spec, generate docs and llms.txt, and (on the Scale plan) generate the Rust SDK preview this post assumes you're publishing.