Skip to content

semver: reject leading zeroes and empty pre-release/build - #9004

Open
sueun-dev wants to merge 4 commits into
open-policy-agent:mainfrom
sueun-dev:fix-semver-strict-numeric-ids
Open

semver: reject leading zeroes and empty pre-release/build#9004
sueun-dev wants to merge 4 commits into
open-policy-agent:mainfrom
sueun-dev:fix-semver-strict-numeric-ids

Conversation

@sueun-dev

Copy link
Copy Markdown
Contributor

What

semver.Parse (behind the semver.is_valid and semver.compare built-ins) accepts several strings that are not valid SemVer 2.0.0 versions:

semver.is_valid("01.2.3")         # true, should be false
semver.is_valid("1.02.3")         # true
semver.is_valid("1.2.3-01")       # true  (leading zero in a numeric pre-release id)
semver.is_valid("1.2.3-")         # true  (empty pre-release)
semver.is_valid("1.2.3+")         # true  (empty build metadata)
semver.compare("1.02.3", "1.2.3") # 0, i.e. treated as equal

The major/minor/patch numbers are parsed straight through strconv.ParseInt, which happily accepts a leading zero, and the pre-release/metadata identifier check is skipped whenever the section is empty (if v.PreRelease != ""). The spec forbids leading zeroes in the numeric identifiers and requires a non-empty pre-release/build section after the -/+.

semver.is_valid is documented as "Validates that the input is a valid SemVer string", so returning true here is wrong, and semver.compare silently treats a malformed version as an ordinary one instead of erroring the way it already does for e.g. 1.2.

Fix

  • Validate major/minor/patch against 0|[1-9][0-9]* before ParseInt.
  • Run the identifier check whenever the -/+ separator is present (an empty section is now rejected), using strings.Cut for the found flag.
  • Reject a leading zero in a numeric pre-release identifier. Build-metadata identifiers keep allowing leading zeroes, which the spec permits.

reMetaIdentifier already rejects other spec violations (e.g. _), so this brings the numeric-identifier handling in line with that.

Test

Added the new invalid strings to TestBadInput (they parsed cleanly before, error now) and a TestGoodInput covering the spec-valid edge cases that must keep parsing — a single-zero pre-release (1.2.3-0), an alphanumeric id starting with zero (1.2.3-0a), and build metadata with a leading zero (1.2.3+01). go test ./internal/semver/ and ./v1/topdown/ pass.

semver.Parse accepted several strings that are not valid SemVer 2.0.0
versions, so semver.is_valid returned true for them and semver.compare
treated them as ordinary versions:

  - a leading zero in the major, minor or patch number ("01.2.3",
    "1.02.3")
  - a leading zero in a numeric pre-release identifier ("1.2.3-01")
  - an empty pre-release or build-metadata section ("1.2.3-", "1.2.3+")

The core numbers were parsed straight through strconv.ParseInt, which
accepts leading zeroes, and the pre-release/metadata identifier check was
skipped whenever the section was empty. The spec forbids all of these.

Validate the major/minor/patch numbers against 0|[1-9][0-9]* before
ParseInt, run the identifier check whenever the separator is present
(rejecting an empty section), and reject a leading zero in numeric
pre-release identifiers. Build-metadata identifiers keep allowing leading
zeroes, as the spec permits. Adds the new invalid cases to TestBadInput
and a TestGoodInput to guard the spec-valid edge cases.

Signed-off-by: Sueun Cho <sueun.dev@gmail.com>

@charlieegan3 charlieegan3 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for this, two small comments. Some of the checks are failing too, please take a look when you have a moment.

Comment thread internal/semver/semver.go Outdated

version, v.Metadata = cut(version, '+')
if v.Metadata != "" && !reMetaIdentifier.MatchString(v.Metadata) {
var found bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var found bool
var foundMetadata bool

Comment thread internal/semver/semver.go
// parseNumeric parses a major, minor or patch identifier, rejecting the empty
// string, a sign or a leading zero (all forbidden by SemVer 2.0.0) before
// converting to int64.
func parseNumeric(s string) (int64, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps not using a regex might be faster here


  func parseNumeric(s string) (int64, error) {
        if s == "" || s[0] == '+' || s[0] == '-' || (len(s) > 1 && s[0] == '0') {
                return 0, fmt.Errorf("%q is not a valid numeric identifier", s)
        }
        return strconv.ParseInt(s, 10, 64)
  }


@charlieegan3 charlieegan3 changed the title internal/semver: reject leading zeroes and empty pre-release/build semver: reject leading zeroes and empty pre-release/build Aug 13, 2026
Signed-off-by: Sueun Cho <sueun.dev@gmail.com>
Signed-off-by: Sueun Cho <sueun.dev@gmail.com>
…eric-ids

Signed-off-by: Sueun Cho <sueun.dev@gmail.com>

# Conflicts:
#	internal/semver/semver.go
@netlify

netlify Bot commented Sep 2, 2026

Copy link
Copy Markdown

Deploy Preview for openpolicyagent ready!

Name Link
🔨 Latest commit b293207
🔍 Latest deploy log https://app.netlify.com/projects/openpolicyagent/deploys/6a9831cb2d0e5700085c3743
😎 Deploy Preview https://deploy-preview-9004--openpolicyagent.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants