Publish attestations to Prefix.dev#

Prefix.dev currently supports Trusted Publishing and Sigstore attestations in its Rattler-Build upload path. This guide shows the recommended automatic path, then the manual path for a bundle created by conda-sigstore.

These are Prefix-specific producer workflows. Prefix.dev serves uploaded bundles through its .v0.sigs transport, not the draft PR 142 transport with repodata attestations_sha256 plus mutable .sigs and immutable .sigs.<sha256> endpoints.

You need a Prefix.dev channel, a GitHub repository, a built conda package, and permission to configure Trusted Publishing for both services.

Configure repository access#

Create the target channel, then open Settings → Repository Access and add a trusted publisher. Specify the GitHub owner, repository, and exact workflow filename allowed to upload. Prefix documents this setup in its Repository Access guide.

The workflow needs id-token: write so Rattler-Build can obtain an ambient OIDC identity. It does not need a long-lived Prefix API key.

Use Rattler-Build’s automatic path#

Prefix recommends Rattler-Build’s --generate-attestation option. It creates one CEP 27 bundle per package and uploads each package and bundle together.

name: Publish conda package

on:
  push:
    tags:
      - "v*"

permissions: {}

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - uses: prefix-dev/rattler-build-action@1ca5f45832f419a46d1326ccc5861d7e14d67c44 # v0.2.39
        with:
          setup-only: true
      - name: Build, attest, and publish
        run: |
          rattler-build publish ./recipe.yaml \
            --to https://prefix.dev/MY-CHANNEL \
            --generate-attestation

Replace MY-CHANNEL and the recipe path. The trusted-publisher configuration must name this workflow file. The option works only with Prefix Trusted Publishing in a supported CI environment. It cannot be combined with an API key.

Rattler-Build’s current Sigstore guide documents this as the preferred path, especially for multi-output recipes because each package requires its own CEP 27 statement.

Upload a conda-sigstore bundle manually#

Use this path when the package has already been built or when you need to create the CEP 27 bundle separately. Both commands use the ambient CI workload identity and request their own service-specific OIDC tokens:

conda sigstore attest ./output/linux-64/example-1.0-0.conda \
  --target-channel https://prefix.dev/MY-CHANNEL

rattler-build upload prefix \
  --channel MY-CHANNEL \
  ./output/linux-64/example-1.0-0.conda \
  --attestation ./output/linux-64/example-1.0-0.conda.sigstore.json

Attach attestations to exactly one package per upload. A CEP 27 statement has exactly one subject, so do not sign a glob that resolves to multiple packages as one statement.

The upload identity and the Sigstore signing identity are separate credentials. Public Prefix.dev documentation does not define their relationship as a portable consumer-authorization rule.

Use GitHub’s attestation action manually#

GitHub’s standard attestation action can also create the custom CEP 27 predicate. Invoke it once for each package and pass its bundle-path output to Rattler-Build. This example is for a public repository, where the action uses the public Sigstore instance. GitHub uses a separate Sigstore instance for private and internal repositories, whose Prefix interoperability is not established here.

permissions:
  contents: read
  id-token: write
  attestations: write

steps:
  - uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
    id: attest
    with:
      subject-path: ./output/linux-64/example-1.0-0.conda
      predicate-type: https://schemas.conda.org/attestations-publish-1.schema.json
      predicate: '{"targetChannel":"https://prefix.dev/MY-CHANNEL"}'
  - name: Upload package and bundle
    run: |
      rattler-build upload prefix \
        --channel MY-CHANNEL \
        ./output/linux-64/example-1.0-0.conda \
        --attestation "${{ steps.attest.outputs.bundle-path }}"

The action also stores the attestation in GitHub’s attestation API. Verify that copy with the GitHub CLI:

gh attestation verify ./example-1.0-0.conda \
  --owner MY-GITHUB-OWNER \
  --predicate-type https://schemas.conda.org/attestations-publish-1.schema.json

GitHub verifies against the selected repository owner and predicate type. Use conda sigstore verify as well when you need CEP 27’s exact filename, single-subject, SHA-256, and target-channel checks.

Verify the Prefix-served copy#

Download the package bytes and the currently served Prefix.dev sidecar:

curl --fail --location --remote-name \
  https://prefix.dev/MY-CHANNEL/linux-64/example-1.0-0.conda
curl --fail --location --remote-name \
  https://prefix.dev/MY-CHANNEL/linux-64/example-1.0-0.conda.v0.sigs

Verify them together and supply the intended channel:

conda sigstore verify ./example-1.0-0.conda \
  --bundle ./example-1.0-0.conda.v0.sigs \
  --channel https://prefix.dev/MY-CHANNEL

The result verifies the package binding and reports the actual certificate identity and issuer. The first output line should end in verified. Current Prefix.dev repodata does not pin the .v0.sigs bytes, so direct verification selects this transport explicitly.

For the transport limitation and live fixture, see Verify Prefix.dev sidecars.