LinkHash.find_hash_url_fragment() searches the whole URL for
[#&](algo)=value, so a query-string parameter named after a hash algorithm is
read as a hash:
>>> from pip._internal.models.link import Link
>>> Link("https://example.com/pkg.whl?token=x&sha256=" + "a" * 64).has_hash
True
This only triggers when the parameter is not the first one: ?sha256=... alone
does not match, since the separator has to be # or &.
Two things suggest this is not intended:
_clean_link(), which decides whether two links are equivalent, reads only
the fragment (urllib.parse.parse_qs(parsed.fragment)). The same URL
therefore counts as hashed for pinning and as unhashed for equivalence.
- The regex is named
_hash_url_fragment_re; news/11936.bugfix.rst describes
the change that introduced [#&] as "Fix and improve the parsing of hashes
embedded in URL fragments"; and every case in test_link_hash_parsing uses
#. The & alternative covers &-separated parameters inside the fragment
(#subdirectory=setup&sha256=...), not a query.
Consequence: for a direct, user-supplied requirement,
InstallRequirement.hashes(trust_internet=False) includes the link's hash, so a
query value counts towards --require-hashes. Reproduced with the CLI against a
locally built wheel (no network):
$ cat req_query.txt
pkg @ file:///tmp/pkg-1.0.0-py3-none-any.whl?token=x&sha256=<digest>
$ python -m pip install --dry-run --no-index --no-deps --require-hashes -r req_query.txt
Processing /tmp/pkg-1.0.0-py3-none-any.whl
Would install pkg-1.0.0
$ cat req_nohash.txt
pkg @ file:///tmp/pkg-1.0.0-py3-none-any.whl
$ python -m pip install --dry-run --no-index --no-deps --require-hashes -r req_nohash.txt
ERROR: Hashes are required in --require-hashes mode, but they are missing from some requirements.
Per SECURITY.md this looks like a normal parser bug rather than a security
report, so I'm filing it here.
Is this divergence worth fixing? A patch that searches only
urlsplit(url).fragment keeps every documented form working, including
#subdirectory=setup&sha256=..., and makes the parser agree with _clean_link.
Happy to open a PR if so.
LinkHash.find_hash_url_fragment()searches the whole URL for[#&](algo)=value, so a query-string parameter named after a hash algorithm isread as a hash:
This only triggers when the parameter is not the first one:
?sha256=...alonedoes not match, since the separator has to be
#or&.Two things suggest this is not intended:
_clean_link(), which decides whether two links are equivalent, reads onlythe fragment (
urllib.parse.parse_qs(parsed.fragment)). The same URLtherefore counts as hashed for pinning and as unhashed for equivalence.
_hash_url_fragment_re;news/11936.bugfix.rstdescribesthe change that introduced
[#&]as "Fix and improve the parsing of hashesembedded in URL fragments"; and every case in
test_link_hash_parsinguses#. The&alternative covers&-separated parameters inside the fragment(
#subdirectory=setup&sha256=...), not a query.Consequence: for a direct, user-supplied requirement,
InstallRequirement.hashes(trust_internet=False)includes the link's hash, so aquery value counts towards
--require-hashes. Reproduced with the CLI against alocally built wheel (no network):
Per
SECURITY.mdthis looks like a normal parser bug rather than a securityreport, so I'm filing it here.
Is this divergence worth fixing? A patch that searches only
urlsplit(url).fragmentkeeps every documented form working, including#subdirectory=setup&sha256=..., and makes the parser agree with_clean_link.Happy to open a PR if so.