Version semantics
Version comparison is what makes vulnerability matching and banned_versions correct, and every ecosystem orders
versions differently. depaudit implements each ecosystem's rules itself (pep440.py, semver.py, dispatched by
versions.py).
| Ecosystem | Versions | Ranges |
|---|---|---|
| PyPI | PEP 440 | PEP 440 specifiers (>=1,<2, ~=2.1) |
| npm | semver 2.0 | node-semver ranges (^, ~, x, -, \|\|) |
| crates.io | semver 2.0 | Cargo requirements (1.2, >=1, <2) |
| Go | semver + pseudo | npm-style ranges on the stripped version |
PEP 440
A version is [N!]N(.N)*[{a|b|rc}N][.postN][.devN][+local]. Parsing accepts the normalisation aliases (alpha, c,
pre, -1 for post, rev, separators -_., a leading v, upper case).
Ordering key (compared left to right):
- epoch
- release tuple with trailing zeros stripped (
1.0 == 1.0.0) - pre-release phase:
dev-only <a<b<rc< final - post-release number (none <
.post0) - dev number (a
.devNsorts before the same version without it) - local label (none < any local; numeric segments > alphanumeric ones)
So 1.0.dev1 < 1.0a1.dev1 < 1.0a1 < 1.0rc1 < 1.0 < 1.0+abc < 1.0.post1.dev1 < 1.0.post1 < 1.0.1 < 1!0.1.
Specifiers
| Operator | Rule |
|---|---|
==V, !=V |
public version equality: the candidate's local label is ignored unless V has one. ==1.2.* is a prefix match on the release segment. |
<=V, >=V |
compare the candidate's public version |
<V |
candidate < V, and a pre-release of V itself is not matched unless V is a pre-release (2.0rc1 is not <2.0) |
>V |
candidate > V, but not a post-release of V (unless V is one) and not a local version of V |
~=V |
>=V and ==prefix.* where the prefix is V without its last segment (~=2.1.5 means >=2.1.5, ==2.1.*) |
===V |
case-insensitive string equality |
Pre-release candidates are excluded unless some clause names a pre-release (pip's default). Callers can force
inclusion; banned_versions does.
Worked example: 2.0rc1 against <2.0 is false (it is a pre-release of the bound), against <2.0rc2 true,
against >=1.0 false (pre-releases are not offered by default), against >=2.0rc1 true.
Semver (npm, crates.io, Go)
Precedence: compare major.minor.patch numerically; a version with a pre-release is lower than the same version
without; pre-release identifiers are compared left to right, numeric identifiers numerically and lower than alphanumeric
ones, and a shorter list is lower when all shared identifiers are equal. Build metadata (+build) is ignored.
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
npm ranges
Each comparator set is desugared to primitive comparators:
| Range | Means |
|---|---|
^1.2.3 |
>=1.2.3 <2.0.0-0 |
^0.2.3 |
>=0.2.3 <0.3.0-0 |
^0.0.3 |
>=0.0.3 <0.0.4-0 |
~1.2.3, ~1.2 |
>=1.2.3 <1.3.0-0, >=1.2.0 <1.3.0-0 |
~1 / 1.x / 1 |
>=1.0.0 <2.0.0-0 |
1.2.3 - 2.3 |
>=1.2.3 <2.4.0-0 |
>1.2 |
>=1.3.0 |
<=1.2 |
<1.3.0-0 |
*, x, empty |
>=0.0.0 |
A \|\| B |
either set |
The upper bounds are written as -0 pre-releases so that pre-releases of the excluded version are excluded too
(2.0.0-beta is not in ^1.2.3).
Pre-release rule: a pre-release version satisfies a comparator set only if some comparator in that set has a
pre-release on the same major.minor.patch. So 1.2.3-beta.2 satisfies ^1.2.3-beta.1 but not ^1.2.0, and
1.2.4-beta.2 does not satisfy ^1.2.3-beta.1.
Cargo requirements
Comma-separated comparators, ANDed. A bare version is a caret requirement (1.5 is ^1.5), then the npm desugaring
above applies, including the pre-release rule.
Go
Module versions are semver with a v prefix. +incompatible is stripped. Pseudo-versions
(v0.0.0-20190308221718-c2843e01d9a2, v1.2.4-0.20200101000000-abcdefabcdef) are semver pre-releases, so ordering
falls out of the rules above: a pseudo-version for a commit after v1.2.3 is > v1.2.3 and < v1.2.4. OSV Go
ranges (written without the v) therefore work unchanged:
installed v0.0.0-20190308221718-c2843e01d9a2 (x/crypto)
fixed 0.0.0-20200220183623-bac4c82f6975
=> installed < fixed, so the advisory applies and the fix suggestion is the pseudo-version
OSV range evaluation
An OSV affected entry has ranges of events (introduced, fixed, last_affected) and/or an explicit versions
list. depaudit:
- matches the installed version against the explicit list using the ecosystem's equality (
1.0 == 1.0.0on PyPI); - sorts each range's events by version (
introduced: "0"is the minimum) and turns them into intervals[introduced, fixed)or[introduced, last_affected]; a trailingintroducedis open-ended; - reports the
fixedevent of the interval that contains the version.
Only ECOSYSTEM and SEMVER range types are evaluated; GIT ranges need commit history and are skipped.
Example, minimist: [0, 0.2.4) and [1.0.0, 1.2.6). 1.2.5 is in the second interval (fixed in 1.2.6); 0.5.0
is in neither interval, so it is not affected.
Minimal upgrade suggestion
For a vulnerable package, the candidate versions are the fixed versions of every advisory in the snapshot for
that package that are newer than the installed version (pre-releases only when the installed one is a pre-release). The
suggestion is the smallest candidate that no advisory in the snapshot affects, which avoids suggesting a version that
fixes one advisory but lands inside the range of another. The report flags the suggestion (major) when the major
version changes (for 0.x versions the first segment is compared as-is).