siyuan-note/siyuan · warning
access to private/internal IP is prohibited
Error message
access to private/internal IP is prohibited
What it means
ssrfSafeDialContext is the agent outbound-dial function that resolves hostnames itself and refuses to connect to private/internal addresses — unconditionally, without relying on SafeMode. When the dialed host is a literal IP that classifies as private (loopback, RFC1918, link-local, unspecified, multicast, or IPv6 transition addresses embedding private IPv4), dialing fails with "access to private/internal IP is prohibited". Implemented to eliminate DNS-rebinding TOCTOU bypasses (GHSA-x8gv-g2g3-65fj).
Solutions
- Expose the target service via a publicly resolvable hostname/IP reachable over the internet
- If both processes are yours, communicate through the allowed channel (SiYuan kernel API) instead of a raw private endpoint
- If you need private access for a trusted deployment, use a client that does not install ssrfSafeDialContext and add your own explicit allowlist validation first
- Verify the URL host is not accidentally a private IP (typo'd 127.0.0.1 or a stub/test endpoint)
Example fix
// before reqURL := "http://127.0.0.1:11434/api/generate" // access to private/internal IP is prohibited // after: use the public endpoint of the service reqURL := "https://llm.example.com/api/generate"
Defensive patterns
Strategy: validation
Validate before calling
// Go: verify the URL host is a public literal IP before issuing an agent request
u, _ := url.Parse(reqURL)
if ip := net.ParseIP(u.Hostname()); ip != nil && (ip.IsPrivate() || ip.IsLoopback()) {
return fmt.Errorf("endpoint %s uses a private IP and will be blocked", reqURL)
} Prevention
- Never target localhost/private IPs from agent outbound requests — this guard is unconditional
- Use public hostnames for services agents must reach
- Treat 169.254.169.254-style URLs as attacker indicators, never valid targets
- Keep CheckHostSSRF checks aligned with the dialer so validation matches connection behavior
When it happens
Trigger: Making an outbound request through ssrfSafeDialContext (AI agent runtime / CheckHostSSRF-guarded clients) with a URL whose host is a literal private IP, e.g. http://127.0.0.1, http://10.0.0.5, http://169.254.169.254, or http://::1.
Common situations: Pointing an AI agent or webhook at a locally running service (local LLM, dev server, internal API); cloud metadata endpoint access attempts (169.254.169.254); a deployment where the service legitimately lives on the same host/LAN.
Related errors
- ip address [ ] is prohibited
- access to private/internal IP is prohibited
- failed to resolve host:
- generated image URL resolved to a private or invalid IP
- host has no public IP:
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/d9e5bb4aff8b9d13.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/net.go:206
},
}
}
// ssrfSafeDialContext 返回智能体出站请求专用的拨号函数:拨号时自行解析主机名并拒绝私网地址,
// 同时直接连接解析出的公网 IP,使 CheckHostSSRF 的守卫结果与拨号目标一致,
// 从根上杜绝 DNS 重绑定导致的 TOCTOU 绕过。
// 与 SSRFSafeDialer 不同,本拨号函数不依赖 SafeMode,始终强制执行。
// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-x8gv-g2g3-65fj
func ssrfSafeDialContext(timeout time.Duration) func(ctx context.Context, network, addr string) (net.Conn, error) {
dialer := &net.Dialer{Timeout: timeout}
return func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
if ip := net.ParseIP(host); ip != nil {
if isPrivateIP(ip) {
return nil, errors.New("access to private/internal IP is prohibited")
}
return dialer.DialContext(ctx, network, addr)
}
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
}View on GitHub (pinned to 9f775e8a12)