Skip to content

Why pylock.toml Includes Digital Attestations

A lockfile pins exact versions, and hashes verify that the files you download match the files the resolver saw. Neither says who published those files. pylock.toml reserves a table for that answer, [[packages.attestation-identities]], and this page explains what it records and what it proves.

One caveat up front: today the table is empty. PEP 751 says lockers SHOULD record attestation identities when the index provides them, but neither uv export --format pylock.toml nor pip lock writes the table yet. The rest of this page describes what a lockfile carries once a locker fills it.

The gap between hashes and provenance

Hashes prove integrity: the file has not been altered since the resolver saw it. They do not prove origin. An attacker with upload access to PyPI publishes a new release with valid hashes, and the lockfile accepts it on the next update because the file matches itself.

That is how the litellm compromise played out in March 2026. The malicious versions went straight to PyPI, bypassing the project’s release workflow, and nothing about their hashes looked wrong. What they lacked was the CI provenance the project’s legitimate releases carried.

What digital attestations prove

PEP 740 digital attestations let a CI system assert “I built and uploaded this file, from this repository, using this workflow.” PyPI verifies the assertion at upload time and serves it alongside the file.

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 gets a certificate naming release.yml in org/repo, the certificate expires in minutes, and every signature lands in a public transparency log. Projects that publish with trusted publishing and the PyPA publish action get attestations without extra configuration; How to Publish Python Packages with Digital Attestations covers that path and uv publish.

How pylock.toml records attestation identities

PEP 751 defines [[packages.attestation-identities]] as an optional array of tables under each package, with a required kind naming the trusted publisher and the publisher’s own keys copied in as-is. A locker that fills the table queries the index for each file’s attestation and writes the publisher identity into the lockfile:

[[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. It is the same identity PyPI verified at upload time.

Search a lockfile from uv export for attestation-identities to see whether the locker you use has started filling the table. Today the search comes back empty.

What a filled table makes visible

Recording publisher identities turns the lockfile into a supply chain audit surface. Three signals appear in an ordinary code review diff, alongside the version bumps and hash changes:

  • The attestation disappears. A package that carried publisher data lacks it after an update, meaning the new version was uploaded without trusted publishing. That is the litellm pattern: a compromised token or a manual upload from an attacker’s machine.
  • The publisher changes. The repository or workflow shifts from org/repo via release.yml to something unexpected.
  • The lockfile and the index disagree. Once installers read the table, they can compare the recorded identity against what the index reports at install time and refuse a mismatch. No installer does this yet.

Verify attestations as a package consumer

No installer verifies attestations at install time. Neither pip nor uv reads attestation-identities before installing, and the pip plugin architecture that would enable it is still an open proposal.

Until then, verification is human review of lockfile diffs. When a pull request updates pylock.toml, read the [[packages.attestation-identities]] sections with the version and hash changes; a vanished or altered publisher stands out the way a changed hash does.

For a check outside the install flow, the pypi-attestations CLI verifies an artifact against the provenance PyPI serves through its Integrity API:

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

Note

The CLI’s version number is still 0.0.x, so expect the command surface to change as install-time verification matures.

Install from pylock.toml today

Attestations or not, the lockfile installs the same way. Recent pip releases accept pip install -r pylock.toml behind an experimental warning, and uv pip install -r pylock.toml reads the file the same way, with its own preview warning. How to Install from a pylock.toml Lockfile with pip covers the flags and caveats.

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 the normal CI pipeline produces a valid attestation. Code review, vulnerability scanning, and a dependency cooldown that holds new releases out of resolution address that threat. Attestations also stop at the upload: they say nothing about whether the wheel matches the source it claims, which is what a byte-identical rebuild checks.

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

Last updated on