Skip to content

Link hashes are parsed from the query string, not only the URL fragment #14315

Description

@yonnnxr

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:

  1. _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.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions