Sigstore attestations#
Sigstore is a way to cryptographically sign packages (or any binary artifacts). It was pioneered in the container space, but has been adopted by many packaging ecosystems: PyPI, Python releases, Rust crates, Homebrew, and Rubygems.
Rattler-Build supports creating Sigstore attestations for conda packages, allowing you to cryptographically sign your packages and provide verifiable provenance information.
What does a Sigstore attestation provide?#
A Sigstore attestation ties the producer of a package (for example, a GitHub Actions workflow) to the package artifact. When the attestation is created, metadata about the artifact is registered in a "Transparency Log" which can be inspected at any time. The metadata contains information such as:
- The name and SHA256 hash of the package
- The CI workflow that built the package
- The repository Git hash that contained the CI workflow
- The organization and username that built the package
Using this information, you can attest that a given package was created and uploaded by a certain GitHub organization, and this is transparently logged on both the server and the Sigstore public good instance.
The attestation follows CEP-27, which standardizes the in-toto attestation format with a Conda-specific predicate containing:
- The SHA256 hash of the package (guaranteed to be unique)
- The full filename (
{name}-{version}-{buildstring}.conda) - Optionally, the target channel URL (e.g.,
https://prefix.dev/my-channel)
One attestation per package
Per CEP-27, a Conda publish
attestation describes exactly one .conda artifact — its subject
contains a single name and digest. If a build produces multiple packages
(for example a multi-output recipe, or a matrix build across platforms),
each package needs its own attestation bundle. Tools that accept a glob
like **/*.conda and emit a single bundle covering all matches do not
produce a CEP-27 compliant attestation, and uploads using such a bundle
will fail to verify.
Automatic attestation generation#
The easiest way to create Sigstore attestations is using the
--generate-attestation flag. The same flag is available on both
rattler-build publish (which builds and uploads in one step) and on
rattler-build upload prefix (which uploads pre-built packages):
# Build and publish in one go
rattler-build publish ./my-recipe.yaml --to https://prefix.dev/my-channel --generate-attestation
# Or upload one or more pre-built packages
rattler-build upload prefix -c my-channel ./output/**/*.conda --generate-attestation
In both cases rattler-build will:
- Take each
.condafile individually, - Mint a fresh OIDC token from the CI runner and create one CEP-27 compliant Sigstore attestation per package,
- Upload the package and its bundle to prefix.dev together.
You do not need actions/attest, cosign, or any other external tool
when you use this flag — rattler-build handles the per-package signing and
the one-attestation-per-package requirement automatically. This is the
recommended path for multi-output recipes and matrix builds.
Pass --store-github-attestation (on upload prefix) to additionally push
the generated bundle to GitHub's attestation API so it shows up in your
repository's attestation tab.
Requirements
The --generate-attestation flag only works when:
- Uploading to prefix.dev channels
- Using Trusted Publishing (OIDC authentication) — it cannot be combined with an API key
- Running in a supported CI environment (e.g., GitHub Actions)
GitHub Actions example#
Here's a complete example workflow using automatic attestation generation:
name: Build and publish with attestation
on: [push]
jobs:
build:
runs-on: ubuntu-latest
# These permissions are needed for OIDC authentication
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Set up rattler-build
uses: prefix-dev/rattler-build-action@v0.2.34
- name: Build and publish with attestation
run: |
rattler-build publish ./recipe.yaml \
--to https://prefix.dev/my-channel \
--generate-attestation
Manual attestation with GitHub Actions#
Prefer --generate-attestation when possible
For most users — including multi-package and matrix builds — the
--generate-attestation flag described above already does everything
in this section automatically and correctly produces one bundle per
package. Reach for the manual flow only when you need to customize the
predicate, sign with an external trust root, or otherwise can't use
rattler-build's built-in signing.
If you need more control over the attestation process, you can use GitHub's
official attest action to create the attestation separately. Because each
attestation must cover exactly one package (see the warning above), the
attestation and upload steps need to run once per .conda artifact.
The example below builds a single recipe and signs the resulting package:
name: Package and sign
on: [push]
jobs:
build:
runs-on: ubuntu-latest
# These permissions are needed to create a sigstore certificate
permissions:
id-token: write
contents: read
attestations: write
steps:
- uses: actions/checkout@v4
- name: Build conda package
uses: prefix-dev/rattler-build-action@v0.2.34
# Use GitHub's official attest action with the Conda predicate.
# `subject-path` MUST point at a single `.conda` file — globs that
# resolve to more than one file produce a non-compliant bundle.
- uses: actions/attest@v1
id: attest
with:
subject-path: ./output/linux-64/my-package-0.1.0-h123_0.conda
predicate-type: "https://schemas.conda.org/attestations-publish-1.schema.json"
predicate: '{"targetChannel": "https://prefix.dev/my-channel"}'
# Upload the package together with its attestation bundle
- name: Upload the package
run: |
rattler-build upload prefix -c my-channel \
./output/linux-64/my-package-0.1.0-h123_0.conda \
--attestation ${{ steps.attest.outputs.bundle-path }}
Multiple packages#
If your build produces more than one .conda file (for example a multi-output
recipe, or several subdirs from a matrix build), actions/attest must be
invoked once per package, with subject-path pointing at exactly that
single file. There is no built-in way to produce one bundle for several
packages — that would not match the CEP-27 schema.
The most common pattern is a matrix strategy
that fans out one job per package, calls actions/attest once per job, and
then uploads the resulting (package, bundle) pair.
This approach gives you full control over the attestation creation and allows you to customize the predicate or add additional attestation metadata.
Source attestation verification#
Experimental
This feature requires the --experimental flag: rattler-build build --experimental -r recipe.yaml
In addition to signing built packages, Rattler-Build can also verify the attestations of source archives during the build. This lets you ensure that the source code you're building from was produced by a trusted publisher (e.g., a specific GitHub Actions workflow).
How it works#
Add an attestation block to a URL source in your recipe:
source:
url: https://files.pythonhosted.org/packages/.../flask-3.1.1.tar.gz
sha256: "6489f1..."
attestation:
publishers:
- github:pallets/flask
When Rattler-Build downloads the source, it will:
- Fetch the Sigstore attestation bundle (automatically derived for PyPI packages, or from
bundle_url) - Verify the bundle signature against the Sigstore transparency log
- Check that the attestation identity matches one of the listed publishers
If verification fails, the build is aborted.
PyPI sources#
For packages hosted on PyPI, the attestation bundle URL is automatically derived from the PyPI attestation API. You only need to specify the publisher:
source:
url: https://files.pythonhosted.org/packages/.../flask-3.1.1.tar.gz
sha256: "6489f1..."
attestation:
publishers:
- github:pallets/flask
GitHub release sources#
For source archives from GitHub releases, specify the bundle_url pointing to the .sigstore.json bundle:
source:
url: https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz
sha256: "eb33e5..."
attestation:
bundle_url: https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz.sigstore.json
publishers:
- github:facebook/zstd
Publisher format#
Publishers are specified in github:owner/repo format. The identity is matched against the Sigstore certificate's Subject Alternative Name (SAN), which for GitHub Actions is the workflow identity.
Verifying attestations#
Once packages are published with attestations, they can be verified using several tools:
Using the GitHub CLI#
gh attestation verify my-package-0.1.0-h123_0.conda \
--owner my-org \
--predicate-type "https://schemas.conda.org/attestations-publish-1.schema.json"
Using cosign#
cosign verify-blob \
--certificate-identity-regexp "https://github.com/my-org/.*" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
my-package-0.1.0-h123_0.conda
Using sigstore-python#
pip install sigstore
sigstore verify identity \
--cert-identity "https://github.com/my-org/my-repo/.github/workflows/build.yml@refs/heads/main" \
--cert-oidc-issuer "https://token.actions.githubusercontent.com" \
my-package-0.1.0-h123_0.conda
Viewing attestations#
You can find attestations for packages published to prefix.dev in several places:
- GitHub: View attestations in your repository's attestations tab (e.g.,
https://github.com/my-org/my-repo/attestations) - Sigstore public goods instance: Search by package hash at search.sigstore.dev
- prefix.dev: View attestations on the package page
Security benefits#
Sigstore attestations provide several security benefits:
-
Unforgeability: Attestations cryptographically bind a package to its producer, preventing forgery of provenance metadata.
-
Transparency: All attestations are logged in a public, append-only transparency log. This means attackers cannot perform targeted attacks without leaving a public trace.
-
No long-lived keys: Sigstore uses ephemeral keys bound to identities (like GitHub Actions workflows), eliminating the risk of key compromise.
-
Build provenance: Machine identities in the attestation provide verifiable information about exactly which workflow, repository, and commit produced the package.