diff --git a/internal/http/tts_test_connection.go b/internal/http/tts_test_connection.go index fec16af2..a17270fb 100644 --- a/internal/http/tts_test_connection.go +++ b/internal/http/tts_test_connection.go @@ -104,6 +104,12 @@ func (h *TTSHandler) handleTestConnection(w http.ResponseWriter, r *http.Request if req.APIBase == "" { req.APIBase = req.BaseURL } + + if err := validateProviderURL(req.APIBase, req.Provider); err != nil { + http.Error(w, fmt.Sprintf(`{"error":%q}`, err.Error()), http.StatusBadRequest) + return + } + // Create ephemeral provider. provider, err := createEphemeralTTSProvider(req) if err != nil { diff --git a/internal/http/tts_test_connection_test.go b/internal/http/tts_test_connection_test.go index 4e6ae8c5..e8338bf6 100644 --- a/internal/http/tts_test_connection_test.go +++ b/internal/http/tts_test_connection_test.go @@ -132,3 +132,41 @@ func TestTestConnection_BelowOperator(t *testing.T) { t.Errorf("want 403, got %d: %s", rr.Code, rr.Body.String()) } } + +// TestTestConnection_BlockedAPIBase verifies test-connection reuses provider URL SSRF validation. +func TestTestConnection_BlockedAPIBase(t *testing.T) { + setupTestToken(t, ttsTestToken) + + operatorRaw := "test-conn-operator-key" + setupTestCache(t, map[string]*store.APIKeyData{ + crypto.HashAPIKey(operatorRaw): { + ID: uuid.New(), + Scopes: []string{"operator.write"}, + }, + }) + + mgr := audio.NewManager(audio.ManagerConfig{}) + mux := newTTSMux(mgr) + + req := httptest.NewRequest("POST", "/v1/tts/test-connection", + ttsBody(t, map[string]string{ + "provider": "openai", + "api_key": "sk-test", + "api_base": "http://127.0.0.1:8080/v1", + })) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+operatorRaw) + rr := httptest.NewRecorder() + mux.ServeHTTP(rr, req) + + if rr.Code != http.StatusBadRequest { + t.Fatalf("want 400, got %d: %s", rr.Code, rr.Body.String()) + } + var resp map[string]any + if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil { + t.Fatalf("decode response: %v", err) + } + if errStr, _ := resp["error"].(string); errStr != "provider URL cannot point to 127.0.0.1" { + t.Fatalf("unexpected error: %q", errStr) + } +}