Open-Source Wikis

/

Grype

/

Systems

/

Version comparators

anchore/grype

Version comparators

Active contributors: Alex Goodman, Will Murphy, Christopher Phillips

Purpose

Different ecosystems have wildly different version semantics: 1.2.3-alpha is a pre-release in semver but a post-release in Debian. RPM has a numeric epoch. Python distinguishes 1.0.0.dev1 from 1.0.0a1. APK has its own modifier semantics. The version package centralizes all of these comparators behind a single Comparator interface.

Directory layout

grype/version/
├── version.go             # Version + Format
├── format.go              # Format enum and detection
├── comparator.go          # Comparator interface
├── constraint.go          # Constraint
├── range.go               # Range + range_expression
├── range_expression.go
├── set.go                 # SetOperator
├── operator.go            # Operator (=, >, <, >=, <=, !=)
├── error.go
├── deprecated.go
│
├── apk_version.go         # Alpine apk
├── deb_version.go         # Debian dpkg
├── rpm_version.go         # RPM
├── semantic_version.go    # SemVer (npm, cargo, ...)
├── gem_version.go         # Ruby gems
├── maven_version.go       # Maven
├── pep440_version.go      # Python PEP 440
├── golang_version.go      # Go modules (semver-ish + pseudo-versions)
├── jvm_version.go         # JVM (Oracle versioning, e.g. 1.8.0_362)
├── pacman_version.go      # Arch pacman
├── portage_version.go     # Gentoo portage
├── kb_version.go          # Microsoft KB articles
├── bitnami_version.go     # Bitnami packaging
├── fuzzy_version.go       # last-resort fuzzy comparison
└── combined_constraint.go # AND/OR composition

The Comparator interface

grype/version/comparator.go:

type Comparator interface {
    Compare(other *Version) (int, error)
}

Each *_version.go file defines a struct implementing this interface, with New<Format>Version(raw) constructors and validation. The Version value (in version.go) holds the raw string plus a Format, and lazily constructs the appropriate comparator.

Constraint evaluation

Constraint (constraint.go) is the matching primitive: given a version string and a list of constraint expressions like >= 1.2.3, < 1.3.0, it returns whether the version satisfies all constraints.

Constraints come in several shapes:

  • generic_constraint.go — operator + version, e.g. >= 1.2.3. The default for most formats.
  • combined_constraint.go — AND/OR composition of sub-constraints.
  • range.go / range_expression.go — interval syntax shared by Java/Maven and others.
  • fuzzy_constraint.go — best-effort comparison when no specific format is known.
  • kb_constraint.go — Microsoft KB-specific (numeric KB id ordering).

The matcher pipeline asks the version package: "does package p's version satisfy this affected-version constraint?" If yes, it's a vulnerability hit; otherwise no.

Format detection

format.go::FormatFromPkgURL and friends convert from PURL and Syft package types to a Format value. The selected format determines which comparator and constraint parser is used. version_format.go in grype/pkg/ is the higher-level call that chooses the right format from a pkg.Package.

Why so many?

Version comparison is one of the highest-leverage correctness concerns in a scanner. Each ecosystem hides corner cases:

  • APK suffix sorting is different from semver pre-release sorting.
  • Debian has the upstream-version + debian-revision split with ~ for pre-releases.
  • RPM has a leading numeric epoch separated by :.
  • Python PEP 440 has post-releases, dev-releases, and local-version segments (+local).
  • Maven ranges include hard [/] and soft (/) boundaries with their own ordering rules.
  • Gem RubyGems uses dot-separated comparators that are semver-similar but not quite.
  • Go modules have semver + +incompatible + pseudo-versions like v0.0.0-20190101000000-abcdef.

The corresponding test files are extensive — deb_version_test.go is 17 KLoC, rpm_version_test.go is 14 KLoC, all encoding subtle edge cases. The wisdom of past CVE triage lives in those tables.

External libraries used

Wherever possible, comparators wrap a battle-tested upstream library:

  • knqyf263/go-apk-version — APK.
  • knqyf263/go-deb-version — Debian.
  • aquasecurity/go-pep440-version — PEP 440.
  • bitnami/go-version — Bitnami.
  • masahiro331/go-mvn-version — Maven.
  • Masterminds/semver/v3 — semver.
  • anchore/go-version — generalized fork.
  • pandatix/go-cvss — used downstream (CVSS, not strictly versions).

Key abstractions

Symbol Where Description
version.Version grype/version/version.go Raw + Format.
version.Format grype/version/format.go Enum of supported formats.
version.Comparator grype/version/comparator.go The compare-two-versions interface.
version.Constraint grype/version/constraint.go Constraint expression evaluator.
version.New<Fmt>Version(raw) per *_version.go Constructor for a format-specific version.

Key source files

File Purpose
grype/version/version.go Public Version type.
grype/version/comparator.go Comparator interface.
grype/version/constraint.go Constraint evaluation.
grype/version/format.go Format enum + detection.
grype/version/<ecosystem>_version.go Per-ecosystem comparator.

Entry points for modification

  • Bug in version comparison — fix in the relevant *_version.go and add a row to its _test.go table. Almost every comparator regression has been driven by table-row additions.
  • New ecosystem — add a <ecosystem>_version.go and <ecosystem>_constraint.go (or reuse generic_constraint), add a Format enum value, and wire it in version_format.go (in grype/pkg/) and format.go.
  • Constraint syntax change — modify constraint.go and the relevant *_constraint.go. Fuzzy and Maven are the trickiest.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Version comparators – Grype wiki | Factory