feat(providers): allow provider_type changes on update with SSRF re-validation

Remove blanket rejection of provider_type on update. Instead, when
provider_type changes, re-validate the existing api_base against the
new type to prevent SSRF via ACP→non-ACP type switch.

This enables provider type changes in onboarding/setup flows while
maintaining security: ACP providers skip URL validation, so switching
from ACP to another type now forces URL re-validation.
This commit is contained in:
viettranx committed 2026-04-02 08:49:54 +07:00
1 parent 9fd9e1f47b
commit abcb9614bc
1 file changed
+13 -7
+13 -7
View File
@@ -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 != "" {