From 4101111df93345cd1bc68378383e04a1d27bff08 Mon Sep 17 00:00:00 2001 From: Kunal Karmakar Date: Thu, 12 Mar 2026 16:00:09 +0000 Subject: [PATCH] Add checks for deployment model name --- pkg/providers/azure/provider.go | 11 ++++++++--- pkg/providers/azure/provider_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/pkg/providers/azure/provider.go b/pkg/providers/azure/provider.go index 6f403708a..6e1d07e78 100644 --- a/pkg/providers/azure/provider.go +++ b/pkg/providers/azure/provider.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strings" "time" @@ -87,9 +88,13 @@ func (p *Provider) Chat( // model is the deployment name for Azure OpenAI deployment := model - // Build Azure-specific URL: {base}/openai/deployments/{deployment}/chat/completions?api-version=... - requestURL := fmt.Sprintf("%s/openai/deployments/%s/chat/completions?api-version=%s", - p.apiBase, deployment, azureAPIVersion) + // Build Azure-specific URL safely using url.JoinPath and query encoding + // to prevent path traversal or query injection via deployment names. + base, err := url.JoinPath(p.apiBase, "openai/deployments", deployment, "chat/completions") + if err != nil { + return nil, fmt.Errorf("failed to build Azure request URL: %w", err) + } + requestURL := base + "?api-version=" + azureAPIVersion // Build request body — no "model" field (Azure infers from deployment URL) requestBody := map[string]any{ diff --git a/pkg/providers/azure/provider_test.go b/pkg/providers/azure/provider_test.go index 086e87d42..8f44edff5 100644 --- a/pkg/providers/azure/provider_test.go +++ b/pkg/providers/azure/provider_test.go @@ -204,3 +204,29 @@ func TestProvider_AzureNewProviderWithTimeout(t *testing.T) { t.Errorf("timeout = %v, want %v", p.httpClient.Timeout, 180*time.Second) } } + +func TestProviderChat_AzureDeploymentNameEscaped(t *testing.T) { + var capturedPath string + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + capturedPath = r.URL.RawPath // use RawPath to see percent-encoding + if capturedPath == "" { + capturedPath = r.URL.Path + } + writeValidResponse(w) + })) + defer server.Close() + + p := NewProvider("test-key", server.URL, "") + + // Deployment name with characters that could cause path injection + _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "my deploy/../../admin", nil) + if err != nil { + t.Fatalf("Chat() error = %v", err) + } + + // The slash and special chars in the deployment name must be escaped, not treated as path separators + if capturedPath == "/openai/deployments/my deploy/../../admin/chat/completions" { + t.Fatal("deployment name was interpolated without escaping — path injection possible") + } +}