mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-18 01:44:38 +08:00
fix(security): block SSRF via custom AI model URL
Apply security.ValidateURL() to custom_api_url in PUT /api/models before storing — blocks private IPs, cloud metadata endpoints, and localhost. Replace plain http.Client in mcp/config.go with security.SafeHTTPClient() for defense-in-depth (DialContext blocks private IPs, CheckRedirect validates targets). Add SSRF warning to WithHTTPClient() docs.
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
|||||||
"nofx/crypto"
|
"nofx/crypto"
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
"nofx/manager"
|
"nofx/manager"
|
||||||
|
"nofx/security"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
"nofx/provider/alpaca"
|
"nofx/provider/alpaca"
|
||||||
"nofx/provider/coinank/coinank_api"
|
"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
|
// Update each model's configuration and track traders that need reload
|
||||||
tradersToReload := make(map[string]bool)
|
tradersToReload := make(map[string]bool)
|
||||||
for modelID, modelData := range req.Models {
|
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
|
// Find traders using this AI model BEFORE updating
|
||||||
traders, _ := s.store.Trader().ListByAIModelID(userID, modelID)
|
traders, _ := s.store.Trader().ListByAIModelID(userID, modelID)
|
||||||
for _, t := range traders {
|
for _, t := range traders {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"nofx/logger"
|
"nofx/logger"
|
||||||
|
"nofx/security"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config client configuration (centralized management of all configurations)
|
// Config client configuration (centralized management of all configurations)
|
||||||
@@ -48,7 +49,7 @@ func DefaultConfig() *Config {
|
|||||||
|
|
||||||
// Default dependencies (use global logger)
|
// Default dependencies (use global logger)
|
||||||
Logger: logger.NewMCPLogger(),
|
Logger: logger.NewMCPLogger(),
|
||||||
HTTPClient: &http.Client{Timeout: DefaultTimeout},
|
HTTPClient: security.SafeHTTPClient(DefaultTimeout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
// Usage example:
|
||||||
// httpClient := &http.Client{Timeout: 60 * time.Second}
|
// httpClient := &http.Client{Timeout: 60 * time.Second}
|
||||||
|
|||||||
Reference in New Issue
Block a user