Skip to content

Why pylock.toml Includes Digital Attestations

A lockfile pins exact versions. Hashes verify that the files you download match the files the resolver saw. But neither tells you who published those files. That gap is what digital attestations in pylock.toml close.

The gap between hashes and provenance

Hashes prove integrity: the file has not been altered. They do not prove origin. If an attacker gains upload access to PyPI and publishes a new release of a package you depend on, the new release will have valid hashes. Your lockfile will accept them during the next update because the file matches itself. Nothing looks wrong.

This happened in March 2026 when an attacker uploaded malicious versions of litellm directly to PyPI, bypassing the project’s normal release process entirely. The uploads had valid hashes. They just didn’t come from the project’s CI pipeline.

What digital attestations prove

Digital attestations (PEP 740) let a CI system cryptographically assert: “I built and uploaded this file, from this repository, using this workflow.” PyPI verifies the assertion at upload time and stores it alongside the package.

The mechanism relies on Sigstore, which replaces long-lived signing keys with short-lived certificates tied to an OpenID Connect identity. A GitHub Actions workflow, for example, gets a certificate that says “this is the release.yml workflow in org/repo.” The certificate expires in minutes, so there is no key to steal. Every signature is recorded in a public transparency log, creating an independent audit trail.

For projects that use trusted publishing and the official PyPA publish action, attestations are generated automatically with no extra configuration.

How pylock.toml records attestation identities

PEP 751 specifies an [[packages.attestation-identities]] table that records the publisher identity for each package. When a tool generates or updates a lockfile, it queries the index for attestation data and writes the publisher details directly into the file:

[[packages]]
name = "attrs"
version = "25.1.0"

[[packages.attestation-identities]]
kind = "GitHub"
repository = "python-attrs/attrs"
workflow = "pypi-package.yml"
environment = "release-pypi"

The fields identify the trusted publisher: which CI provider (kind), which source repository, and which workflow produced the upload. This is the same publisher identity that PyPI verified at upload time through its OIDC-based trusted publishing flow.

What this makes visible

Recording publisher identities turns your lockfile into a supply chain audit surface. Three scenarios become detectable:

Attestation details disappear. A package that previously had attestation data in your lockfile suddenly lacks it after an update. That means the new version was uploaded without trusted publishing, which could indicate a compromised API token or a manual upload from an attacker’s machine.

The publisher changes. The repository or workflow in the attestation identity shifts to something unexpected. A package that was always published from org/repo via release.yml now shows a different repository or workflow.

The lockfile and the index disagree. An installer can compare the attestation identity recorded in the lockfile against what the index reports. If they differ, something changed between when the lockfile was generated and when installation occurs.

All three scenarios are visible in a standard code review diff. When a teammate opens a pull request that updates dependencies, the attestation fields are right there in the changeset, alongside the version bumps and hash changes.

Verifying attestations as a package consumer

No installer currently verifies attestations automatically at install time. Neither pip nor uv checks the attestation-identities in a pylock.toml file before installing packages. Trail of Bits is building a plugin architecture for pip that would enable this, but it has not shipped yet.

Until that lands, the primary verification mechanism is human review of lockfile diffs. When a pull request updates your pylock.toml, check the [[packages.attestation-identities]] sections alongside the version and hash changes. The fields are short and readable; a disappeared or altered publisher identity stands out in a diff the same way a changed hash does.

For programmatic verification outside the install flow, you can query the PyPI Integrity API or use the pypi-attestations CLI to verify a downloaded artifact against its published provenance:

$ pypi-attestations verify pypi \
    --repository https://github.com/python-attrs/attrs \
    pypi:attrs-25.1.0-py3-none-any.whl

Note

The pypi-attestations CLI is intended for experimentation and is not considered a stable interface. Expect the command surface to change as install-time verification matures.

What attestations do not solve

Attestations verify where a package was built, not what it contains. A compromised maintainer who pushes malicious code to their own repository and publishes through their normal CI pipeline will produce a valid attestation. Code review, vulnerability scanning, and delayed ingestion policies address that threat.

Attestations also require adoption. Packages that do not use trusted publishing will not have attestation data, so missing attestations alone are not proof of compromise. The signal is most useful for packages that had attestations and lost them.

Learn More

Last updated on

Please submit corrections and feedback...