Skip to content

[BUG] ComparablePackageURL omits the name, so purls differing only by name compare equal #1021

Description

@arpitjain099

Description

ComparablePackageURL in cyclonedx/_internal/compare.py builds its comparison tuple from type, namespace, version, qualifiers and subpath, but not name:

return super().__new__(cls, (
    p.type,
    p.namespace,
    p.version,
    ComparableDict(p.qualifiers) if isinstance(p.qualifiers, dict) else p.qualifiers,
    p.subpath
))

So two different packages compare equal whenever everything except the name matches:

>>> ComparablePackageURL(PackageURL(type="pypi", name="foo", version="1.0.0")) \
...     == ComparablePackageURL(PackageURL(type="pypi", name="bar", version="1.0.0"))
True

Both produce the tuple ("pypi", None, "1.0.0", (), None).

name is a required component of a purl and is the main thing that distinguishes one package from another, so leaving it out makes the comparison much weaker than intended. Anything that sorts or compares components by purl treats unrelated packages as interchangeable, and ordering between them is then decided by whatever the sort algorithm happens to do, which is not stable across inputs.

Expected

name participates in the comparison, in its canonical purl position between namespace and version.

I have a fix and a regression test ready and will open a PR referencing this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions