// Package validate provides shared input validation for all Volt components. // Every CLI command and API endpoint should validate user input through these // functions before using names in file paths, systemd units, or shell commands. package validate import ( "fmt" "regexp" "strings" ) // nameRegex allows lowercase alphanumeric, hyphens, underscores, and dots. // Must start with a letter or digit. Max 64 chars. var nameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`) // WorkloadName validates a workload/container/VM name. // Names are used in file paths, systemd unit names, and network identifiers, // so they must be strictly validated to prevent path traversal, injection, etc. // // Rules: // - 1-64 characters // - Alphanumeric, hyphens, underscores, dots only // - Must start with a letter or digit // - No path separators (/, \) // - No whitespace // - No shell metacharacters func WorkloadName(name string) error { if name == "" { return fmt.Errorf("name cannot be empty") } if len(name) > 64 { return fmt.Errorf("name too long (%d chars, max 64)", len(name)) } if !nameRegex.MatchString(name) { return fmt.Errorf("invalid name %q: must be alphanumeric with hyphens, underscores, or dots, starting with a letter or digit", name) } // Extra safety: reject anything with path components if strings.Contains(name, "/") || strings.Contains(name, "\\") || strings.Contains(name, "..") { return fmt.Errorf("invalid name %q: path separators and '..' not allowed", name) } return nil } // BridgeName validates a network bridge name. // Linux interface names are max 15 chars, alphanumeric + hyphens. func BridgeName(name string) error { if name == "" { return fmt.Errorf("bridge name cannot be empty") } if len(name) > 15 { return fmt.Errorf("bridge name too long (%d chars, max 15 for Linux interfaces)", len(name)) } if !regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9-]*$`).MatchString(name) { return fmt.Errorf("invalid bridge name %q: must start with a letter, alphanumeric and hyphens only", name) } return nil } // SafePath checks that a constructed path stays within the expected base directory. // Use this after filepath.Join to prevent traversal. func SafePath(base, constructed string) error { // Clean both paths for comparison cleanBase := strings.TrimRight(base, "/") + "/" cleanPath := constructed + "/" if !strings.HasPrefix(cleanPath, cleanBase) { return fmt.Errorf("path %q escapes base directory %q", constructed, base) } return nil }