package validator import ( "encoding/hex" "fmt" "net" "net/url" "regexp" "strings" "browser.local/platform/domain" ) var ( runtimeDLLModKeyPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9_-]{0,79}$`) runtimeDLLABIPattern = regexp.MustCompile(`^[A-Za-z0-9._-]{1,80}$`) ) func ValidateGamePluginRuntimeProfiles(profiles domain.GamePluginRuntimeProfiles) error { profiles = domain.CopyGamePluginRuntimeProfiles(profiles) var violations []string lifecycleKeys := map[string]struct{}{} transportKeys := map[string]struct{}{} transportProfiles := map[string]domain.RuntimeTransportProfile{} dataTargetKeys := map[string]struct{}{} dataTargetWorkspaces := map[string]struct{}{} dllExtensionKeys := map[string]struct{}{} dllExtensionStates := map[string]string{} discoveryKeys := map[string]struct{}{} dependencyKeys := map[string]struct{}{} installPlanKeys := map[string]struct{}{} serverDeploymentKeys := map[string]struct{}{} logSourceKeys := map[string]struct{}{} for i, probe := range profiles.Discovery { prefix := fmt.Sprintf("runtimeProfiles.discovery[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", probe.Key)...) violations = append(violations, recordRuntimeProfileKey(discoveryKeys, prefix+".key", probe.Key)...) violations = append(violations, validateProfileKey(prefix+".targetKey", probe.TargetKey)...) if !oneOf(probe.Kind, "file.exists", "command.version", "service.status", "port.open", "steam.app", "docker.container") { violations = append(violations, prefix+".kind is invalid") } violations = append(violations, validateRuntimePlatforms(prefix+".platforms", probe.Platforms)...) violations = append(violations, validateSafeRuntimeValue(prefix+".expected", probe.Expected)...) } for i, profile := range profiles.LifecycleProfiles { prefix := fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", profile.Key)...) violations = append(violations, recordRuntimeProfileKey(lifecycleKeys, prefix+".key", profile.Key)...) if !oneOf(profile.Mode, "local-process", "hosted-ftp-rcon", "ftp-only") { violations = append(violations, prefix+".mode is invalid") } if len(profile.Capabilities) == 0 { violations = append(violations, prefix+".capabilities must not be empty") } for j, capability := range profile.Capabilities { if !validPluginRunCapability(capability) { violations = append(violations, fmt.Sprintf("%s.capabilities[%d] is not allowed", prefix, j)) } } violations = append(violations, duplicateViolations(prefix+".capabilities", profile.Capabilities)...) violations = append(violations, validateLifecycleActionsOptional(profile.ActionRefs)...) for j, key := range profile.TransportKeys { violations = append(violations, validateProfileKey(fmt.Sprintf("%s.transportKeys[%d]", prefix, j), key)...) } violations = append(violations, duplicateViolations(prefix+".transportKeys", profile.TransportKeys)...) for j, key := range profile.DLLExtensionRefs { violations = append(violations, validateProfileKey(fmt.Sprintf("%s.dllExtensionRefs[%d]", prefix, j), key)...) } violations = append(violations, duplicateViolations(prefix+".dllExtensionRefs", profile.DLLExtensionRefs)...) violations = append(violations, validateRuntimePlatforms(prefix+".platforms", profile.Platforms)...) } for i, probe := range profiles.DependencyProbes { prefix := fmt.Sprintf("runtimeProfiles.dependencyProbes[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", probe.Key)...) violations = append(violations, recordRuntimeProfileKey(dependencyKeys, prefix+".key", probe.Key)...) violations = append(violations, validateProfileKey(prefix+".targetKey", probe.TargetKey)...) if !oneOf(probe.Kind, "command.version", "service.exists", "port.available", "steam.app", "java.version", "docker.available", "package.installed", "file.exists") { violations = append(violations, prefix+".kind is invalid") } violations = append(violations, validateSafeRuntimeValue(prefix+".minimumVersion", probe.MinimumVersion)...) violations = append(violations, validateRuntimePlatforms(prefix+".platforms", probe.Platforms)...) } for i, plan := range profiles.InstallPlans { prefix := fmt.Sprintf("runtimeProfiles.installPlans[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", plan.Key)...) violations = append(violations, recordRuntimeProfileKey(installPlanKeys, prefix+".key", plan.Key)...) violations = appendRequired(violations, prefix+".title", plan.Title) violations = append(violations, validateSafeRuntimeValue(prefix+".title", plan.Title)...) if len(plan.Steps) == 0 { violations = append(violations, prefix+".steps must not be empty") } if len(plan.Steps) > 64 { violations = append(violations, prefix+".steps must not exceed 64") } for j, step := range plan.Steps { stepPrefix := fmt.Sprintf("%s.steps[%d]", prefix, j) if !oneOf(step.Type, "package", "verified-download", "steamcmd-app", "manual") { violations = append(violations, stepPrefix+".type is invalid") } violations = append(violations, validateProfileKey(stepPrefix+".targetKey", step.TargetKey)...) for field, value := range map[string]string{"packageManager": step.PackageManager, "packageName": step.PackageName, "version": step.Version} { violations = append(violations, validateSafeRuntimeValue(stepPrefix+"."+field, value)...) } if step.DownloadRef != "" { parsed, err := url.Parse(step.DownloadRef) host := "" if parsed != nil { host = strings.ToLower(parsed.Hostname()) } ip := net.ParseIP(host) if err != nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil || parsed.Fragment != "" || host == "localhost" || strings.HasSuffix(host, ".localhost") || ip != nil && (ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() || ip.IsLinkLocalUnicast()) { violations = append(violations, stepPrefix+".downloadRef must be a credential-free HTTPS URL") } } if step.Checksum != "" { encoded := strings.TrimPrefix(step.Checksum, "sha256:") if !strings.HasPrefix(step.Checksum, "sha256:") || len(encoded) != 64 { violations = append(violations, stepPrefix+".checksum is invalid") } else if _, err := hex.DecodeString(encoded); err != nil { violations = append(violations, stepPrefix+".checksum is invalid") } } switch step.Type { case "package": if !oneOf(step.PackageManager, "winget", "choco", "scoop", "apt", "yum", "dnf", "pacman", "zypper", "brew") { violations = append(violations, stepPrefix+".packageManager is unsupported for package step") } if !regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.:+@/-]{0,119}$`).MatchString(step.PackageName) { violations = append(violations, stepPrefix+".packageName is invalid") } case "verified-download": if step.DownloadRef == "" || step.Checksum == "" { violations = append(violations, stepPrefix+" requires downloadRef and checksum") } case "steamcmd-app": if step.PackageManager != "" && step.PackageManager != "steamcmd" || !regexp.MustCompile(`^[0-9]{1,12}$`).MatchString(step.PackageName) { violations = append(violations, stepPrefix+" requires a numeric Steam app and steamcmd adapter") } case "manual": if step.DownloadRef != "" || step.Checksum != "" || step.PackageName != "" { violations = append(violations, stepPrefix+" manual step cannot contain machine execution fields") } } } violations = append(violations, validateRuntimePlatforms(prefix+".platforms", plan.Platforms)...) } for i, profile := range profiles.ServerDeployments { prefix := fmt.Sprintf("runtimeProfiles.serverDeployments[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", profile.Key)...) violations = append(violations, recordRuntimeProfileKey(serverDeploymentKeys, prefix+".key", profile.Key)...) if !validSemanticVersion(profile.Version) { violations = append(violations, prefix+".version must be semantic") } if !regexp.MustCompile(`^[0-9]{1,12}$`).MatchString(profile.SteamAppID) { violations = append(violations, prefix+".steamAppId must be numeric") } for field, value := range map[string]string{"executableKey": profile.ExecutableKey, "installRootKey": profile.InstallRootKey, "configKey": profile.ConfigKey} { violations = append(violations, validateProfileKey(prefix+"."+field, value)...) } if profile.ConfigFormat != "ini" && profile.ConfigFormat != "json" && profile.ConfigFormat != "yaml" && profile.ConfigFormat != "properties" { violations = append(violations, prefix+".configFormat is invalid") } if len(profile.SupportedTargets) == 0 { violations = append(violations, prefix+".supportedTargets must not be empty") } prerequisiteKeys := map[string]struct{}{} for j, prerequisite := range profile.Prerequisites { prerequisitePrefix := fmt.Sprintf("%s.prerequisites[%d]", prefix, j) violations = append(violations, validateProfileKey(prerequisitePrefix+".key", prerequisite.Key)...) if _, exists := prerequisiteKeys[prerequisite.Key]; exists { violations = append(violations, prerequisitePrefix+".key duplicates another prerequisite") } prerequisiteKeys[prerequisite.Key] = struct{}{} if !oneOf(prerequisite.Kind, "steamcmd", "windows-vcredist", "windows-directx") { violations = append(violations, prerequisitePrefix+".kind is invalid") } } for j, target := range profile.SupportedTargets { if !validPluginSupportedOS(target.OS) || !oneOf(target.Arch, "amd64", "arm64") { violations = append(violations, fmt.Sprintf("%s.supportedTargets[%d] is invalid", prefix, j)) } } mappingKeys := map[string]struct{}{} for j, mapping := range profile.ConfigMappings { mappingPrefix := fmt.Sprintf("%s.configMappings[%d]", prefix, j) if !regexp.MustCompile(`^[A-Za-z][A-Za-z0-9._/-]{0,79}$`).MatchString(mapping.FieldKey) { violations = append(violations, mappingPrefix+".fieldKey is invalid") } violations = append(violations, validateProfileKey(mappingPrefix+".configKey", mapping.ConfigKey)...) if _, exists := mappingKeys[mapping.FieldKey]; exists { violations = append(violations, mappingPrefix+".fieldKey duplicates another mapping") } mappingKeys[mapping.FieldKey] = struct{}{} if !oneOf(mapping.ValueType, "text", "integer", "number", "boolean", "port") { violations = append(violations, mappingPrefix+".valueType is invalid") } } markerKeys := map[string]struct{}{} for j, marker := range profile.DiscoveryMarkers { markerPrefix := fmt.Sprintf("%s.discoveryMarkers[%d]", prefix, j) violations = append(violations, validateProfileKey(markerPrefix+".key", marker.Key)...) violations = append(violations, validateProfileKey(markerPrefix+".targetKey", marker.TargetKey)...) if _, exists := markerKeys[marker.Key]; exists { violations = append(violations, markerPrefix+".key duplicates another marker") } markerKeys[marker.Key] = struct{}{} if !oneOf(marker.Kind, "file.exists", "command.version", "port.open", "steam.app") { violations = append(violations, markerPrefix+".kind is invalid") } violations = append(violations, validateSafeRuntimeValue(markerPrefix+".expected", marker.Expected)...) } checkKeys := map[string]struct{}{} for j, check := range profile.VerificationChecks { checkPrefix := fmt.Sprintf("%s.verificationChecks[%d]", prefix, j) violations = append(violations, validateProfileKey(checkPrefix+".key", check.Key)...) violations = append(violations, validateProfileKey(checkPrefix+".targetKey", check.TargetKey)...) if _, exists := checkKeys[check.Key]; exists { violations = append(violations, checkPrefix+".key duplicates another check") } checkKeys[check.Key] = struct{}{} if !oneOf(check.Kind, "executable.present", "version.matches", "port.bound", "config.readable", "process.healthy") { violations = append(violations, checkPrefix+".kind is invalid") } } if len(profile.VerificationChecks) == 0 || !containsRequiredVerification(profile.VerificationChecks) { violations = append(violations, prefix+".verificationChecks must include executable, config, port, and process checks") } } for i, source := range profiles.LogSources { prefix := fmt.Sprintf("runtimeProfiles.logSources[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", source.Key)...) violations = append(violations, recordRuntimeProfileKey(logSourceKeys, prefix+".key", source.Key)...) if !oneOf(source.Kind, "process.stdout", "process.stderr", "file.tail", "ftp.poll", "sql.query") { violations = append(violations, prefix+".kind is invalid") } if source.TargetKey != "" { violations = append(violations, validateProfileKey(prefix+".targetKey", source.TargetKey)...) } violations = append(violations, validateProfileKey(prefix+".streamKey", source.StreamKey)...) if source.CursorKind != "" && !oneOf(source.CursorKind, "sequence", "offset", "fingerprint", "ftp-listing", "sql-cursor") { violations = append(violations, prefix+".cursorKind is invalid") } if source.RetentionDays < 0 || source.RetentionDays > 365 { violations = append(violations, prefix+".retentionDays is invalid") } } for i, transport := range profiles.TransportProfiles { prefix := fmt.Sprintf("runtimeProfiles.transportProfiles[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", transport.Key)...) violations = append(violations, recordRuntimeProfileKey(transportKeys, prefix+".key", transport.Key)...) if !oneOf(transport.Kind, "file", "ftp", "rsync", "mysql", "sqlite", "rcon", "program") { violations = append(violations, prefix+".kind is invalid") } if transport.TargetKey != "" { violations = append(violations, validateProfileKey(prefix+".targetKey", transport.TargetKey)...) } if len(transport.Capabilities) == 0 { violations = append(violations, prefix+".capabilities must not be empty") } for j, capability := range transport.Capabilities { if !validPluginRunCapability(capability) { violations = append(violations, fmt.Sprintf("%s.capabilities[%d] is not allowed", prefix, j)) } } violations = append(violations, duplicateViolations(prefix+".capabilities", transport.Capabilities)...) if transport.Key != "" { transportProfiles[transport.Key] = transport } } if len(profiles.DataTargets) > 16 { violations = append(violations, "runtimeProfiles.dataTargets must not exceed 16") } for i, target := range profiles.DataTargets { prefix := fmt.Sprintf("runtimeProfiles.dataTargets[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", target.Key)...) violations = append(violations, recordRuntimeProfileKey(dataTargetKeys, prefix+".key", target.Key)...) violations = append(violations, validateProfileKey(prefix+".transportKey", target.TransportKey)...) violations = append(violations, validateProfileKey(prefix+".sourceRootKey", target.SourceRootKey)...) violations = append(violations, validateSafeRelativeRuntimePath(prefix+".sourcePath", target.SourcePath)...) violations = append(violations, validateProfileKey(prefix+".workspaceKey", target.WorkspaceKey)...) if target.Kind != "sqlite.snapshot" || target.RefreshPolicy != "on-demand-snapshot" || !strings.HasPrefix(target.WorkspaceKey, "databases/") { violations = append(violations, prefix+" must declare an on-demand sqlite snapshot workspace") } if target.MaxBytes < 1 || target.MaxBytes > 1024*1024*1024 { violations = append(violations, prefix+".maxBytes is invalid") } violations = append(violations, validateRuntimePlatforms(prefix+".platforms", target.Platforms)...) if _, exists := dataTargetWorkspaces[target.WorkspaceKey]; exists { violations = append(violations, prefix+".workspaceKey is duplicated") } dataTargetWorkspaces[target.WorkspaceKey] = struct{}{} transport, exists := transportProfiles[target.TransportKey] if !exists || transport.Kind != "sqlite" || transport.TargetKey != target.TransportKey || !containsString(transport.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteQuery) { violations = append(violations, prefix+".transportKey must reference a declared SQLite query transport") } } for i, extension := range profiles.DLLExtensions { prefix := fmt.Sprintf("runtimeProfiles.dllExtensions[%d]", i) violations = append(violations, validateProfileKey(prefix+".key", extension.Key)...) violations = append(violations, recordRuntimeProfileKey(dllExtensionKeys, prefix+".key", extension.Key)...) if extension.Key != "" { dllExtensionStates[extension.Key] = extension.ReleaseState } violations = append(violations, validateRuntimeDLLExtensionProfile(prefix, extension)...) } for i, profile := range profiles.LifecycleProfiles { for _, key := range profile.TransportKeys { if _, ok := transportKeys[key]; !ok { violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d].transportKeys references undeclared transport %q", i, key)) } } if len(profile.DLLExtensionRefs) > 0 { if profile.Mode != "local-process" || !containsString(profile.Capabilities, domain.LifecycleCapabilityStart) || len(profile.Platforms) != 1 || profile.Platforms[0] != "windows" { violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d] DLL extensions require a windows local-process start profile", i)) } for _, key := range profile.DLLExtensionRefs { state, exists := dllExtensionStates[key] if !exists { violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d].dllExtensionRefs references undeclared DLL extension %q", i, key)) continue } if state != "ready" { violations = append(violations, fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d].dllExtensionRefs references unpublished DLL extension %q", i, key)) } } } } return finish(violations) } func containsRequiredVerification(checks []domain.RuntimeServerVerificationCheck) bool { required := map[string]bool{"executable.present": false, "port.bound": false, "config.readable": false, "process.healthy": false} for _, check := range checks { if check.Required { if _, ok := required[check.Kind]; ok { required[check.Kind] = true } } } for _, present := range required { if !present { return false } } return true } func validateRuntimeDLLExtensionProfile(prefix string, extension domain.RuntimeDLLExtensionProfile) []string { var violations []string if extension.Kind != "ue4ss-dll" || extension.Activation != "server-start" { violations = append(violations, prefix+".kind and activation must be ue4ss-dll/server-start") } if !validSemanticVersion(extension.Version) { violations = append(violations, prefix+".version must be semantic") } violations = append(violations, validateSafeRuntimeValue(prefix+".displayName", extension.DisplayName)...) violations = append(violations, validateProfileKey(prefix+".targetKey", extension.TargetKey)...) if !runtimeDLLModKeyPattern.MatchString(extension.ModKey) { violations = append(violations, prefix+".modKey is invalid") } if extension.DLLRef != "ue4ss/Mods/"+extension.ModKey+"/dlls/main.dll" { violations = append(violations, prefix+".dllRef must be the declared UE4SS main.dll path") } if extension.UpdateOnStart != true { violations = append(violations, prefix+".updateOnStart must be true") } if extension.RCONPort < 1024 || extension.RCONPort > 65535 { violations = append(violations, prefix+".rconPort must be an unprivileged port") } if len(extension.SupportedTargets) != 1 || extension.SupportedTargets[0].OS != "windows" || extension.SupportedTargets[0].Arch != "amd64" { violations = append(violations, prefix+".supportedTargets must contain only windows/amd64") } if extension.ReleaseState != "ready" && extension.ReleaseState != "unpublished" { violations = append(violations, prefix+".releaseState is invalid") } if extension.ReleaseURL != "" { violations = append(violations, validateRuntimeDLLReleaseURL(prefix+".releaseUrl", extension.ReleaseURL)...) } if extension.ReleaseState == "ready" { if extension.ReleaseURL == "" { violations = append(violations, prefix+".releaseUrl is required for a ready release") } if !validSHA256Checksum(extension.Checksum) || !validSHA256Checksum(extension.SCUMExecutableChecksum) { violations = append(violations, prefix+".checksum and scumExecutableChecksum must be SHA-256") } if extension.SizeBytes < 1 || extension.SizeBytes > 128*1024*1024 { violations = append(violations, prefix+".sizeBytes is out of bounds") } if !runtimeDLLABIPattern.MatchString(extension.UE4SSABI) { violations = append(violations, prefix+".ue4ssAbi is invalid") } } return violations } func validateRuntimeDLLReleaseURL(field string, value string) []string { parsed, err := url.Parse(value) host := "" if parsed != nil { host = strings.ToLower(parsed.Hostname()) } ip := net.ParseIP(host) if err != nil || parsed == nil || parsed.Scheme != "https" || parsed.Host == "" || parsed.User != nil || parsed.Fragment != "" || parsed.RawQuery != "" || parsed.Port() != "" && parsed.Port() != "443" || host == "localhost" || strings.HasSuffix(host, ".localhost") || strings.HasSuffix(host, ".local") || ip != nil && (ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() || ip.IsLinkLocalUnicast()) || !strings.HasSuffix(strings.ToLower(parsed.Path), ".dll") { return []string{field + " must be a public credential-free HTTPS DLL URL"} } return nil } func validateRuntimeDLLExtensionPlan(prefix string, plan domain.RuntimeDLLExtensionPlan) []string { return validateRuntimeDLLExtensionProfile(prefix, domain.RuntimeDLLExtensionProfile{ Key: plan.Key, Kind: "ue4ss-dll", Activation: "server-start", Version: plan.Version, ReleaseState: "ready", ReleaseURL: plan.ReleaseURL, Checksum: plan.Checksum, SizeBytes: plan.SizeBytes, TargetKey: plan.TargetKey, ModKey: plan.ModKey, DLLRef: plan.DLLRef, SCUMExecutableChecksum: plan.SCUMExecutableChecksum, UE4SSABI: plan.UE4SSABI, SupportedTargets: []domain.RuntimeTarget{{OS: "windows", Arch: "amd64"}}, UpdateOnStart: true, RCONPort: plan.RCONPort, }) } func validateProfileKey(field, value string) []string { if strings.TrimSpace(value) == "" { return []string{field + " is required"} } if !validDistributionLogicalKey(value) { return []string{field + " is invalid"} } return nil } func validateRuntimePlatforms(field string, platforms []string) []string { var violations []string for i, platform := range platforms { if !validPluginSupportedOS(platform) { violations = append(violations, fmt.Sprintf("%s[%d] is invalid", field, i)) } } return append(violations, duplicateViolations(field, platforms)...) } func validateSafeRuntimeValue(field, value string) []string { if value == "" { return nil } lowered := strings.ToLower(value) if strings.HasPrefix(value, "/") || strings.HasPrefix(value, `\`) || strings.Contains(value, "://") || containsUnsafeRuntimeSecret(value) || looksLikeRawHostPath(value) || strings.Contains(value, "..") || strings.ContainsAny(value, "\r\n") || strings.Contains(lowered, "bash -c") || strings.Contains(lowered, "powershell -") || strings.Contains(lowered, "cmd.exe") || strings.Contains(lowered, "curl |") { return []string{field + " contains unsafe runtime content"} } return nil } func recordRuntimeProfileKey(seen map[string]struct{}, field, key string) []string { if key == "" { return nil } if _, exists := seen[key]; exists { return []string{field + " is duplicated"} } seen[key] = struct{}{} return nil } func validateRuntimeProfileCapabilityDeclarations(profiles domain.GamePluginRuntimeProfiles, declared []string) []string { declaredSet := map[string]struct{}{} for _, capability := range declared { declaredSet[capability] = struct{}{} } var violations []string check := func(field string, capabilities []string) { for i, capability := range capabilities { if _, ok := declaredSet[capability]; !ok { violations = append(violations, fmt.Sprintf("%s[%d] must also be declared in manifest capabilities", field, i)) } } } for i, profile := range profiles.LifecycleProfiles { check(fmt.Sprintf("runtimeProfiles.lifecycleProfiles[%d].capabilities", i), profile.Capabilities) } for i, transport := range profiles.TransportProfiles { check(fmt.Sprintf("runtimeProfiles.transportProfiles[%d].capabilities", i), transport.Capabilities) } return violations } func validateLifecycleActionsOptional(actions domain.PluginLifecycleActions) []string { var violations []string for field, value := range map[string]string{"install": actions.Install, "start": actions.Start, "stop": actions.Stop, "restart": actions.Restart, "status": actions.Status} { if value != "" && !safeRelativeJSONRef(value) { violations = append(violations, "runtime actionRefs."+field+" must be a safe relative JSON reference") } } return violations } func oneOf(value string, allowed ...string) bool { for _, candidate := range allowed { if value == candidate { return true } } return false } func validateSafeRelativeRuntimePath(field, value string) []string { if strings.TrimSpace(value) == "" || strings.HasPrefix(value, "/") || strings.HasPrefix(value, `\`) || strings.Contains(value, "..") || strings.Contains(value, "://") || strings.ContainsAny(value, "\r\n|;&`$<>") || len(value) >= 2 && value[1] == ':' || !regexp.MustCompile(`^[A-Za-z0-9_./-]{1,160}$`).MatchString(value) { return []string{field + " must be a safe relative path"} } return nil } func validSemanticVersion(value string) bool { _, ok := semanticVersionTuple(value) return ok } func semanticVersionTuple(value string) ([3]int, bool) { match := regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?$`).FindStringSubmatch(value) if match == nil { return [3]int{}, false } var result [3]int for i := 0; i < 3; i++ { if _, err := fmt.Sscanf(match[i+1], "%d", &result[i]); err != nil { return [3]int{}, false } } return result, true } func compareSemanticVersion(left, right [3]int) int { for i := 0; i < 3; i++ { if left[i] < right[i] { return -1 } if left[i] > right[i] { return 1 } } return 0 }