Tencent/WeKnora · error

default HTTP transport is not *http.Transport

Error message

default HTTP transport is not *http.Transport

What it means

NewSearchHTTPClient clones http.DefaultTransport to build an SSRF-safe client, and requires it to be a concrete *http.Transport. If some other package has replaced http.DefaultTransport with a custom RoundTripper (or nil), the type assertion fails and this error is returned at client construction time.

Source

Thrown at internal/infrastructure/web_search/proxy.go:29

)

// ValidateProxyURL delegates to utils.ValidateURLForSSRF (only http/https pass that check).
func ValidateProxyURL(proxyURL string) error {
	proxyURL = strings.TrimSpace(proxyURL)
	if proxyURL == "" {
		return nil
	}
	return utils.ValidateURLForSSRF(proxyURL)
}

// NewSearchHTTPClient builds an http.Client for outbound web search requests.
// It uses utils.SSRFSafeDialContext, optional explicit or environment proxy, and
// redirect validation consistent with utils.NewSSRFSafeHTTPClient.
func NewSearchHTTPClient(timeout time.Duration, proxyURL string) (*http.Client, error) {
	proxyURL = strings.TrimSpace(proxyURL)
	def, ok := http.DefaultTransport.(*http.Transport)
	if !ok {
		return nil, fmt.Errorf("default HTTP transport is not *http.Transport")
	}
	t := def.Clone()
	t.DialContext = utils.SSRFSafeDialContext

	if proxyURL != "" {
		if err := ValidateProxyURL(proxyURL); err != nil {
			return nil, err
		}
		u, err := url.Parse(proxyURL)
		if err != nil {
			return nil, fmt.Errorf("invalid proxy_url: %w", err)
		}
		if u.Scheme == "" || u.Host == "" {
			return nil, fmt.Errorf("invalid proxy_url: scheme and host are required")
		}
		t.Proxy = http.ProxyURL(u)
	} else {
		t.Proxy = http.ProxyFromEnvironment

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Stop replacing http.DefaultTransport globally; wrap per-request instead
  2. If replacement is required, assign a *http.Transport (e.g. def.Clone() with wrapped RoundTrip)
  3. Initialize the search providers before any code mutates http.DefaultTransport
  4. Construct providers with their own transport instead of relying on the default

Example fix

// before
http.DefaultTransport = otelhttp.NewTransport(http.DefaultTransport)
// after
customClient := &http.Client{Transport: otelhttp.NewTransport(http.DefaultTransport.(*http.Transport).Clone())}
Defensive patterns

Strategy: type-guard

Validate before calling

if _, ok := http.DefaultTransport.(*http.Transport); !ok {
    return fmt.Errorf("search client unavailable: default transport was replaced")
}

Type guard

func defaultTransportIsCloneable() bool {
    _, ok := http.DefaultTransport.(*http.Transport)
    return ok
}

Try / catch

client, err := web_search.NewSearchHTTPClient(timeout, proxy)
if err != nil {
    if strings.Contains(err.Error(), "default HTTP transport is not") {
        log.Printf("transport replaced globally; constructing own transport")
        return buildClientWithOwnTransport(timeout, proxy)
    }
    return err
}

Prevention

When it happens

Trigger: Any of NewBaiduProvider/NewBingProvider/NewDuckDuckGoProvider/NewExaProvider/NewGoogleProvider/NewKeenableProvider constructed after app code assigned a non-*http.Transport RoundTripper to http.DefaultTransport (e.g. http.DefaultTransport = myInstrumentedTripper).

Common situations: Instrumentation/OTel or mocking libraries replacing the default transport in init(), a test helper leaked into production code, or a dependency mutating the global transport.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/333feb41adadca5d. Report an issue: GitHub.