siyuan-note/siyuan · error

host has no public IP: %s

Error message

host has no public IP: %s

What it means

When the host is a hostname (not a literal IP), this dialer resolves all its IPs and requires at least one public address to proceed. If every resolved IP is private/internal (or resolution returned nothing usable), it returns 'host has no public IP' to block SSRF via DNS rebinding.

Source

Thrown at kernel/util/net.go:209

		ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
		if err != nil {
			return nil, err
		}
		var lastErr error
		for _, ipAddr := range ips {
			if isPrivateIP(ipAddr.IP) {
				continue
			}
			conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))
			if err == nil {
				return conn, nil
			}
			lastErr = err
		}
		if lastErr != nil {
			return nil, lastErr
		}
		return nil, errors.New("host has no public IP: " + host)
	}
}

// isPrivateIP 判断 IP 是否为私网地址,含内嵌私网 IPv4 的 IPv6 过渡地址(NAT64、6to4、Teredo、IPv4 兼容)。
// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-qq8m-8p8v-x4xg
// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-rg26-cg95-gq6p
func isPrivateIP(ip net.IP) bool {
	if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() ||
		ip.IsPrivate() || ip.IsUnspecified() || ip.IsMulticast() {
		return true
	}
	// Go 标准库的分类方法不识别 IPv6 过渡地址,需按 RFC 内嵌格式提取其中的 IPv4 后再递归判断。
	if ip4 := extractEmbeddedIPv4(ip); nil != ip4 && !ip4.Equal(ip) {
		return isPrivateIP(ip4)
	}
	return false
}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Point the hostname at a genuinely public IP, or use the SafeMode-permitting dialer if private access is intended
  2. Check DNS resolution (nslookup/dig the host) and remove private-only records
  3. If testing locally, run without the hardened dialer or bind via a public-facing reverse proxy

Example fix

// before
/etc/hosts: api.example.com 127.0.0.1  // dialer rejects
// after
# use the real public DNS record for api.example.com
Defensive patterns

Strategy: validation

Validate before calling

// resolve first, then decide
ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil { return err }
for _, ip := range ips {
    if !(ip.IP.IsPrivate() || ip.IP.IsLoopback()) { /* at least one public IP */ }
}

Try / catch

if _, err := doFetch(url); err != nil && strings.HasPrefix(err.Error(), "host has no public IP") {
    return fmt.Errorf("%s resolves only to private addresses; use a public host", url)
}

Prevention

When it happens

Trigger: Fetching from a hostname whose DNS resolves only to private addresses (e.g. internal DNS names, hosts-file entries pointing a domain at 127.0.0.1, split-horizon DNS).

Common situations: Developers adding /etc/hosts entries mapping public-looking domains to localhost; self-hosted services reachable only via LAN DNS names; misconfigured DNS returning private IPs.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/446d29335e26dbc2. Report an issue: GitHub.