Package signing and attestations are two ways of attaching evidence to a published artifact so that someone downloading it later can check where it came from. A signature binds the bytes to a key, either the registry’s or the author’s. A provenance attestation is a signed statement about where an artifact came from, usually in-toto and signed through Sigstore. SLSA build provenance names the source repository, commit and build workflow, while a publish attestation such as PyPI’s records which trusted publisher uploaded the file.

Trusted publishing, which several registries have added alongside attestations, replaces long-lived API tokens with short-lived OIDC credentials from CI. It makes provenance easy to produce because the registry receives a verified workflow identity with each upload. Some registries offer trusted publishing on its own. The workflow’s identity becomes the publishing credential, so a compromised workflow can publish a malicious release. GitHub Actions security in Python packages audits those workflows for PyPI.

Linux distributions have signed packages for decades. Debian builds binaries from uploaded source on its own buildd machines and Fedora builds through Koji, and the archive key signs the result, so a package apt or dnf accepts is one the distribution built, from source uploaded by one of a vetted group of packagers.

Language registries accept uploads from anyone with an account, and the wheel, gem, crate or tarball was built wherever its author built it, on a laptop or a CI runner. A registry signature over those bytes shows they match what was uploaded, which protects against a tampered mirror. A release pushed with a stolen token gets the same signature as one from the project’s real workflow.

Attestations record the build itself: when Ultralytics was compromised in December 2024, the first malicious releases came out of the project’s release workflow with valid attestations, and a later batch uploaded with a leaked API token carried none, which let investigators separate the token uploads from the workflow releases.

The registries are the same set as the 2FA survey in August, and for each one I looked at what it signs and where it stores the evidence, plus whether the client checks it by default. Signing is for the bad days explains the reasons for TUF and in-toto, and for Sigstore. Zach Steindler’s FOSDEM talk (written summary) covers npm, PyPI, RubyGems and Maven Central in more detail.

In a pull request to in-toto/friends, merged in September 2026, Sivana Hamer at NC State catalogued where in-toto attestations are stored. The pull request adds a producer table covering package registries, Git repositories and releases, OCI image manifests and referrers, GitHub’s attestation API, Archivista and Rekor. For each one it records whether attestations are stored alongside the artifact, which format they use, and whether they can be private. Hamer co-authored a study of 1,523 SLSA-related GitHub issues, whose authors identified complex implementation and unclear communication as the main obstacles to adopting SLSA, and Research Directions in Software Supply Chain Security with Laurie Williams.

Sigstore

npm launched provenance in April 2023 as an opt-in --provenance flag for packages built on GitHub Actions or GitLab CI/CD cloud runners from a public repository whose URL matches the repository field in package.json. Since trusted publishing went GA in July 2025, provenance is generated by default when publishing that way. The packument lists it in dist.attestations, and the bundles are served from /-/npm/v1/attestations/<package>@<version>.

Each version published with provenance has two bundles there: a SLSA v1 provenance statement signed with a Sigstore certificate, and an npm publish attestation signed with a registry key that Sigstore’s trust root includes. npm designed its publish predicate before in-toto had a release attestation type, and the later in-toto release predicate records the same claim in another format.

CircleCI was added as a trusted publisher in April 2026, with provenance still limited to GitHub Actions and GitLab publishes, and every provider requires cloud-hosted runners. After the SlugFest report showed trust configurations were bound to a repository’s name instead of its ID, npm now expires unvalidated configurations after 48 hours and rejects issue_comment triggers.

PyPI added trusted publishing in April 2023, stopped accepting PGP signatures the following month, and saw PEP 740 attestations go generally available in November 2024. pypa/gh-action-pypi-publish generates attestations by default for projects using trusted publishing, using PyPI’s own publish predicate in place of SLSA build provenance: the provenance object for pip 26.2.2’s wheel contains a single publish attestation from GitHub. PyPI accepts GitHub Actions, GitLab CI/CD, Google Cloud and ActiveState as publishers, with a beta for self-managed GitLab since November 2025.

PyPI’s attestations are per file, served from the integrity API at /integrity/<project>/<version>/<filename>/provenance and linked from each file in the Simple JSON API. They use PyPI’s own envelope format around the Sigstore material, and PEP 740 permits attestations to be added to a file after upload, so a collector has to re-fetch files it has already processed.

