Repository: grafana/grafana-plugin-sdk-go (go)

Issue:
Stop using http.DefaultClient
When `httpclient.New()` is called without providing any options we [return http.DefaultClient](https://github.com/grafana/grafana-plugin-sdk-go/blob/ef2d80624da9a8e9e09e50791ae4f7f7b0f48c01/backend/httpclient/http_client.go#L25), which can lead to unexpected behavior and create potential security issues if it has been modified by another caller.

It also means that `httpclient.New()` and `httpclient.New(httpclient.Options{})` have different semantics as the latter (correctly) returns a new `http.Client` with all of our default settings, middleware, etc. This is confusing by itself, but is even more confusing to a user who attempts to pass (or stop passing) a single option and gets a completely different set of client behaviors.

The simplest resolution to these problems is to remove the special case and stop returning `http.DefaultClient`.

It's possible that this change will cause problems for callers who are either relying on side effects of changes to `http.DefaultClient`, or are not expecting our default settings or middleware.

In both of these cases the caller has the option of replacing `httpclient.New()` with `http.DefaultClient` (or `&http.Client{}`) if they want the default client rather than our opinionated client.

Pull request description:
When calling httpclient.New without any options return a new client with default configuration
Fixes #1311 

Patch:
diff --git a/.github/workflows/check-grafana-compatibility.yml b/.github/workflows/check-grafana-compatibility.yml
index 98f5857..2132a03 100644
--- a/.github/workflows/check-grafana-compatibility.yml
+++ b/.github/workflows/check-grafana-compatibility.yml
@@ -26,7 +26,7 @@ jobs:
           persist-credentials: false
           repository: "grafana/grafana"
           path: "./grafana"
-      - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # 5.3.0
+      - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # 5.5.0
         with:
           cache: false
           go-version: "~1.22"
diff --git a/.github/workflows/check-incompatibility.yml b/.github/workflows/check-incompatibility.yml
index 5fcceb5..7912cea 100644
--- a/.github/workflows/check-incompatibility.yml
+++ b/.github/workflows/check-incompatibility.yml
@@ -21,7 +21,7 @@ jobs:
       with:
         persist-credentials: false
         path: './grafana-plugin-sdk-go'
-    - uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # 5.3.0
+    - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # 5.5.0
       with:
         cache: false
         check-latest: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d3ea6bc..0afbabd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
           persist-credentials: false
 
       - name: Setup go
-        uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # 5.3.0
+        uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # 5.5.0
         with:
           go-version-file: ./go.mod
 
diff --git a/backend/httpclient/http_client.go b/backend/httpclient/http_client.go
index 0abc678..066c689 100644
--- a/backend/httpclient/http_client.go
+++ b/backend/httpclient/http_client.go
@@ -14,17 +14,12 @@ import (
 )
 
 // New creates a new http.Client.
-// If opts is nil the http.DefaultClient will be returned.
 // If no middlewares are provided the DefaultMiddlewares will be used. If you
 // provide middlewares you have to manually add the DefaultMiddlewares for it to be
 // enabled.
 // Note: Middlewares will be executed in the same order as provided.
 // Note: If more than one Options is provided a panic is raised.
 func New(opts ...Options) (*http.Client, error) {
-	if opts == nil {
-		return http.DefaultClient, nil
-	}
-
 	clientOpts := createOptions(opts...)
 	transport, err := GetTransport(clientOpts)
 	if err != nil {
diff --git a/go.mod b/go.mod
index 3dec5ee..5fc7095 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.24.1
 require (
 	github.com/apache/arrow-go/v18 v18.2.0
 	github.com/cheekybits/genny v1.0.0
-	github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2
+	github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4
 	github.com/elazarl/goproxy v1.7.2
 	github.com/getkin/kin-openapi v0.132.0
 	github.com/go-openapi/loads v0.22.0
@@ -43,10 +43,10 @@ require (
 	go.opentelemetry.io/otel/trace v1.35.0
 	golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
 	golang.org/x/net v0.39.0
-	golang.org/x/oauth2 v0.29.0
-	golang.org/x/sync v0.13.0
+	golang.org/x/oauth2 v0.30.0
+	golang.org/x/sync v0.14.0
 	golang.org/x/sys v0.32.0
-	golang.org/x/text v0.24.0
+	golang.org/x/text v0.25.0
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a
 	google.golang.org/grpc v1.71.1
 	google.golang.org/protobuf v1.36.6
diff --git a/go.sum b/go.sum
index d33a07a..a1f9987 100644
--- a/go.sum
+++ b/go.sum
@@ -23,9 +23,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
 github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
-github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 h1:XCdvHbz3LhewBHN7+mQPx0sg/Hxil/1USnBmxkjHcmY=
-github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
-github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
+github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE=
+github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
 github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -289,13 +288,13 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
 golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
-golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
-golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
+golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -312,8 +311,8 @@ golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
 golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
-golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
+golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=


Test patch:
diff --git a/backend/httpclient/http_client_test.go b/backend/httpclient/http_client_test.go
index bbc5f83..60f0398 100644
--- a/backend/httpclient/http_client_test.go
+++ b/backend/httpclient/http_client_test.go
@@ -10,13 +10,27 @@ import (
 )
 
 func TestNewClient(t *testing.T) {
-	t.Run("New() without any opts should return http.DefaultClient", func(t *testing.T) {
+	t.Run("New() without any opts should return expected http client and middlewares", func(t *testing.T) {
 		client, err := New()
 		require.NoError(t, err)
-		require.Same(t, http.DefaultClient, client)
+		require.NotNil(t, client)
+		require.NotSame(t, http.DefaultClient, client)
+
+		require.Equal(t, 30*time.Second, client.Timeout)
+		require.NotSame(t, &http.DefaultTransport, &client.Transport)
+	})
+
+	t.Run("New() with opts and no middlewares specified should return expected http client and middlewares", func(t *testing.T) {
+		client, err := New(Options{})
+		require.NoError(t, err)
+		require.NotNil(t, client)
+		require.NotSame(t, http.DefaultClient, client)
+
+		require.Equal(t, 30*time.Second, client.Timeout)
+		require.NotSame(t, &http.DefaultTransport, &client.Transport)
 	})
 
-	t.Run("New() with opts and no middleware should return expected http client and transport", func(t *testing.T) {
+	t.Run("New() with opts and empty middlewares should return expected http client and transport", func(t *testing.T) {
 		client, err := New(Options{
 			Timeouts: &TimeoutOptions{
 				Timeout:               time.Second,
@@ -35,6 +49,7 @@ func TestNewClient(t *testing.T) {
 		require.NotNil(t, client)
 		require.Equal(t, time.Second, client.Timeout)
 
+		// this only works when there are no middlewares, otherwise the transport is wrapped
 		transport, ok := client.Transport.(*http.Transport)
 		require.True(t, ok)
 		require.NotNil(t, transport)
diff --git a/experimental/e2e/storage/har.go b/experimental/e2e/storage/har.go
index eaa98ef..98b5f49 100644
--- a/experimental/e2e/storage/har.go
+++ b/experimental/e2e/storage/har.go
@@ -3,6 +3,7 @@ package storage
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
@@ -246,7 +247,7 @@ func (s *HAR) saveUnsafe() error {
 	if err != nil {
 		return err
 	}
-	raw, err := s.har.MarshalJSON()
+	raw, err := json.Marshal(s.har)
 	if err != nil {
 		return err
 	}
@@ -265,7 +266,8 @@ func (s *HAR) loadUnsafe() error {
 	if err != nil {
 		return err
 	}
-	return s.har.UnmarshalJSON(raw)
+
+	return json.Unmarshal(raw, &s.har)
 }
 
 // Match returns the stored http.Response for the given request.