Skip to content

[BUG] requirements.txt: any version specifier is treated as an exact pin (pillow>=10.0.0 reported as pillow==10.0.0 with its CVEs) #294

Description

@Mark2Mac

Description

_extract_packages_from_requirements keeps the version string for any version specifier, so a lower bound like pillow>=10.0.0 is treated as if it were the exact pin pillow==10.0.0. The CVEs of that floor version are then attributed to the dependency, producing a CRITICAL finding for a requirements file that does not pin anything.

The same file already contains the correct logic in another extractor, which is what makes this look like an oversight rather than a design choice.

Reproduction

$ cat requirements.txt
pillow>=10.0.0

$ skillspector scan . --no-llm --format json -o out.json
$ jq -r '.issues[] | select(.category=="Supply Chain") | "[\(.severity)] \(.finding)"' out.json
[LOW] pillow>=10.0.0
[CRITICAL] pillow==10.0.0

Both findings come from line 1. The LOW one is correct — the dependency is unpinned. The CRITICAL one reports a pin that does not exist in the file.

Version: SkillSpector v2.3.13.

Cause

skillspector/nodes/analyzers/static_patterns_supply_chain.py:429

m = re.match(r"^([a-zA-Z][a-zA-Z0-9._-]*)(?:\[.*?\])?\s*(?:([=<>!~]=?)\s*([\d.*]+))?", line)
if m:
    name = m.group(1)
    version = m.group(3) if m.group(2) else None   # <-- any operator counts as a pin

The regex captures the operator in group 2, but the guard only checks that some operator matched. >=, != and ~= all fall through as exact versions.

Line 497 of the same file, in _extract_packages_from_setup_py, gets it right:

version = m.group(3) if m.group(2) in ("==", "<=") else None

Suggested fix

Apply the same guard at line 429:

version = m.group(3) if m.group(2) in ("==", "<=") else None

<= is arguably also not a pin, but keeping the two extractors consistent seems like the right first step; tightening both is a separate decision.

Impact

Any requirements.txt that uses floors — the common convention for libraries — generates false CRITICAL supply-chain findings that never go away, since the "fix" the report implies (pin to a newer version) is not what the file was expressing. In a batch of 46 findings I triaged across three skill collections, this produced the only CRITICAL in the set, and it was a false positive.

Related

#233 covers a different defect in the same area (OSV queried without a version specifier, so historical CVEs surface even for an exact pin that is clean). This one is upstream of that: the specifier operator is misread before the query is built. Fixing #233 alone would not fix this case, since the analyzer would still be asking about the wrong version.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions