From 41aacc18467244cf03b020a83ad17bc9bebddfe1 Mon Sep 17 00:00:00 2001 From: adam jones Date: Wed, 10 Sep 2025 10:52:08 -0700 Subject: [PATCH] Ban 'latest' as a reserved version string (#413) ## Summary - Adds validation to prevent servers from being published with version string "latest" - "latest" is now a reserved keyword that cannot be used as a version string ## Implementation - Added `ErrReservedVersionString` error constant - Created `validateVersion` function to check for reserved version strings - Integrated validation into the package field validation flow - Added comprehensive test coverage --- This is so we can potentially support things like https://github.com/modelcontextprotocol/registry/discussions/333 in future more easily, as I imagine we might want to. --- internal/validators/constants.go | 1 + internal/validators/validators.go | 25 ++++++++++++++++++++----- internal/validators/validators_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/internal/validators/constants.go b/internal/validators/constants.go index fa5392c..d3079a9 100644 --- a/internal/validators/constants.go +++ b/internal/validators/constants.go @@ -10,6 +10,7 @@ var ( // Package validation errors ErrPackageNameHasSpaces = errors.New("package name cannot contain spaces") + ErrReservedVersionString = errors.New("version string 'latest' is reserved and cannot be used") // Remote validation errors ErrInvalidRemoteURL = errors.New("invalid remote URL") diff --git a/internal/validators/validators.go b/internal/validators/validators.go index d61cd59..d4a07f0 100644 --- a/internal/validators/validators.go +++ b/internal/validators/validators.go @@ -72,6 +72,11 @@ func validatePackageField(obj *model.Package) error { return ErrPackageNameHasSpaces } + // Validate version string + if err := validateVersion(obj.Version); err != nil { + return err + } + // Validate runtime arguments for _, arg := range obj.RuntimeArguments { if err := validateArgument(&arg); err != nil { @@ -95,6 +100,16 @@ func validatePackageField(obj *model.Package) error { return nil } +// validateVersion validates the version string +// NB: we decided that we would not enforce strict semver for version strings +func validateVersion(version string) error { + if version == "latest" { + return ErrReservedVersionString + } + + return nil +} + // validateArgument validates argument details func validateArgument(obj *model.Argument) error { if obj.Type == model.ArgumentTypeNamed { @@ -144,12 +159,12 @@ func validateArgumentValueFields(name, value, defaultValue string) error { // collectAvailableVariables collects all available template variables from a package func collectAvailableVariables(pkg *model.Package) []string { var variables []string - + // Add environment variable names for _, env := range pkg.EnvironmentVariables { variables = append(variables, env.Name) } - + // Add runtime argument names and value hints for _, arg := range pkg.RuntimeArguments { if arg.Name != "" { @@ -159,7 +174,7 @@ func collectAvailableVariables(pkg *model.Package) []string { variables = append(variables, arg.ValueHint) } } - + // Add package argument names and value hints for _, arg := range pkg.PackageArguments { if arg.Name != "" { @@ -169,7 +184,7 @@ func collectAvailableVariables(pkg *model.Package) []string { variables = append(variables, arg.ValueHint) } } - + return variables } @@ -193,7 +208,7 @@ func validatePackageTransport(transport *model.Transport, availableVariables []s // Check if it's a template variable issue or basic URL issue templateVars := extractTemplateVariables(transport.URL) if len(templateVars) > 0 { - return fmt.Errorf("%w: template variables in URL %s reference undefined variables. Available variables: %v", + return fmt.Errorf("%w: template variables in URL %s reference undefined variables. Available variables: %v", ErrInvalidRemoteURL, transport.URL, availableVariables) } return fmt.Errorf("%w: %s", ErrInvalidRemoteURL, transport.URL) diff --git a/internal/validators/validators_test.go b/internal/validators/validators_test.go index bbeb1d8..5a3d5e2 100644 --- a/internal/validators/validators_test.go +++ b/internal/validators/validators_test.go @@ -193,6 +193,30 @@ func TestValidate(t *testing.T) { }, expectedError: validators.ErrPackageNameHasSpaces.Error(), }, + { + name: "package with reserved version 'latest'", + serverDetail: apiv0.ServerJSON{ + Name: "com.example/test-server", + Description: "A test server", + Repository: model.Repository{ + URL: "https://github.com/owner/repo", + Source: "github", + }, + Version: "1.0.0", + Packages: []model.Package{ + { + Identifier: "test-package", + RegistryType: "npm", + RegistryBaseURL: "https://registry.npmjs.org", + Version: "latest", + Transport: model.Transport{ + Type: "stdio", + }, + }, + }, + }, + expectedError: validators.ErrReservedVersionString.Error(), + }, { name: "multiple packages with one invalid", serverDetail: apiv0.ServerJSON{