feat(pypi): support non-sha256 --hash pins and index digests - #3974
Open
abcdabcd987 wants to merge 1 commit into
Open
feat(pypi): support non-sha256 --hash pins and index digests#3974abcdabcd987 wants to merge 1 commit into
abcdabcd987 wants to merge 1 commit into
Conversation
pip.parse only recognized --hash=sha256: pins in requirements files and #sha256= URL fragments on Simple API pages; pins using any other algorithm were silently dropped, so they were neither matched against the index metadata nor verified at download time. - Parse --hash=<algo>:<digest> for all hashlib algorithms and keep the pins in the reconstructed requirement line so that the pip fallback verifies them. - Parse #<algo>=<digest> Simple API URL fragments per PEP 503. - Match non-sha256 pins against the digests advertised by the index. - Download with ctx.download(integrity = ...) when only a non-sha256 digest is known, via a new integrity attribute on whl_library. - Parse the uv.lock hash field algorithm instead of assuming sha256. - Store "<algo>:<digest>" values in the lockfile facts for non-sha256 digests, and fix the dist filename fact lookup to use the URL key the filenames are stored under. Matching still requires the index to advertise a digest with the same algorithm as the pin; support for the multi-algorithm PEP 691 hashes dict would be a separate feature. Fixes bazel-contrib#3972 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
abcdabcd987
marked this pull request as ready for review
July 29, 2026 03:54
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Previously,
pip.parseonly recognized--hash=sha256:pins in requirementsfiles and
#sha256=URL fragments on Simple API pages. Pins using any otherhash algorithm were silently dropped: the requirement was treated as hash-less,
so with
experimental_index_urlthe resolver fell back to trusting whateverdigests the index listed for that version, and in the pip fallback the hashes
were stripped from the requirement line entirely. The artifact was no longer
verified against what was locked.
This PR parses
--hash=<algo>:<digest>(and#<algo>=<digest>URL fragments)for all
hashlibalgorithms, matches those pins against the index metadata,and verifies downloads with the pinned digest.
Fixes #3972.
Before / after behavior
sha256ctx.download(integrity = ...)integrityuv_lockentry with a non-sha256hashsha256(download fails)integrityDesign
Hash representation
A new private module
python/private/pypi/hashes.bzlcentralizes hashhandling:
HASH_ALGOS— the accepted algorithm names (hashlib.algorithms_guaranteed,the set PEP 691 defines as valid and PEP 503 recommends for fragments).
Unknown algorithms are still dropped rather than propagated.
hex_to_sri(algo, hex_digest)/integrity_from_hashes(hashes)— pureStarlark hex→base64 conversion to the Subresource Integrity format accepted
by
ctx.download(integrity = ...)(sha256/sha384/sha512 only, strongestpreferred).
preferred_digest(hashes)— picks a stable hex digest for e.g. repo naming.Two representations are used, chosen to fit each side of the match:
index_sources()):hashes: list[str]of"<algo>:<digest>"strings, replacing the oldshas: list[str]of baresha256 digests. A requirement can pin several files, possibly with mixed
algorithms, so a flat list keyed by nothing is the right shape.
parse_simpleapi_html()dists,parse_requirements()srcentries):
hashes: dict[str, str]mapping algorithm → hex digest. Oneartifact has at most one digest per algorithm, and this shape is
forward-compatible with the multi-digest PEP 691
hashesdict.The existing
sha256fields keep their exact semantics everywhere (""whenno sha256 digest is known) so the common path is untouched.
Matching
parse_simpleapi_html()keys thewhls/sdistsdicts by the bare hex digestfrom the URL fragment, whatever its algorithm (previously all non-sha256
dists collapsed onto the
""key)._add_dists()strips the<algo>:prefixfrom each pin and looks the digest up directly. Digests of different
algorithms have different lengths, so cross-algorithm key collisions are not a
concern, and no dual-key index is needed.
Matching therefore succeeds whenever the index advertises a digest with the
same algorithm as the pin. When it does not, the requirement now falls back to
pip with the pins preserved in the requirement line (previously they were
stripped), so the install is still verified — just without the bazel
downloader.
Download verification
whl_library(bothwhl_archiveandpip_archive) gains anintegrityattribute (SRI string). The hub builder sets it only when
sha256is emptyand a convertible digest is available, so
sha256andintegrityaremutually exclusive by construction and existing lock outputs are byte-for-byte
unchanged for sha256 requirements.
Lockfile facts (
MODULE.bazel.lock)dist_hashesvalues are now either a bare sha256 hex digest (unchanged legacyform) or
"<algo>:<digest>"for other algorithms;dist_yankedis keyed bythe same stored value.
fact_versionstays atv1: old facts parseidentically under the new reader, and non-sha256 dists never round-tripped
before this change, so there is nothing to migrate.
This also fixes a latent facts bug:
dist_filenamesis written keyed by thedistribution URL but was read back keyed by the digest, so stored filenames
(e.g. for URL-encoded wheel names) were never found and the reader always fell
back to deriving the filename from the URL.
API changes
whl_library/whl_archive/pip_archive: newintegritystringattribute (documented,
:::{versionadded}). Only used whenurlsis setand
sha256is empty.python/private/pypi, not public API):index_sources()returnshashes("algo:digest"strings) instead ofshas.parse_simpleapi_html()dist structs andparse_requirements()srcentries gain a
hashesdict; dict keys ofwhls/sdistsare theadvertised digest of any algorithm.
hashes.bzlmodule as described above.Out of scope
Requesting and parsing the PEP 691 JSON Simple API (whose
hashesdict cancarry several algorithms per file) would let pins match even when the HTML
fragment uses a different algorithm; that is left as a follow-up feature.
Testing
tests/pypi/hashes(SRI conversion vectors), non-sha256cases in
index_sources,parse_simpleapi_html,parse_requirements(requirements +
uv_lockpaths),hub_builder(end-to-endintegrityarg), and a
pypi_cachefacts round-trip.bazel test //tests/pypi/...— 237/237 pass.🤖 Generated with Claude Code