RubyGems added trusted publishing for GitHub Actions in December 2023 and server support for Sigstore attestations in November 2024. Attestations are uploaded with gem push --attestation, or through the rubygems/release-gem action, where the input is labelled experimental, and they are served as Sigstore bundles from /api/v1/attestations/<name>-<version>.json. Automatic attestation in gem push, limited to GitHub Actions publishing to rubygems.org, was merged in April 2026 and ships in the RubyGems 4.1 line.

RubyGems also has an older X.509 signing scheme through gem cert, which the guide describes as rarely used, and gem install checks those signatures when a trust policy is passed with -P.

JSR has had OIDC publishing from GitHub Actions since its open beta in March 2024, and publishes from Actions get SLSA provenance recorded in Rekor automatically, with --no-provenance as the opt-out. Maven Central has validated optional .sigstore.json files since January 2025, with an invalid one producing a warning, and Sonatype states that PGP remains the required signature.

conda’s CEP 27, accepted in July 2025, defines a publish predicate in a Sigstore bundle, and CEP 50, accepted in September 2026, specifies serving attestations as .sigs sidecar files next to each package, listed through an attestations_sha256 field in repodata. prefix.dev has generated CEP 27 attestations since August 2025 for packages uploaded through trusted publishing from GitHub Actions, GitLab CI/CD or Google Cloud.

conda-forge’s own channel uses neither signing nor trusted publishing: builds upload to a staging channel with a per-feedstock token and are copied to conda-forge after an output check, and the September 2025 core meeting minutes report little progress on signing. The conda client checks each download against the SHA-256 and size in repodata and fails on a mismatch. rattler added opt-in Sigstore verification at install time in September 2026, and conda has open issues requesting install-time verification.

In each of these clients verification is a separate step from installing: npm ships npm audit signatures, which checks registry signatures and provenance as its own command. For Python, open issues on pip (the attestation-identities field in PEP 751 lockfiles) and uv (PEP 740) track install-time checks, and PyPI’s documentation recommends the pypi-attestations CLI for manual checks.

Identity

Checking the signature on an attestation shows that it was signed with a key certified by Sigstore. Checking that the certificate belongs to the repository you expected is a second step, and both gh and pypi-attestations require the user to supply that repository: gh attestation verify takes --owner or --repo and pypi-attestations verify pypi takes --repository on every call, and each compares the value with the repository in the signing certificate. PEP 751 lockfiles can record the expected identity once per package in attestation-identities, which is the field the open pip issue asks installs to be checked against.

Keys

npm has signed every package with an ECDSA registry key since April 2023, when it deprecated its PGP signatures, with signatures in dist.signatures and public keys at /-/npm/v1/keys. NuGet.org repository-signs every package and accepts optional author signatures from X.509 certificates issued by a CA in the Windows root store. The dotnet client checks those signatures during restore by default: always on Windows, and on Linux since the .NET 8 SDK, with macOS left off. NuGet added trusted publishing for GitHub Actions in September 2025, with GitLab supported through the token API, and its artifact-level evidence is limited to signatures.

Maven Central requires a PGP .asc signature on every published file, along with MD5 and SHA-1 checksums. Maven checks those signatures during dependency resolution when a plugin such as pgpverify-maven-plugin is added. A PGP signature identifies the uploader’s key and carries no record of the source the file was built from, and Central has no trusted publishing to supply one.

Clojars accepts optional PGP .asc or SSH .sig files and requires that if any artifact in a deploy is signed, all of them are, but it skips checking the signatures against a key. Leiningen fails on a checksum mismatch by default and checks PGP signatures through lein deps :verify, while tools.deps warns on a mismatch and skips signature checks.

The Terraform Registry requires providers to publish a GPG signature over a SHA256SUMS file, from a key uploaded to the registry, and terraform init checks it by default and reports whether the signing key belongs to HashiCorp, a HashiCorp partner, or a community publisher. Terraform modules are published unsigned from git tags through a webhook.

Open VSX signs every extension with a registry key, and its version API returns the signature, a SHA-256 and the public key URL. verify commands were added to the ovsx CLI in September 2026. Checking those signatures at install is up to each editor build, and Arch’s Code - OSS package has failed to verify them. Open VSX shipped trusted publishing for GitHub Actions and GitLab CI in server v1.2.0 in September 2026, which sets a publishedWithTrustedPublishing flag on the version. v1.3.0, still pending deployment on open-vsx.org, records the CI run on each upload.

Hex signs all registry resources with the repository’s RSA key, and the hex_core client verifies those signatures on every fetch. Hex added Workload Identity, its name for trusted publishing, in October 2026. A GitHub Actions job exchanges an OIDC token for a 15-minute key scoped to one package, and the verified claims are stored unsigned on the release and shown on the package page as a “Provenance” card. SLSA provenance and Sigstore are on the Erlang Ecosystem Foundation’s roadmap as unfunded work.

CPAN’s per-author CHECKSUMS files are PGP-signed and include SHA-256 hashes, and authors can sign distributions with Module::Signature. CPAN.pm and cpanm both check signatures only when the user opts in.

Swift’s package registry protocol has supported CMS signing since Swift 5.9. SwiftPM sets checksum pinning to strict by default and validates signed archives against its trust store. The Swift Package Index lists git repository URLs, and SwiftPM fetches those packages by cloning, so registry signing covers only packages installed from a registry.

Hackage signs its index with TUF through hackage-security, and hashes in the signed index cover package tarballs. cabal-install enables the check by default for hackage.haskell.org.

Repository Service for TUF (RSTUF) is software a registry can deploy to sign and republish its index on a schedule. Zach Steindler’s FOSDEM talk noted that RSTUF’s output is not yet verified in a normal production install workflow.

Checksums

Go has had the checksum database at sum.golang.org on by default since Go 1.13 in 2019. It is a transparency log of module hashes, so every client gets the same bytes for a given version, and it can be bypassed per path with GOPRIVATE or GONOSUMDB. A Go module version is a git tag pushed to the source repository, so the checksum database is the only signed record.

crates.io records a SHA-256 of each .crate in the index and Cargo rejects a download with a different hash, but the index itself is unsigned. A Sigstore RFC was closed and a TUF RFC has been open since October 2024. crates.io shipped trusted publishing for GitHub Actions in July 2025 and added GitLab in January 2026, along with a per-crate setting to disable token publishing.

Julia’s General registry stores a content-addressed git tree hash per version as its only integrity check. CRAN’s PACKAGES file lists an MD5 for each package, which install.packages ignores when downloading. R 4.6.0, released in April 2026, added optional package signing: a package can ship a SHA256 file with a detached GnuPG signature, which R CMD INSTALL checks when present and requires when _R_INSTALL_REQUIRE_SIGNED is set, and otherwise uses the unsigned hash files inside the tarball.

Caches

Nix, Guix and Spack build packages from source on project infrastructure and sign the binary caches they serve, as Debian and Fedora do. Each client checks those signatures by default. On cache.nixos.org each .narinfo carries an Ed25519 signature over the store path, its hash and its references. Nix’s require-sigs defaults to true, and the default trusted-public-keys includes the cache.nixos.org key.

Guix rejects substitutes that are unsigned or signed by a key outside its ACL, and since 1.2.0 in 2020 guix pull authenticates every channel commit against the OpenPGP keys listed in the parent commit’s .guix-authorizations file, the same check guix git authenticate applies to any repository. Spack signs build cache manifests with GPG and verifies them on install by default, with --no-check-signature and --unsigned as opt-outs, though spack buildcache keys --install --trust installs and trusts every key the mirror serves. Nix has an open Sigstore issue and a provenance tracking PR, the only build provenance proposals across the three.

Retired

Anaconda’s TUF-based conda-content-trust, available since 2021, reached end of support in September 2026. Docker announced the retirement of Docker Content Trust in July 2025, and the Notary v1 server at notary.docker.io shuts down on 8 December 2026, with cosign and Notation named as replacements for publishers. Docker Hardened Images are signed with cosign and carry provenance, SBOM and VEX attestations.

Unsigned

pub.dev has accepted OIDC publishing from GitHub Actions and Google Cloud since January 2023 and its audit log records the CI run that published each version. packagist.org offers neither signing nor trusted publishing, and since July 2026 each stable version has been fixed to a commit, though GitHub generates the dist zips on request. A transparency log for security events is in progress. The undated longer-term plan is hosted artifacts with SLSA provenance and Sigstore attestations that Composer verifies. CocoaPods uses git for integrity, and trunk stops accepting new podspecs on 2 December 2026.

Forges

The forge is the identity provider that registries accept for trusted publishing, and for git-based registries like Homebrew it is also where attestations are stored. GitHub artifact attestations went GA in June 2024. Attestations for public repositories are signed through Sigstore’s public-good instance and logged in Rekor, while private repositories use GitHub’s own instance with no transparency log. A REST API lists attestations by subject digest with a predicate_type filter.

Immutable releases, GA since October 2025, add a release attestation with its own in-toto predicate, checked with gh release verify. Actions themselves are repositories referenced by tag or SHA, with no signature. Immutable Actions, which would have published actions as OCI packages with provenance, was removed from the roadmap in December 2025. The workflow lockfiles that entered public preview in October 2026 pin direct and transitive actions to commits without integrity hashes.

Homebrew has attached GitHub attestations to homebrew-core bottles since May 2024, and verifying them is opt-in with HOMEBREW_VERIFY_ATTESTATIONS, which shells out to gh. Who Built This? traces an attestation like these from a package back to its commit, and Reproducible Builds in Language Package Managers covers checking the artifact by rebuilding it.

GitLab’s SLSA Level 3 provenance was introduced as an experiment in GitLab 18.3 and is still marked experimental, available to public projects on GitLab.com Ultimate behind the slsa_provenance_statement feature flag. Attestations are listed per project and subject digest through the attestations API and checked with glab attestation verify. As an OIDC issuer, GitLab is accepted for trusted publishing by npm, PyPI, crates.io, NuGet, Open VSX and prefix.dev.

Forgejo v15, released in April 2026 and run by Codeberg, added OIDC ID tokens for Actions, and registries have yet to accept it as a trusted publisher. PyPI has a discussion thread and crates.io has named it as a possibility. Gitea merged the equivalent in October 2026 for its unreleased 29.0 release. Bitbucket Pipelines issues OIDC tokens, and a request for npm trusted publishing from Pipelines is open on Atlassian’s Bitbucket Cloud tracker with the status Gathering Interest.

Gitea, Forgejo, Bitbucket Cloud, Gerrit and Tangled have no dedicated attestation endpoint. fremforge, a Forgejo-based service, has a release attestation endpoint that returns DSSE envelopes and Sigstore bundles.

Summary

In the “signature checked by default” column, Go’s checksum log and Hackage’s TUF index sign the metadata that holds the hashes, so the go command and cabal-install authenticate the hashes as well as the downloads. Cargo, Julia’s Pkg and conda check downloads against hashes in metadata served over TLS without a signature. That catches corruption, but the hashes are only as trustworthy as the registry serving them.

  signatures provenance attestations trusted publishing signature checked by default attestation checked by default
npm registry ECDSA SLSA + npm publish, Sigstore GitHub, GitLab, CircleCI no; npm audit signatures no; npm audit signatures
PyPI PGP removed 2023 PEP 740 publish GitHub, GitLab, Google Cloud, ActiveState n/a no; pypi-attestations
RubyGems optional X.509 Sigstore GitHub opt-in (-P) no
JSR none SLSA, Sigstore GitHub n/a no
Maven Central PGP required optional .sigstore.json no plugin no
prefix.dev none Sigstore (CEP 27) GitHub, GitLab, Google Cloud n/a opt-in (rattler)
conda-forge none none no n/a; repodata checksum n/a
NuGet repository + optional author none GitHub, GitLab Windows, Linux n/a
Clojars optional PGP/SSH, not verified none no opt-in (Leiningen); checksum n/a
Terraform Registry GPG required for providers none no yes, providers n/a
Open VSX registry key none GitHub, GitLab depends on editor n/a
Hex.pm registry RSA planned GitHub yes n/a
CPAN PGP CHECKSUMS, optional author none no opt-in n/a
Swift registries CMS none no yes; checksum pinning n/a
Hackage TUF index none no yes, TUF n/a
Go signed checksum log none n/a yes, checksum DB n/a
crates.io none none GitHub, GitLab n/a; index checksum n/a
Julia General none none n/a n/a; tree hash n/a
CRAN optional GnuPG (R 4.6) none no if present n/a
nixpkgs Ed25519 cache signatures none n/a yes n/a
Guix substitutes + signed commits none n/a yes n/a
Spack GPG build cache none n/a yes n/a
Docker Hub DCT, retiring Dec 2026 Hardened Images only no no no
pub.dev none none GitHub, Google Cloud n/a n/a
Packagist none planned planned n/a n/a
CocoaPods none none no n/a n/a
GitHub Actions none immutable releases only n/a n/a no
Homebrew none GitHub attestations n/a n/a opt-in