siyuan-note/siyuan · error
failed to resolve host:
Error message
failed to resolve host:
What it means
CheckHostSSRF performs a DNS lookup of the requested host before any outbound agent HTTP request (web_fetch / http_request) so it can block private/loopback addresses. If net.LookupIP fails — the hostname does not resolve, DNS is down, or the host string is malformed — the request is rejected with this error wrapping the resolver message.
Source
Thrown at kernel/util/httprequest.go:52
"time"
"github.com/siyuan-note/httpclient"
golangProxy "golang.org/x/net/proxy"
)
const (
maxHTTPRequestBytes = 5 * 1024 * 1024 // text/html、text/plain、application/json 等文本类响应上限
maxHTTPRequestFileBytes = 10 * 1024 * 1024 // 二进制响应落盘上限
maxHTTPRequestChars = 50000
)
// CheckHostSSRF 校验主机名解析出的 IP 不落在内网/回环等不可达地址段,
// 防止智能体被诱导发起 SSRF 攻击。web_fetch 与 http_request 共用此校验。
// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-rg26-cg95-gq6p
func CheckHostSSRF(host string) error {
ips, err := net.LookupIP(host)
if err != nil {
return errors.New("failed to resolve host: " + err.Error())
}
for _, ip := range ips {
// 与 SSRFSafeDialer 共用 isPrivateIP,覆盖 NAT64、6to4、Teredo 等 IPv6 过渡地址。
if isPrivateIP(ip) {
return errors.New("access to private/internal IP is prohibited")
}
}
return nil
}
// ssrfSafeClient 是智能体出站请求专用的 HTTP 客户端:直连时将目标固定到已校验的公网 IP,
// 使用代理时则先与用户配置的代理建立隧道,再通过隧道连接固定后的目标 IP,同时保留原始 Host 和 TLS SNI。
// 两种方式都不会在校验后再次按目标域名解析,避免 DNS 重绑定 TOCTOU 绕过。
// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-x8gv-g2g3-65fj
var ssrfSafeClient = newSSRFSafeClient()
func newSSRFSafeClient() *http.Client {
return newSSRFSafeClientWithResolver(net.DefaultResolver.LookupIPAddr)View on GitHub (pinned to 8641553a1f)
Solutions
- Verify the hostname spelling and that it is a real registered domain (dig/nslookup the host from the same machine)
- Check the machine's DNS configuration (/etc/resolv.conf, systemd-resolved) and network connectivity
- Retry once DNS is restored — this is a transient/environmental failure, not a policy block (a policy block returns the private-IP error instead)
- If a local/hosts-file name is intended, it will still fail the SSRF policy by design; use the public domain instead
Example fix
// before HTTPRequest(ctx, "https://exmaple.com/api") // typo, DNS fails // after HTTPRequest(ctx, "https://example.com/api")
Defensive patterns
Strategy: retry
Validate before calling
addrs, err := net.LookupIP(host)
if err != nil || len(addrs) == 0 {
return fmt.Errorf("host %q does not resolve; aborting request", host)
} Try / catch
if err := CheckHostSSRF(host); err != nil {
if strings.Contains(err.Error(), "failed to resolve host") {
time.Sleep(retryBackoff)
return retryFetch(url, 2)
}
return err // policy blocks are not retryable
} Prevention
- Validate and normalize URLs extracted from model output before requesting them
- Check DNS health (resolv.conf, systemd-resolved) in sandboxed/containerized environments
- Distinguish resolver failures (transient) from private-IP blocks (policy) by message before retrying
When it happens
Trigger: HTTPRequest, WebFetch, downloadGeneratedImage, or downloadSkillSource called with a hostname that cannot be resolved: typo'd domain, expired domain, offline/filtered DNS, or a garbage host string.
Common situations: An AI agent hallucinating a URL; corporate DNS blocking external lookups; IPv6-only or sandboxed environments without resolv.conf; typo like 'htps://example.com' producing an unparseable host.
Related errors
- host has no public IP:
- host has no public IP: %s
- access to private/internal IP is prohibited
- host has no IP address:
- ip address [%s] is prohibited
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/836b5c703117c5ba.
Report an issue: GitHub.