Safeguard
Developer Tools•

Why does the same Python package have different names in two scans?

Python treats Friendly-Bard and friendly_bard as one package. Here is the rule, why scanners can mismatch on it, and how to normalize before comparing.

Safeguard Security Team
3 min read

Python package names are compared after normalization: lowercase the name, then replace each run of dots, dashes or underscores with one dash. So Friendly.Bard, friendly_bard and FRIENDLY-BARD are the same package. A scanner that compares raw strings can report a mismatch where none exists.

The rule, exactly

The Python Packaging specification defines the normalization with one line of code.

import re

def normalize(name):
    return re.sub(r"[-_.]+", "-", name).lower()

The specification lists examples that all normalize to friendly-bard, including friendly.bard, friendly_bard, a name with doubled dashes and a mixed case version with punctuation in the middle.

Why scanners can disagree because of it

Different files spell the same name differently. A requirements file might say Django, a lock file might say django, and an installed package record might say something else again. An advisory entry can use a third spelling.

If a scanner matches an advisory to your package with an exact string comparison, one spelling will match and another will not. Two tools reading two files can then produce two different answers for the same package.

The OSV schema helps here, because it requires both an ecosystem and a name in each affected entry, and says the ecosystem defines how the name is interpreted. A tool that respects that can apply the right rule per ecosystem.

How to check whether this is your cause

  1. Take the package one tool reported and the other did not.
  2. Write down every spelling that appears in your repo for it: manifest, lockfile, installed environment.
  3. Normalize each with the function above.
  4. If they collapse to one name, the mismatch is a naming artifact, not a coverage gap.
  5. Check the advisory entry spelling the same way.

If the names still differ after normalization, the problem is elsewhere, usually the version or the file each tool read.

Other ecosystems are not the same

Do not apply the Python rule everywhere. Each ecosystem has its own naming conventions, which is the reason purl types exist. The Package-URL introduction says each purl type gives a standard way to identify packages according to that ecosystem's conventions. When you build a comparison key, apply the rule for the ecosystem in the row, and leave other ecosystems alone until you have read their rules.

SituationAction
Python rowsApply the normalization function
Other ecosystemsUse that ecosystem's documented rule
UnsureCompare on purl from both tools if available

A small habit that prevents the problem

When you export results from any scanner for comparison, add a normalized name column and keep the original beside it. You keep the evidence and you get a reliable join. It also makes later audits easier, because every row can be traced back to the exact spelling in the source file.

One more tip: when you report the result to someone else, say which spelling you used as the key. A reviewer who sees two spellings in two tools will otherwise assume you are counting a package twice.

Quick check

Is Friendly-Bard a different package from friendly_bard on PyPI?

No. Under the normalization rule they identify the same project.

Does lowercasing alone fix the problem?

No. Dots, dashes and underscores must also be collapsed to a single dash, or names like friendly.bard will still not match.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.