From f99db8040fa47f58ed79dfe2413354a6656659ae Mon Sep 17 00:00:00 2001 From: ilyasse benrkia Date: Mon, 28 Sep 2026 09:53:32 +0000 Subject: [PATCH] fix: guard transport type assertion against non-standard DefaultTransport newAPITransport asserted http.DefaultTransport to *http.Transport with an unchecked assertion. http.DefaultTransport is an http.RoundTripper, and instrumentation libraries such as otelhttp replace it with a wrapper that is not an *http.Transport, so the assertion panicked at client construction. Fall back to a fresh *http.Transport when the default is not the standard transport. The API client only talks to the local Runtime and Extensions API, so it does not need to inherit any transport tuning from the wrapped default. Fixes: https://github.com/aws/aws-lambda-go/issues/642 --- lambda/api_client_proxy_test.go | 39 +++++++++++++++++++++++++++++++++ lambda/runtime_api_client.go | 7 +++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lambda/api_client_proxy_test.go b/lambda/api_client_proxy_test.go index ec4230f8..c16de107 100644 --- a/lambda/api_client_proxy_test.go +++ b/lambda/api_client_proxy_test.go @@ -61,3 +61,42 @@ func TestExtensionAPIClientBypassesProxy(t *testing.T) { proxy := proxyForClient(t, client, nonLoopbackAPIRequest(t)) require.Nil(t, proxy, "Extensions API client must not route through the configured proxy, got %v", proxy) } + +// wrappedRoundTripper is a RoundTripper that is not an *http.Transport, mirroring +// how instrumentation libraries (for example otelhttp) replace http.DefaultTransport. +type wrappedRoundTripper struct { + base http.RoundTripper +} + +func (w *wrappedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return w.base.RoundTrip(req) +} + +// When http.DefaultTransport is a non-*http.Transport wrapper, constructing the +// clients must not panic on a type assertion, and the transport must still bypass +// the proxy. +func TestAPIClientsWithWrappedDefaultTransport(t *testing.T) { + t.Setenv("HTTP_PROXY", "http://192.0.2.100:3128") + t.Setenv("HTTPS_PROXY", "http://192.0.2.100:3128") + t.Setenv("http_proxy", "http://192.0.2.100:3128") + t.Setenv("https_proxy", "http://192.0.2.100:3128") + + original := http.DefaultTransport + t.Cleanup(func() { http.DefaultTransport = original }) + http.DefaultTransport = &wrappedRoundTripper{base: original} + + for _, tc := range []struct { + name string + client func() *http.Client + }{ + {"runtime", func() *http.Client { return newRuntimeAPIClient("192.0.2.1:9001").httpClient }}, + {"extension", func() *http.Client { return newExtensionAPIClient("192.0.2.1:9001").httpClient }}, + } { + t.Run(tc.name, func(t *testing.T) { + var client *http.Client + require.NotPanics(t, func() { client = tc.client() }) + proxy := proxyForClient(t, client, nonLoopbackAPIRequest(t)) + require.Nil(t, proxy, "%s API client must not route through the configured proxy, got %v", tc.name, proxy) + }) + } +} diff --git a/lambda/runtime_api_client.go b/lambda/runtime_api_client.go index 42fe5232..7893c176 100644 --- a/lambda/runtime_api_client.go +++ b/lambda/runtime_api_client.go @@ -43,7 +43,12 @@ type runtimeAPIClient struct { // newAPITransport returns an HTTP transport that never proxies, so calls to the // link-local Runtime/Extensions API bypass any customer-configured proxy. func newAPITransport() *http.Transport { - transport := http.DefaultTransport.(*http.Transport).Clone() + transport, ok := http.DefaultTransport.(*http.Transport) + if ok { + transport = transport.Clone() + } else { + transport = &http.Transport{} + } transport.Proxy = nil return transport }