Tencent/WeKnora · error · ErrSSRFRedirectBlocked

%w: invalid scheme %s

Error message

%w: invalid scheme %s

What it means

A redirect target used a URL scheme other than http or https (e.g. file:, gopher:, ftp:). The redirect policy wraps ErrSSRFRedirectBlocked with 'invalid scheme %s' because even whitelisted hosts must use http/https, blocking scheme-based attacks during redirect following.

Source

Thrown at internal/utils/security.go:721

// every redirect target against SSRF protections.
func newSSRFCheckRedirect(maxRedirects int) func(*http.Request, []*http.Request) error {
	return func(req *http.Request, via []*http.Request) error {
		// Check redirect count
		if len(via) >= maxRedirects {
			return fmt.Errorf("stopped after %d redirects", maxRedirects)
		}

		// Strip credentials when the redirect crosses hosts so connector
		// tokens (e.g. Yuque X-Auth-Token) cannot leak to a third party.
		if len(via) > 0 && !sameHTTPOrigin(via[0].URL, req.URL) {
			stripRedirectSensitiveHeaders(req)
		}

		// Validate the redirect target URL for SSRF (whitelist-aware).
		// Even whitelisted hosts must use http/https to prevent scheme-based attacks.
		redirectScheme := strings.ToLower(req.URL.Scheme)
		if redirectScheme != "http" && redirectScheme != "https" {
			return fmt.Errorf("%w: invalid scheme %s", ErrSSRFRedirectBlocked, redirectScheme)
		}
		redirectHost := req.URL.Hostname()
		if redirectHost != "" && IsSSRFWhitelisted(redirectHost) {
			return nil
		}
		if err := validateURLForSSRFForOutbound(req.URL.String()); err != nil {
			return fmt.Errorf("%w: %w", ErrSSRFRedirectBlocked, err)
		}

		return nil
	}
}

// SSRFValidatingRoundTripper enforces the URL policy for every outbound
// request, including URLs discovered at runtime by SDKs (for example OAuth
// metadata) that never passed through an application handler. Dial-time checks
// remain necessary to pin DNS answers and cover transports that cannot accept
// this wrapper directly.

View on GitHub (pinned to 988cbb0330)

Solutions

  1. errors.Is(err, secutils.ErrSSRFRedirectBlocked) will match — handle it as a blocked redirect and stop.
  2. Only follow redirects from trusted servers; never point the client at untrusted endpoints that control Location.
  3. Ensure the upstream URL you call is https so any redirect it issues stays in-scheme.
  4. Check the server's Location header construction for a bug producing a bad scheme.

Example fix

// before
resp, err := client.Do(req) // server redirects to file://...
// after
if errors.Is(err, secutils.ErrSSRFRedirectBlocked) {
    log.Printf("refusing redirect: %v", err) // inspect/fix upstream Location scheme
}
Defensive patterns

Strategy: try-catch

Validate before calling

if u.Scheme != "http" && u.Scheme != "https" { return fmt.Errorf("unsupported scheme: %s", u.Scheme) }

Type guard

func isBlockedRedirectScheme(err error) bool {
    return errors.Is(err, secutils.ErrSSRFRedirectBlocked)
}

Try / catch

if errors.Is(err, secutils.ErrSSRFRedirectBlocked) {
    log.Printf("redirect rejected (check scheme/Location): %v", err)
    return err
}

Prevention

When it happens

Trigger: A server responds with a Location header whose scheme is not http/https while the client follows redirects through newSSRFCheckRedirect.

Common situations: Malicious or buggy servers emitting 'Location: file:///etc/passwd' or custom-scheme redirects; attacker-controlled endpoints attempting to pivot the client away from HTTP.

Related errors


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