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
- Restart the kernel WITHOUT SafeMode when you legitimately need to reach a trusted private/loopback endpoint.
- Point the endpoint at a genuinely public hostname/IP.
- Do not weaken the dialer; SafeMode is opt-in for diagnostics, so a normal launch re-enables private targets (still audited).
- 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
- Exit SafeMode to reach trusted internal endpoints
- Use public hostnames for AI/sync targets
- Audit plugin endpoints for private addresses before enabling SafeMode
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
- access to private/internal IP is prohibited
- generated image URL must use HTTPS
- generated image redirect is not allowed
- generated image URL resolved to a private or invalid IP
- invalid custom emoji URL
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/448491c89fc442d6.
Report an issue: GitHub.