mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 06:15:25 +00:00
Prevent TTS test-connection from bypassing provider URL guards (#937)
The repo already validates provider base URLs during provider CRUD, but TTS test-connection built ephemeral providers from user-supplied api_base without reusing that protection. This change applies the same validation at test time and adds regression coverage for blocked loopback targets. Constraint: Must preserve current local-provider SSRF policy semantics from provider validation Rejected: Restrict test-connection to admins only | larger behavior change than needed for the bug Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep TTS test-connection URL validation aligned with validateProviderURL behavior for provider CRUD Tested: go test ./internal/http -run 'TestTestConnection_(MissingProvider|UnsupportedProvider|MissingAPIKey|BelowOperator|BlockedAPIBase)$' -count=1 -v Tested: go build ./... Tested: go build -tags sqliteonly ./... Not-tested: Full repo race test suite Co-authored-by: viettranx <viettranx@gmail.com>
This commit is contained in:
1 parent
93ce4fa9d9
commit
f729b2d618
2 files changed
+44
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user