diff --git a/providers/azure/services/synapse/client.go b/providers/azure/services/synapse/client.go index 7a7a915fc..652810d1d 100644 --- a/providers/azure/services/synapse/client.go +++ b/providers/azure/services/synapse/client.go @@ -64,10 +64,11 @@ func NewClient(cred azcore.TokenCredential, subscriptionID, region string) *Syna } // NewClientWithHTTP creates a new Azure Synapse client with a custom HTTP client (for testing). -// If httpClient is nil, http.DefaultClient is used. +// When httpClient is nil, the SSRF-hardened httpclient.New() is used so the nil +// fallback also blocks IMDS connections. func NewClientWithHTTP(cred azcore.TokenCredential, subscriptionID, region string, httpClient HTTPClient) *SynapseClient { if httpClient == nil { - httpClient = http.DefaultClient + httpClient = httpclient.New() } return &SynapseClient{ cred: cred, @@ -345,7 +346,7 @@ func (c *SynapseClient) GetOfferingDetails(ctx context.Context, rec common.Recom upfrontCost = 0 recurringCost = totalCost / (float64(termYears) * 12) default: - // Fail loud on an unrecognised payment option rather than silently + // Fail loud on an unrecognized payment option rather than silently // billing it as all-upfront (owner policy: no silent fallbacks on // money-affecting fields). return nil, fmt.Errorf("unsupported payment option for Azure Synapse offering details: %q", rec.PaymentOption) diff --git a/providers/azure/services/synapse/client_test.go b/providers/azure/services/synapse/client_test.go index 30d67d9ed..14127491e 100644 --- a/providers/azure/services/synapse/client_test.go +++ b/providers/azure/services/synapse/client_test.go @@ -905,3 +905,24 @@ func TestPurchaseCommitment_canonicalReservedResourceType(t *testing.T) { armreservations.ReservedResourceType(capturedRRT), "reservedResourceType %q must be a member of armreservations.PossibleReservedResourceTypeValues()", capturedRRT) } + +// ---- nil HTTP client fallback ---------------------------------------------- + +// TestNewClientWithHTTP_NilFallbackIsHardened is a regression test for the +// codebase-review finding SEC-03 (issue #1143): when httpClient is nil, the +// httpClient falls back to httpclient.New() (SSRF-hardened), not +// http.DefaultClient, so the fallback also rejects IMDS connections. +func TestNewClientWithHTTP_NilFallbackIsHardened(t *testing.T) { + c := NewClientWithHTTP(nil, "sub-123", "eastus", nil) + require.NotNil(t, c.httpClient) + require.NotEqual(t, http.DefaultClient, c.httpClient, + "nil fallback must never be http.DefaultClient") + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://169.254.169.254/metadata/instance", nil) + require.NoError(t, err) + resp, err := c.httpClient.Do(req) + if resp != nil { + defer resp.Body.Close() + } + require.Error(t, err, "nil-fallback client must reject IMDS connections") + assert.Contains(t, err.Error(), "blocked") +}