siyuan-note/siyuan · error

ip address [%s] is prohibited

Error message

ip address [%s] is prohibited

What it means

SSRFSafeDialer's Control hook resolves the destination and, when SafeMode is on, blocks any private/loopback/link-local/unsup IP (including IPv6 transition addresses that embed private v4) by returning this error. In non-SafeMode the same dialer only logs a warning. It is the SSRF guard for outbound HTTP from AI, sync, bazaar, and plugin features.

Source

Thrown at kernel/util/net.go:164

	host = strings.ToLower(strings.TrimSuffix(strings.TrimSuffix(strings.TrimSpace(host), ":80"), ":443"))
	return "" != originHost && originHost == host
}

// SSRFSafeDialer returns a net.Dialer whose Control hook blocks private, loopback, link-local and unspecified IPs.
func SSRFSafeDialer(timeout time.Duration) *net.Dialer {
	return &net.Dialer{
		Timeout: timeout,
		Control: func(network, address string, _ syscall.RawConn) error {
			host, _, err := net.SplitHostPort(address)
			if err != nil {
				return err
			}
			if ip := net.ParseIP(host); ip != nil && isPrivateIP(ip) {
				if _, loaded := auditedAddresses.LoadOrStore(address, struct{}{}); !loaded {
					logging.LogWarnf("Establishing a connection to the private network [address=%s, network=%s]", address, network)
				}
				if SafeMode {
					return fmt.Errorf("ip address [%s] is prohibited", host)
				}
			}
			return nil
		},
	}
}

// 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)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Restart the kernel WITHOUT SafeMode when you legitimately need to reach a trusted private/loopback endpoint.
  2. Point the endpoint at a genuinely public hostname/IP.
  3. Do not weaken the dialer; SafeMode is opt-in for diagnostics, so a normal launch re-enables private targets (still audited).
  4. Audit plugin/AI endpoints for private addresses before enabling SafeMode.

Example fix

# before: launched in safe mode
siyuan --safe   # blocks 127.0.0.1:11434 (local Ollama)

# after: normal launch
siyuan          # same private endpoint allowed, still logged
Defensive patterns

Strategy: validation

Validate before calling

if util.SafeMode {
    if ip := net.ParseIP(host); ip != nil && isPrivateIP(ip) {
        return errors.New("target blocked in SafeMode; restart normally")
    }
}

Type guard

func SafeToDial(host string) bool {
    if !util.SafeMode { return true }
    ip := net.ParseIP(host)
    return ip == nil || !isPrivateIP(ip)
}

Prevention

When it happens

Trigger: Kernel launched in SafeMode makes an outbound request whose hostname resolves to a private address (127.0.0.1, 10/172.16/192.168, .local mDNS, ::1, NAT64/6to4/Teredo embedding private v4).

Common situations: AI/embedding endpoint pointed at a localhost service (e.g. local Ollama on 127.0.0.1:11434) while in SafeMode; plugin targeting an internal service; DNS rebinding where a public hostname resolves to a private IP; testing against a local LLM.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/448491c89fc442d6. Report an issue: GitHub.