diff --git a/internal/http/providers.go b/internal/http/providers.go index f4996fd4..560ff506 100644 --- a/internal/http/providers.go +++ b/internal/http/providers.go @@ -376,13 +376,6 @@ func (h *ProvidersHandler) handleUpdateProvider(w http.ResponseWriter, r *http.R } } - // provider_type is immutable after creation — changing it would bypass - // security checks (e.g. ACP skips URL validation). Reject if client tries. - if _, ok := updates["provider_type"]; ok { - writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidRequest, "provider_type cannot be changed after creation")}) - return - } - // Strip masked API key — don't overwrite real value with "***" if apiKey, ok := updates["api_key"]; ok { if s, _ := apiKey.(string); s == "***" || s == "" { @@ -415,6 +408,9 @@ func (h *ProvidersHandler) handleUpdateProvider(w http.ResponseWriter, r *http.R if displayName, ok := updates["display_name"].(string); ok { candidate.DisplayName = displayName } + if pt, ok := updates["provider_type"].(string); ok && pt != "" { + candidate.ProviderType = pt + } if settings, ok := updates["settings"]; ok { rawSettings, err := marshalJSONRaw(settings) if err != nil { @@ -424,6 +420,11 @@ func (h *ProvidersHandler) handleUpdateProvider(w http.ResponseWriter, r *http.R candidate.Settings = rawSettings } + // Re-validate URLs against the (possibly new) provider type. + // When provider_type changes, existing api_base must also pass validation + // for the new type — prevents SSRF via ACP→non-ACP type switch. + typeChanged := candidate.ProviderType != currentProvider.ProviderType + if apiBase, ok := updates["api_base"]; ok { if s, _ := apiBase.(string); s != "" { if err := validateProviderURL(s, candidate.ProviderType); err != nil { @@ -431,6 +432,11 @@ func (h *ProvidersHandler) handleUpdateProvider(w http.ResponseWriter, r *http.R return } } + } else if typeChanged && candidate.APIBase != "" { + if err := validateProviderURL(candidate.APIBase, candidate.ProviderType); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) + return + } } if baseURL, ok := updates["base_url"]; ok { if s, _ := baseURL.(string); s != "" {