package config import ( "fmt" "io/ioutil" "os" "strings" "time" "chorus.services/bzzz/pkg/security" "gopkg.in/yaml.v2" ) // SecurityConfig holds cluster security and election configuration type SecurityConfig struct { // Admin key sharing AdminKeyShares ShamirShare `yaml:"admin_key_shares" json:"admin_key_shares"` ElectionConfig ElectionConfig `yaml:"election_config" json:"election_config"` // Key management KeyRotationDays int `yaml:"key_rotation_days,omitempty" json:"key_rotation_days,omitempty"` AuditLogging bool `yaml:"audit_logging" json:"audit_logging"` AuditPath string `yaml:"audit_path,omitempty" json:"audit_path,omitempty"` } // AIConfig holds AI/LLM integration settings type AIConfig struct { Ollama OllamaConfig `yaml:"ollama"` OpenAI OpenAIConfig `yaml:"openai"` } // OllamaConfig holds Ollama API configuration type OllamaConfig struct { Endpoint string `yaml:"endpoint"` Timeout time.Duration `yaml:"timeout"` Models []string `yaml:"models"` } // OpenAIConfig holds OpenAI API configuration type OpenAIConfig struct { APIKey string `yaml:"api_key"` Endpoint string `yaml:"endpoint"` Timeout time.Duration `yaml:"timeout"` } // Config represents the complete configuration for a Bzzz agent type Config struct { WHOOSHAPI WHOOSHAPIConfig `yaml:"whoosh_api"` Agent AgentConfig `yaml:"agent"` GitHub GitHubConfig `yaml:"github"` P2P P2PConfig `yaml:"p2p"` Logging LoggingConfig `yaml:"logging"` Slurp SlurpConfig `yaml:"slurp"` V2 V2Config `yaml:"v2"` // BZZZ v2 protocol settings UCXL UCXLConfig `yaml:"ucxl"` // UCXL protocol settings Security SecurityConfig `yaml:"security"` // Cluster security and elections AI AIConfig `yaml:"ai"` // AI/LLM integration settings } // WHOOSHAPIConfig holds WHOOSH system integration settings type WHOOSHAPIConfig struct { BaseURL string `yaml:"base_url"` APIKey string `yaml:"api_key"` Timeout time.Duration `yaml:"timeout"` RetryCount int `yaml:"retry_count"` } // CollaborationConfig holds role-based collaboration settings type CollaborationConfig struct { PreferredMessageTypes []string `yaml:"preferred_message_types"` AutoSubscribeToRoles []string `yaml:"auto_subscribe_to_roles"` AutoSubscribeToExpertise []string `yaml:"auto_subscribe_to_expertise"` ResponseTimeoutSeconds int `yaml:"response_timeout_seconds"` MaxCollaborationDepth int `yaml:"max_collaboration_depth"` EscalationThreshold int `yaml:"escalation_threshold"` CustomTopicSubscriptions []string `yaml:"custom_topic_subscriptions"` } // AgentConfig holds agent-specific configuration type AgentConfig struct { ID string `yaml:"id"` Capabilities []string `yaml:"capabilities"` PollInterval time.Duration `yaml:"poll_interval"` MaxTasks int `yaml:"max_tasks"` Models []string `yaml:"models"` Specialization string `yaml:"specialization"` ModelSelectionWebhook string `yaml:"model_selection_webhook"` DefaultReasoningModel string `yaml:"default_reasoning_model"` SandboxImage string `yaml:"sandbox_image"` // Role-based configuration from Bees-AgenticWorkers Role string `yaml:"role"` SystemPrompt string `yaml:"system_prompt"` ReportsTo []string `yaml:"reports_to"` Expertise []string `yaml:"expertise"` Deliverables []string `yaml:"deliverables"` // Role-based collaboration settings CollaborationSettings CollaborationConfig `yaml:"collaboration"` } // GitHubConfig holds GitHub integration settings type GitHubConfig struct { TokenFile string `yaml:"token_file"` UserAgent string `yaml:"user_agent"` Timeout time.Duration `yaml:"timeout"` RateLimit bool `yaml:"rate_limit"` Assignee string `yaml:"assignee"` } // P2PConfig holds P2P networking configuration type P2PConfig struct { ServiceTag string `yaml:"service_tag"` BzzzTopic string `yaml:"bzzz_topic"` HmmmTopic string `yaml:"hmmm_topic"` DiscoveryTimeout time.Duration `yaml:"discovery_timeout"` // Human escalation settings EscalationWebhook string `yaml:"escalation_webhook"` EscalationKeywords []string `yaml:"escalation_keywords"` ConversationLimit int `yaml:"conversation_limit"` } // LoggingConfig holds logging configuration type LoggingConfig struct { Level string `yaml:"level"` Format string `yaml:"format"` Output string `yaml:"output"` Structured bool `yaml:"structured"` } // V2Config holds BZZZ v2 protocol configuration type V2Config struct { // Enable v2 protocol features Enabled bool `yaml:"enabled" json:"enabled"` // Protocol version ProtocolVersion string `yaml:"protocol_version" json:"protocol_version"` // URI resolution settings URIResolution URIResolutionConfig `yaml:"uri_resolution" json:"uri_resolution"` // DHT settings DHT DHTConfig `yaml:"dht" json:"dht"` // Semantic addressing SemanticAddressing SemanticAddressingConfig `yaml:"semantic_addressing" json:"semantic_addressing"` // Feature flags FeatureFlags map[string]bool `yaml:"feature_flags" json:"feature_flags"` } // URIResolutionConfig holds URI resolution settings type URIResolutionConfig struct { CacheTTL time.Duration `yaml:"cache_ttl" json:"cache_ttl"` MaxPeersPerResult int `yaml:"max_peers_per_result" json:"max_peers_per_result"` DefaultStrategy string `yaml:"default_strategy" json:"default_strategy"` ResolutionTimeout time.Duration `yaml:"resolution_timeout" json:"resolution_timeout"` } // DHTConfig holds DHT-specific configuration type DHTConfig struct { Enabled bool `yaml:"enabled" json:"enabled"` BootstrapPeers []string `yaml:"bootstrap_peers" json:"bootstrap_peers"` Mode string `yaml:"mode" json:"mode"` // "client", "server", "auto" ProtocolPrefix string `yaml:"protocol_prefix" json:"protocol_prefix"` BootstrapTimeout time.Duration `yaml:"bootstrap_timeout" json:"bootstrap_timeout"` DiscoveryInterval time.Duration `yaml:"discovery_interval" json:"discovery_interval"` AutoBootstrap bool `yaml:"auto_bootstrap" json:"auto_bootstrap"` } // SemanticAddressingConfig holds semantic addressing settings type SemanticAddressingConfig struct { EnableWildcards bool `yaml:"enable_wildcards" json:"enable_wildcards"` DefaultAgent string `yaml:"default_agent" json:"default_agent"` DefaultRole string `yaml:"default_role" json:"default_role"` DefaultProject string `yaml:"default_project" json:"default_project"` EnableRoleHierarchy bool `yaml:"enable_role_hierarchy" json:"enable_role_hierarchy"` } // UCXLConfig holds UCXL protocol configuration type UCXLConfig struct { // Enable UCXL protocol Enabled bool `yaml:"enabled" json:"enabled"` // UCXI server configuration Server UCXIServerConfig `yaml:"server" json:"server"` // Address resolution settings Resolution UCXLResolutionConfig `yaml:"resolution" json:"resolution"` // Storage settings Storage UCXLStorageConfig `yaml:"storage" json:"storage"` // P2P integration settings P2PIntegration UCXLP2PConfig `yaml:"p2p_integration" json:"p2p_integration"` } // UCXIServerConfig holds UCXI server settings type UCXIServerConfig struct { Port int `yaml:"port" json:"port"` BasePath string `yaml:"base_path" json:"base_path"` Enabled bool `yaml:"enabled" json:"enabled"` } // UCXLResolutionConfig holds address resolution settings type UCXLResolutionConfig struct { CacheTTL time.Duration `yaml:"cache_ttl" json:"cache_ttl"` EnableWildcards bool `yaml:"enable_wildcards" json:"enable_wildcards"` MaxResults int `yaml:"max_results" json:"max_results"` } // UCXLStorageConfig holds storage settings type UCXLStorageConfig struct { Type string `yaml:"type" json:"type"` // "filesystem", "memory" Directory string `yaml:"directory" json:"directory"` MaxSize int64 `yaml:"max_size" json:"max_size"` // in bytes } // UCXLP2PConfig holds P2P integration settings type UCXLP2PConfig struct { EnableAnnouncement bool `yaml:"enable_announcement" json:"enable_announcement"` EnableDiscovery bool `yaml:"enable_discovery" json:"enable_discovery"` AnnouncementTopic string `yaml:"announcement_topic" json:"announcement_topic"` DiscoveryTimeout time.Duration `yaml:"discovery_timeout" json:"discovery_timeout"` } // LoadConfig loads configuration from file, environment variables, and defaults func LoadConfig(configPath string) (*Config, error) { // Start with defaults config := getDefaultConfig() // Load from file if it exists if configPath != "" && fileExists(configPath) { if err := loadFromFile(config, configPath); err != nil { return nil, fmt.Errorf("failed to load config file: %w", err) } } // Override with environment variables if err := loadFromEnv(config); err != nil { return nil, fmt.Errorf("failed to load environment variables: %w", err) } // Validate configuration if err := validateConfig(config); err != nil { return nil, fmt.Errorf("invalid configuration: %w", err) } return config, nil } // getDefaultConfig returns the default configuration func getDefaultConfig() *Config { return &Config{ WHOOSHAPI: WHOOSHAPIConfig{ BaseURL: "https://whoosh.home.deepblack.cloud", Timeout: 30 * time.Second, RetryCount: 3, }, Agent: AgentConfig{ Capabilities: []string{"general", "reasoning", "task-coordination"}, PollInterval: 30 * time.Second, MaxTasks: 3, Models: []string{"phi3", "llama3.1"}, Specialization: "general_developer", ModelSelectionWebhook: "https://n8n.home.deepblack.cloud/webhook/model-selection", DefaultReasoningModel: "phi3", SandboxImage: "registry.home.deepblack.cloud/tony/bzzz-sandbox:latest", }, GitHub: GitHubConfig{ TokenFile: "/home/tony/chorus/business/secrets/gh-token", UserAgent: "Bzzz-P2P-Agent/1.0", Timeout: 30 * time.Second, RateLimit: true, Assignee: "anthonyrawlins", }, P2P: P2PConfig{ ServiceTag: "bzzz-peer-discovery", BzzzTopic: "bzzz/coordination/v1", HmmmTopic: "hmmm/meta-discussion/v1", DiscoveryTimeout: 10 * time.Second, EscalationWebhook: "https://n8n.home.deepblack.cloud/webhook-test/human-escalation", EscalationKeywords: []string{"stuck", "help", "human", "escalate", "clarification needed", "manual intervention"}, ConversationLimit: 10, }, Logging: LoggingConfig{ Level: "info", Format: "text", Output: "stdout", Structured: false, }, Slurp: GetDefaultSlurpConfig(), UCXL: UCXLConfig{ Enabled: false, // Disabled by default Server: UCXIServerConfig{ Port: 8081, BasePath: "/bzzz", Enabled: true, }, Resolution: UCXLResolutionConfig{ CacheTTL: 5 * time.Minute, EnableWildcards: true, MaxResults: 50, }, Storage: UCXLStorageConfig{ Type: "filesystem", Directory: "/tmp/bzzz-ucxl-storage", MaxSize: 100 * 1024 * 1024, // 100MB }, P2PIntegration: UCXLP2PConfig{ EnableAnnouncement: true, EnableDiscovery: true, AnnouncementTopic: "bzzz/ucxl/announcement/v1", DiscoveryTimeout: 30 * time.Second, }, }, Security: SecurityConfig{ AdminKeyShares: ShamirShare{ Threshold: 3, TotalShares: 5, }, ElectionConfig: ElectionConfig{ HeartbeatTimeout: 5 * time.Second, DiscoveryTimeout: 30 * time.Second, ElectionTimeout: 15 * time.Second, MaxDiscoveryAttempts: 6, DiscoveryBackoff: 5 * time.Second, MinimumQuorum: 3, ConsensusAlgorithm: "raft", SplitBrainDetection: true, ConflictResolution: "highest_uptime", }, KeyRotationDays: 90, AuditLogging: true, AuditPath: ".bzzz/security-audit.log", }, V2: V2Config{ Enabled: false, // Disabled by default for backward compatibility ProtocolVersion: "2.0.0", URIResolution: URIResolutionConfig{ CacheTTL: 5 * time.Minute, MaxPeersPerResult: 5, DefaultStrategy: "best_match", ResolutionTimeout: 30 * time.Second, }, DHT: DHTConfig{ Enabled: false, // Disabled by default BootstrapPeers: []string{}, Mode: "auto", ProtocolPrefix: "/bzzz", BootstrapTimeout: 30 * time.Second, DiscoveryInterval: 60 * time.Second, AutoBootstrap: false, }, SemanticAddressing: SemanticAddressingConfig{ EnableWildcards: true, DefaultAgent: "any", DefaultRole: "any", DefaultProject: "any", EnableRoleHierarchy: true, }, FeatureFlags: map[string]bool{ "uri_protocol": false, "semantic_addressing": false, "dht_discovery": false, "advanced_resolution": false, }, }, AI: AIConfig{ Ollama: OllamaConfig{ Endpoint: "http://localhost:11434", Timeout: 30 * time.Second, Models: []string{"phi3", "llama3.1"}, }, OpenAI: OpenAIConfig{ APIKey: "", Endpoint: "https://api.openai.com/v1", Timeout: 30 * time.Second, }, }, } } // loadFromFile loads configuration from a YAML file with zero-trust validation func loadFromFile(config *Config, filePath string) error { // SECURITY: Validate file path to prevent directory traversal validator := security.NewSecurityValidator() if err := validator.ValidateFilePath(filePath); err != nil { return fmt.Errorf("security validation failed for config path: %w", err) } data, err := ioutil.ReadFile(filePath) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } // SECURITY: Limit file size to prevent memory exhaustion attacks maxConfigSize := 1024 * 1024 // 1MB limit for config files if len(data) > maxConfigSize { return fmt.Errorf("config file too large: %d bytes exceeds limit of %d bytes", len(data), maxConfigSize) } if err := yaml.Unmarshal(data, config); err != nil { return fmt.Errorf("failed to parse YAML config: %w", err) } // SECURITY: Validate all configuration values after loading if err := validateConfigSecurity(config, validator); err != nil { return fmt.Errorf("security validation failed for loaded config: %w", err) } return nil } // loadFromEnv loads configuration from environment variables func loadFromEnv(config *Config) error { // WHOOSH API configuration if url := os.Getenv("BZZZ_WHOOSH_API_URL"); url != "" { config.WHOOSHAPI.BaseURL = url } if apiKey := os.Getenv("BZZZ_WHOOSH_API_KEY"); apiKey != "" { config.WHOOSHAPI.APIKey = apiKey } // Agent configuration if agentID := os.Getenv("BZZZ_AGENT_ID"); agentID != "" { config.Agent.ID = agentID } if capabilities := os.Getenv("BZZZ_AGENT_CAPABILITIES"); capabilities != "" { config.Agent.Capabilities = strings.Split(capabilities, ",") } if specialization := os.Getenv("BZZZ_AGENT_SPECIALIZATION"); specialization != "" { config.Agent.Specialization = specialization } if modelWebhook := os.Getenv("BZZZ_MODEL_SELECTION_WEBHOOK"); modelWebhook != "" { config.Agent.ModelSelectionWebhook = modelWebhook } // GitHub configuration if tokenFile := os.Getenv("BZZZ_GITHUB_TOKEN_FILE"); tokenFile != "" { config.GitHub.TokenFile = tokenFile } // P2P configuration if webhook := os.Getenv("BZZZ_ESCALATION_WEBHOOK"); webhook != "" { config.P2P.EscalationWebhook = webhook } // Logging configuration if level := os.Getenv("BZZZ_LOG_LEVEL"); level != "" { config.Logging.Level = level } // SLURP configuration if slurpURL := os.Getenv("BZZZ_SLURP_URL"); slurpURL != "" { config.Slurp.BaseURL = slurpURL } if slurpKey := os.Getenv("BZZZ_SLURP_API_KEY"); slurpKey != "" { config.Slurp.APIKey = slurpKey } if slurpEnabled := os.Getenv("BZZZ_SLURP_ENABLED"); slurpEnabled == "true" { config.Slurp.Enabled = true } // UCXL protocol configuration if ucxlEnabled := os.Getenv("BZZZ_UCXL_ENABLED"); ucxlEnabled == "true" { config.UCXL.Enabled = true } if ucxiPort := os.Getenv("BZZZ_UCXI_PORT"); ucxiPort != "" { // Would need strconv.Atoi but keeping simple for now // In production, add proper integer parsing } // V2 protocol configuration if v2Enabled := os.Getenv("BZZZ_V2_ENABLED"); v2Enabled == "true" { config.V2.Enabled = true } if dhtEnabled := os.Getenv("BZZZ_DHT_ENABLED"); dhtEnabled == "true" { config.V2.DHT.Enabled = true } if dhtMode := os.Getenv("BZZZ_DHT_MODE"); dhtMode != "" { config.V2.DHT.Mode = dhtMode } if bootstrapPeers := os.Getenv("BZZZ_DHT_BOOTSTRAP_PEERS"); bootstrapPeers != "" { config.V2.DHT.BootstrapPeers = strings.Split(bootstrapPeers, ",") } return nil } // validateConfig validates the configuration values func validateConfig(config *Config) error { // Validate required fields if config.WHOOSHAPI.BaseURL == "" { return fmt.Errorf("whoosh_api.base_url is required") } // Note: Agent.ID can be empty - it will be auto-generated from node ID in main.go if len(config.Agent.Capabilities) == 0 { return fmt.Errorf("agent.capabilities cannot be empty") } if config.Agent.PollInterval <= 0 { return fmt.Errorf("agent.poll_interval must be positive") } if config.Agent.MaxTasks <= 0 { return fmt.Errorf("agent.max_tasks must be positive") } // Validate GitHub token file exists if specified if config.GitHub.TokenFile != "" && !fileExists(config.GitHub.TokenFile) { return fmt.Errorf("github token file does not exist: %s", config.GitHub.TokenFile) } // Validate SLURP configuration if err := ValidateSlurpConfig(config.Slurp); err != nil { return fmt.Errorf("slurp configuration invalid: %w", err) } return nil } // SaveConfig saves the configuration to a YAML file func SaveConfig(config *Config, filePath string) error { data, err := yaml.Marshal(config) if err != nil { return fmt.Errorf("failed to marshal config to YAML: %w", err) } if err := ioutil.WriteFile(filePath, data, 0644); err != nil { return fmt.Errorf("failed to write config file: %w", err) } return nil } // GetGitHubToken reads the GitHub token from the configured file func (c *Config) GetGitHubToken() (string, error) { if c.GitHub.TokenFile == "" { return "", fmt.Errorf("no GitHub token file configured") } tokenBytes, err := ioutil.ReadFile(c.GitHub.TokenFile) if err != nil { return "", fmt.Errorf("failed to read GitHub token: %w", err) } return strings.TrimSpace(string(tokenBytes)), nil } // fileExists checks if a file exists func fileExists(filePath string) bool { _, err := os.Stat(filePath) return err == nil } // GenerateDefaultConfigFile creates a default configuration file func GenerateDefaultConfigFile(filePath string) error { config := getDefaultConfig() return SaveConfig(config, filePath) } // IsSetupRequired checks if BZZZ needs to be set up (no valid configuration exists) func IsSetupRequired(configPath string) bool { // Check if config file exists if !fileExists(configPath) { return true } // Try to load the configuration _, err := LoadConfig(configPath) if err != nil { return true } // Configuration exists and is valid return false } // IsValidConfiguration checks if a configuration is valid for production use func IsValidConfiguration(config *Config) bool { // Check essential configuration elements if config.Agent.ID == "" { return false } if len(config.Agent.Capabilities) == 0 { return false } // Configuration appears valid return true } // validateConfigSecurity performs zero-trust validation on all configuration values func validateConfigSecurity(config *Config, validator *security.SecurityValidator) error { // Validate Agent configuration if config.Agent.ID != "" { // Agent IDs should be alphanumeric identifiers if len(config.Agent.ID) > 64 { return fmt.Errorf("agent ID too long (max 64 characters): %s", config.Agent.ID) } if !isAlphanumericWithDashes(config.Agent.ID) { return fmt.Errorf("agent ID contains invalid characters: %s", config.Agent.ID) } } // Validate specialization if config.Agent.Specialization != "" { if len(config.Agent.Specialization) > 64 { return fmt.Errorf("specialization too long (max 64 characters)") } if !isAlphanumericWithUnderscore(config.Agent.Specialization) { return fmt.Errorf("specialization contains invalid characters: %s", config.Agent.Specialization) } } // Validate role if config.Agent.Role != "" { if len(config.Agent.Role) > 32 { return fmt.Errorf("role too long (max 32 characters)") } if !isAlphanumericWithUnderscore(config.Agent.Role) { return fmt.Errorf("role contains invalid characters: %s", config.Agent.Role) } } // Validate capabilities list if len(config.Agent.Capabilities) > 20 { return fmt.Errorf("too many capabilities (max 20)") } for _, capability := range config.Agent.Capabilities { if len(capability) > 32 { return fmt.Errorf("capability name too long (max 32 characters): %s", capability) } if !isAlphanumericWithUnderscore(capability) { return fmt.Errorf("capability contains invalid characters: %s", capability) } } // Validate models list if len(config.Agent.Models) > 50 { return fmt.Errorf("too many models (max 50)") } for _, model := range config.Agent.Models { if len(model) > 64 { return fmt.Errorf("model name too long (max 64 characters): %s", model) } if !isAlphanumericWithDots(model) { return fmt.Errorf("model name contains invalid characters: %s", model) } } // Validate URLs and webhooks if config.WHOOSHAPI.BaseURL != "" { if err := validateURL(config.WHOOSHAPI.BaseURL); err != nil { return fmt.Errorf("invalid WHOOSH API URL: %w", err) } } if config.Agent.ModelSelectionWebhook != "" { if err := validateURL(config.Agent.ModelSelectionWebhook); err != nil { return fmt.Errorf("invalid model selection webhook URL: %w", err) } } if config.P2P.EscalationWebhook != "" { if err := validateURL(config.P2P.EscalationWebhook); err != nil { return fmt.Errorf("invalid escalation webhook URL: %w", err) } } // Validate file paths if config.GitHub.TokenFile != "" { if err := validator.ValidateFilePath(config.GitHub.TokenFile); err != nil { return fmt.Errorf("invalid GitHub token file path: %w", err) } } if config.Security.AuditPath != "" { if err := validator.ValidateFilePath(config.Security.AuditPath); err != nil { return fmt.Errorf("invalid audit path: %w", err) } } // Validate numeric limits if config.Agent.MaxTasks < 1 || config.Agent.MaxTasks > 100 { return fmt.Errorf("invalid max tasks: must be between 1 and 100, got %d", config.Agent.MaxTasks) } if config.WHOOSHAPI.RetryCount < 0 || config.WHOOSHAPI.RetryCount > 10 { return fmt.Errorf("invalid retry count: must be between 0 and 10, got %d", config.WHOOSHAPI.RetryCount) } if config.Security.KeyRotationDays < 0 || config.Security.KeyRotationDays > 365 { return fmt.Errorf("invalid key rotation days: must be between 0 and 365, got %d", config.Security.KeyRotationDays) } // Validate timeout durations (prevent extremely long timeouts that could cause DoS) maxTimeout := 300 * time.Second // 5 minutes max if config.WHOOSHAPI.Timeout > maxTimeout { return fmt.Errorf("WHOOSH API timeout too long (max 5 minutes): %v", config.WHOOSHAPI.Timeout) } if config.GitHub.Timeout > maxTimeout { return fmt.Errorf("GitHub timeout too long (max 5 minutes): %v", config.GitHub.Timeout) } if config.Agent.PollInterval > 3600*time.Second { return fmt.Errorf("poll interval too long (max 1 hour): %v", config.Agent.PollInterval) } return nil } // Helper validation functions func isAlphanumericWithDashes(s string) bool { if len(s) == 0 { return false } for _, char := range s { if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' || char == '_') { return false } } return true } func isAlphanumericWithUnderscore(s string) bool { if len(s) == 0 { return false } for _, char := range s { if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '_') { return false } } return true } func isAlphanumericWithDots(s string) bool { if len(s) == 0 { return false } for _, char := range s { if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '.' || char == '-' || char == '_') { return false } } return true } func validateURL(url string) error { if len(url) > 2048 { return fmt.Errorf("URL too long (max 2048 characters)") } // Basic URL validation - should start with http:// or https:// if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { return fmt.Errorf("URL must start with http:// or https://") } // Check for dangerous characters that could be used for injection dangerousChars := []string{"`", "$", ";", "|", "&", "<", ">", "\n", "\r"} for _, char := range dangerousChars { if strings.Contains(url, char) { return fmt.Errorf("URL contains dangerous characters") } } return nil }