From c2c4b5ed9587f9bc912b62ce2f60c83fbd67ede7 Mon Sep 17 00:00:00 2001 From: Byron Date: Wed, 16 Sep 2026 14:58:00 +1000 Subject: [PATCH 1/5] fix(terraform): stop provider Configure mutating the global http.DefaultClient Configure aliased http.DefaultClient (a pointer, where a copy was clearly intended) and set its Transport, so every Configure rewrote the process-global client. In a normal provider process this is invisible, but when several provider instances share a process (in-process test frameworks such as terraform-plugin-testing) concurrent Configures race on the global under the race detector, and the last configured provider's transport leaks to any other http.DefaultClient user in the process. Build a fresh http.Client instead. --- .changesets/1789534602-780dfc2e.yaml | 10 ++++++++++ .../templates/terraform/provider/provider.go.stmpl | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 .changesets/1789534602-780dfc2e.yaml diff --git a/.changesets/1789534602-780dfc2e.yaml b/.changesets/1789534602-780dfc2e.yaml new file mode 100644 index 00000000..19c77377 --- /dev/null +++ b/.changesets/1789534602-780dfc2e.yaml @@ -0,0 +1,10 @@ +id: 1789534602-780dfc2e +features: + - core +targets: + - terraform +type: fix +bump: patch +description: stop provider Configure mutating the global http.DefaultClient +author: "" +date: "2026-09-16" diff --git a/templates/templates/terraform/provider/provider.go.stmpl b/templates/templates/terraform/provider/provider.go.stmpl index a80c6089..687415cf 100644 --- a/templates/templates/terraform/provider/provider.go.stmpl +++ b/templates/templates/terraform/provider/provider.go.stmpl @@ -75,8 +75,7 @@ func (p *{{sanitizeClassName .Global.Config.PackageName}}Provider) Configure(ctx {{ templateProviderHTTPTransportOptsSetHeaders .Local "data" "providerHTTPTransportOpts" }} {{ templateProviderHTTPTransportTlsSkipVerify .Local "data" "providerHTTPTransportOpts" }} - httpClient := http.DefaultClient - httpClient.Transport = NewProviderHTTPTransport(providerHTTPTransportOpts) + httpClient := &http.Client{Transport: NewProviderHTTPTransport(providerHTTPTransportOpts)} {{ templateProviderConfigureSDKOptions .Global.AST.MainSDK }} From a3133d038891544c720e7aa94dc43fbeb3320d2d Mon Sep 17 00:00:00 2001 From: Byron Date: Wed, 16 Sep 2026 15:14:12 +1000 Subject: [PATCH 2/5] chore: set changeset author [skip changelog] --- .../{1789534602-780dfc2e.yaml => 1789535643-f4e3aef3.yaml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .changesets/{1789534602-780dfc2e.yaml => 1789535643-f4e3aef3.yaml} (78%) diff --git a/.changesets/1789534602-780dfc2e.yaml b/.changesets/1789535643-f4e3aef3.yaml similarity index 78% rename from .changesets/1789534602-780dfc2e.yaml rename to .changesets/1789535643-f4e3aef3.yaml index 19c77377..050721c0 100644 --- a/.changesets/1789534602-780dfc2e.yaml +++ b/.changesets/1789535643-f4e3aef3.yaml @@ -1,4 +1,4 @@ -id: 1789534602-780dfc2e +id: 1789535643-f4e3aef3 features: - core targets: @@ -6,5 +6,5 @@ targets: type: fix bump: patch description: stop provider Configure mutating the global http.DefaultClient -author: "" +author: byron-lambda date: "2026-09-16" From 6e2bf89b8f218be52aef9c303b2737d00f868692 Mon Sep 17 00:00:00 2001 From: Byron Date: Wed, 16 Sep 2026 15:40:13 +1000 Subject: [PATCH 3/5] fix(terraform): clone the transport before applying tlsSkipVerify The tlsSkipVerify block mutated the TLS configuration of the transport it was handed, which is http.DefaultTransport, leaking InsecureSkipVerify to every other user of the global in the process. Clone it, mutate the clone, and hand the clone to the provider transport. Same class of bug as the http.DefaultClient aliasing this PR fixes; flagged by review. --- .../{1789535643-f4e3aef3.yaml => 1789537146-da471faf.yaml} | 4 ++-- templates/templates/terraform/includes/provider.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) rename .changesets/{1789535643-f4e3aef3.yaml => 1789537146-da471faf.yaml} (73%) diff --git a/.changesets/1789535643-f4e3aef3.yaml b/.changesets/1789537146-da471faf.yaml similarity index 73% rename from .changesets/1789535643-f4e3aef3.yaml rename to .changesets/1789537146-da471faf.yaml index 050721c0..c2d10444 100644 --- a/.changesets/1789535643-f4e3aef3.yaml +++ b/.changesets/1789537146-da471faf.yaml @@ -1,10 +1,10 @@ -id: 1789535643-f4e3aef3 +id: 1789537146-da471faf features: - core targets: - terraform type: fix bump: patch -description: stop provider Configure mutating the global http.DefaultClient +description: stop provider Configure mutating the global http.DefaultClient and http.DefaultTransport author: byron-lambda date: "2026-09-16" diff --git a/templates/templates/terraform/includes/provider.ts b/templates/templates/terraform/includes/provider.ts index 6f2a8bb2..2d560324 100644 --- a/templates/templates/terraform/includes/provider.ts +++ b/templates/templates/terraform/includes/provider.ts @@ -770,12 +770,17 @@ function templateProviderHTTPTransportTlsSkipVerify( result.push( `if transport, ok := ${httpTransportVariable}.Transport.(*http.Transport); ok {`, ); + // Clone before mutating: the transport here is http.DefaultTransport, and + // writing its TLS configuration would leak InsecureSkipVerify to every + // other user of the global in the process. + result.push(`transport = transport.Clone()`); result.push(`if transport.TLSClientConfig == nil {`); result.push(`transport.TLSClientConfig = &tls.Config{}`); result.push(`}`); result.push( `transport.TLSClientConfig.InsecureSkipVerify = ${dataModelVariable}.${tlsSkipVerifyFieldName}.ValueBool()`, ); + result.push(`${httpTransportVariable}.Transport = transport`); result.push(`}`); return result.join("\n"); From e81c8da9868cbb6194f4bdf7ee41b69919570d92 Mon Sep 17 00:00:00 2001 From: Byron Date: Wed, 16 Sep 2026 15:47:21 +1000 Subject: [PATCH 4/5] fix(terraform): clone the default transport where Configure wires it Rather than cloning inside the tlsSkipVerify block, give Configure ownership of its transport at the point http.DefaultTransport enters: the downstream helpers (headers, TLS skip verification) can then mutate the transport they are handed without touching process globals. --- templates/templates/terraform/includes/provider.ts | 5 ----- templates/templates/terraform/provider/provider.go.stmpl | 8 +++++++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/templates/templates/terraform/includes/provider.ts b/templates/templates/terraform/includes/provider.ts index 2d560324..6f2a8bb2 100644 --- a/templates/templates/terraform/includes/provider.ts +++ b/templates/templates/terraform/includes/provider.ts @@ -770,17 +770,12 @@ function templateProviderHTTPTransportTlsSkipVerify( result.push( `if transport, ok := ${httpTransportVariable}.Transport.(*http.Transport); ok {`, ); - // Clone before mutating: the transport here is http.DefaultTransport, and - // writing its TLS configuration would leak InsecureSkipVerify to every - // other user of the global in the process. - result.push(`transport = transport.Clone()`); result.push(`if transport.TLSClientConfig == nil {`); result.push(`transport.TLSClientConfig = &tls.Config{}`); result.push(`}`); result.push( `transport.TLSClientConfig.InsecureSkipVerify = ${dataModelVariable}.${tlsSkipVerifyFieldName}.ValueBool()`, ); - result.push(`${httpTransportVariable}.Transport = transport`); result.push(`}`); return result.join("\n"); diff --git a/templates/templates/terraform/provider/provider.go.stmpl b/templates/templates/terraform/provider/provider.go.stmpl index 687415cf..ed1692e3 100644 --- a/templates/templates/terraform/provider/provider.go.stmpl +++ b/templates/templates/terraform/provider/provider.go.stmpl @@ -67,9 +67,15 @@ func (p *{{sanitizeClassName .Global.Config.PackageName}}Provider) Configure(ctx {{ templateProviderConfigureGlobals .Global.AST.MainSDK.Globals }} {{ templateProviderConfigureSecurity .Global.AST.MainSDK.Security }} + // Configure owns its transport: clone the default so provider-specific + // settings (headers, TLS skip verification) never mutate process globals. + transport := http.DefaultTransport + if t, ok := transport.(*http.Transport); ok { + transport = t.Clone() + } providerHTTPTransportOpts := ProviderHTTPTransportOpts{ SetHeaders: make(map[string]string), - Transport: http.DefaultTransport, + Transport: transport, } {{ templateProviderHTTPTransportOptsSetHeaders .Local "data" "providerHTTPTransportOpts" }} From 98f7f8f9e69674b455e5bb63b93ea9c69ed08be9 Mon Sep 17 00:00:00 2001 From: Byron Date: Wed, 16 Sep 2026 19:33:07 +1000 Subject: [PATCH 5/5] chore: refresh the terraform review fixture [skip changelog] TARGET=review output for terraform-provider-testing, regenerated so the fixture demonstrates the Configure change: a cloned transport wired into the provider transport opts and a fresh http.Client, with the process globals untouched. --- zSDKs/terraform-provider-testing/.speakeasy/gen.lock | 2 +- .../internal/provider/provider.go | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/zSDKs/terraform-provider-testing/.speakeasy/gen.lock b/zSDKs/terraform-provider-testing/.speakeasy/gen.lock index b04e25f7..711ad04d 100644 --- a/zSDKs/terraform-provider-testing/.speakeasy/gen.lock +++ b/zSDKs/terraform-provider-testing/.speakeasy/gen.lock @@ -1048,7 +1048,7 @@ trackedFiles: internal/provider/patch_resource_sdk.go: last_write_checksum: sha1:9b583e2ac47191350373ce5a86408c118bd606b1 internal/provider/provider.go: - last_write_checksum: sha1:165aa656ebff0c9e83540c0fececc3cf4ab7eeeb + last_write_checksum: sha1:1da048473254e15222459129544e6a54163e660d internal/provider/reflect/diags.go: last_write_checksum: sha1:ace8bc53054bb1d8ee8689acf3e4323de75a6297 internal/provider/reflect/doc.go: diff --git a/zSDKs/terraform-provider-testing/internal/provider/provider.go b/zSDKs/terraform-provider-testing/internal/provider/provider.go index 484398c5..89e07be5 100644 --- a/zSDKs/terraform-provider-testing/internal/provider/provider.go +++ b/zSDKs/terraform-provider-testing/internal/provider/provider.go @@ -632,9 +632,15 @@ func (p *TestingProvider) Configure(ctx context.Context, req provider.ConfigureR security.Custom = custom + // Configure owns its transport: clone the default so provider-specific + // settings (headers, TLS skip verification) never mutate process globals. + transport := http.DefaultTransport + if t, ok := transport.(*http.Transport); ok { + transport = t.Clone() + } providerHTTPTransportOpts := ProviderHTTPTransportOpts{ SetHeaders: make(map[string]string), - Transport: http.DefaultTransport, + Transport: transport, } resp.Diagnostics.Append(data.HTTPHeaders.ElementsAs(ctx, &providerHTTPTransportOpts.SetHeaders, false)...) @@ -648,8 +654,7 @@ func (p *TestingProvider) Configure(ctx context.Context, req provider.ConfigureR transport.TLSClientConfig.InsecureSkipVerify = data.TLSSkipVerify.ValueBool() } - httpClient := http.DefaultClient - httpClient.Transport = NewProviderHTTPTransport(providerHTTPTransportOpts) + httpClient := &http.Client{Transport: NewProviderHTTPTransport(providerHTTPTransportOpts)} opts := []sdk.SDKOption{ sdk.WithTemplatedServerURL(serverUrl, serverUrlParams),