larksuite/cli · error

local/internal host is not allowed

Error message

local/internal host is not allowed

What it means

The download-source validator rejects any URL whose hostname is literally "localhost" or ends in ".localhost" before any DNS lookup happens. This is part of the SSRF protection in resolveDownloadHost: untrusted download URLs must point at public hosts only, so local/loopback names are blocked unconditionally. The check is purely on the lowercased, trimmed hostname string, so no network access occurs.

Source

Thrown at internal/validate/url.go:101

	if err != nil || u == nil {
		return fmt.Errorf("invalid URL")
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return fmt.Errorf("only http/https URLs are supported")
	}
	_, err = resolveDownloadHost(ctx, u.Hostname(), net.DefaultResolver.LookupIP)
	return err
}

type downloadLookupIPFunc func(context.Context, string, string) ([]net.IP, error)

func resolveDownloadHost(ctx context.Context, rawHost string, lookupIP downloadLookupIPFunc) ([]net.IP, error) {
	host := strings.TrimSpace(strings.ToLower(rawHost))
	if host == "" {
		return nil, fmt.Errorf("URL host is required")
	}
	if host == "localhost" || strings.HasSuffix(host, ".localhost") {
		return nil, fmt.Errorf("local/internal host is not allowed")
	}
	if ip := net.ParseIP(host); ip != nil {
		if isRestrictedDownloadIP(ip) {
			return nil, fmt.Errorf("local/internal host is not allowed")
		}
		return []net.IP{ip}, nil
	}
	if lookupIP == nil {
		lookupIP = net.DefaultResolver.LookupIP
	}
	ips, err := lookupIP(ctx, "ip", host)
	if err != nil {
		return nil, fmt.Errorf("failed to resolve host")
	}
	if len(ips) == 0 {
		return nil, fmt.Errorf("failed to resolve host")
	}
	for _, ip := range ips {

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Replace the localhost / *.localhost hostname with the public production hostname the download is meant to come from.
  2. If you need a local test server, expose it via a public tunnel (ngrok, cloudflared) and use the resulting public https URL.
  3. If hosting your own mirror, deploy it on a host with a public IP and ensure DNS resolves it to a non-restricted address.
  4. Check environment-specific config (e.g. a base-URL or mirror setting) for leftover local development values.

Example fix

// before
err := validate.ValidateDownloadSourceURL(ctx, "http://localhost:8080/file.zip")
// after
err := validate.ValidateDownloadSourceURL(ctx, "https://cdn.example.com/file.zip")
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(downloadURL)
if err == nil && u != nil {
    h := strings.ToLower(strings.TrimSpace(u.Hostname()))
    if h == "localhost" || strings.HasSuffix(h, ".localhost") {
        return fmt.Errorf("refusing to download from local host %q", h)
    }
}

Type guard

func isPublicDownloadHost(rawURL string) bool {
    u, err := url.Parse(rawURL)
    if err != nil || u == nil || (u.Scheme != "http" && u.Scheme != "https") {
        return false
    }
    h := strings.ToLower(strings.TrimSpace(u.Hostname()))
    if h == "" || h == "localhost" || strings.HasSuffix(h, ".localhost") {
        return false
    }
    if ip := net.ParseIP(h); ip != nil {
        return !ip.IsLoopback() && !ip.IsPrivate() && !ip.IsLinkLocalUnicast() && !ip.IsUnspecified()
    }
    return true
}

Prevention

When it happens

Trigger: Calling ValidateDownloadSourceURL(ctx, url) with a URL like http://localhost:8080/file, http://foo.localhost/file, or HTTPS://LOCALHOST/x (case-insensitive, whitespace-trimmed). Also raised inside NewDownloadHTTPClient redirect handling when a redirect target resolves to a *.localhost host, and via proxyAwareDownloadTransport.RoundTrip when the request URL host is a localhost name.

Common situations: Pointing a download/config option at a locally running dev server (localhost:3000) or a container-internal service (api.localhost); testing the CLI against a local mock; switching an environment from a local stub to production without updating the URL; IPv6 ::1 typed differently is caught by the IP branch instead.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/b51365fba442bd62. Report an issue: GitHub.