diff --git a/api/server.go b/api/server.go index 16759092..c75a5dc8 100644 --- a/api/server.go +++ b/api/server.go @@ -12,6 +12,7 @@ import ( "nofx/crypto" "nofx/logger" "nofx/manager" + "nofx/security" "nofx/market" "nofx/provider/alpaca" "nofx/provider/coinank/coinank_api" @@ -1770,6 +1771,15 @@ func (s *Server) handleUpdateModelConfigs(c *gin.Context) { // Update each model's configuration and track traders that need reload tradersToReload := make(map[string]bool) for modelID, modelData := range req.Models { + // SSRF protection: validate custom_api_url before storing + if modelData.CustomAPIURL != "" { + cleanURL := strings.TrimSuffix(modelData.CustomAPIURL, "#") + if err := security.ValidateURL(cleanURL); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid custom_api_url for model %s: %s", modelID, err.Error())}) + return + } + } + // Find traders using this AI model BEFORE updating traders, _ := s.store.Trader().ListByAIModelID(userID, modelID) for _, t := range traders { diff --git a/mcp/config.go b/mcp/config.go index 1aebadd2..208b0180 100644 --- a/mcp/config.go +++ b/mcp/config.go @@ -7,6 +7,7 @@ import ( "time" "nofx/logger" + "nofx/security" ) // Config client configuration (centralized management of all configurations) @@ -48,7 +49,7 @@ func DefaultConfig() *Config { // Default dependencies (use global logger) Logger: logger.NewMCPLogger(), - HTTPClient: &http.Client{Timeout: DefaultTimeout}, + HTTPClient: security.SafeHTTPClient(DefaultTimeout), } } diff --git a/mcp/options.go b/mcp/options.go index 2a6fb03d..12ee9a30 100644 --- a/mcp/options.go +++ b/mcp/options.go @@ -22,7 +22,11 @@ func WithLogger(logger Logger) ClientOption { } } -// WithHTTPClient sets custom HTTP client +// WithHTTPClient sets custom HTTP client. +// +// WARNING: The default client uses security.SafeHTTPClient() with SSRF protection +// (blocks private IPs, cloud metadata, validates redirects). Overriding it bypasses +// these protections. Only use in tests or with a client providing equivalent safeguards. // // Usage example: // httpClient := &http.Client{Timeout: 60 * time.Second}