siyuan-note/siyuan · error
access to private/internal IP is prohibited
Error message
access to private/internal IP is prohibited
What it means
Returned by CheckHostSSRF when any IP returned by net.LookupIP is classified as private by isPrivateIP (net.go:175). isPrivateIP covers loopback, link-local, multicast, unspecified, RFC1918/IsPrivate ranges, and embedded-IPv4 IPv6 transition addresses (NAT64, 6to4, Teredo). This is the core SSRF defense (GHSA-rg26-cg95-gq6p) and is by design non-bypassable for the http_request / web_fetch tools.
Source
Thrown at kernel/util/httprequest.go:50
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
}
// HTTPRequest 发起一次通用 HTTP 调用,供智能体 http_request 工具使用。
// 与 WebFetch 不同:本函数不做 HTML→Markdown 转换,文本类响应(含 JSON/XML)原样返回,
// 便于智能体直接消费 REST API 的 JSON 输出。method 取值:GET/POST/PUT/DELETE/PATCH。
// 返回的 text 为响应正文(文本类)或落盘后的文件路径(二进制类)。
func HTTPRequest(method, rawURL string, headers map[string]string, body string) (statusCode int, contentType string, text string, err error) {
u, err := url.Parse(rawURL)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
return 0, "", "", errors.New("URL must start with http:// or https://")
}
if u.Host == "" {
return 0, "", "", errors.New("URL has no host")
}
View on GitHub (pinned to 251596fc0d)
Solutions
- Target a genuinely public host whose DNS returns public IPs.
- If the internal service must be reached, do not route it through the SSRF-guarded tools — call it directly from trusted kernel code, not via the agent HTTP tool.
- Re-check the hostname is not resolving to a private IP via split-horizon DNS (dig from the kernel host).
Example fix
// before
util.HTTPRequest("GET", "http://localhost:8080/health", nil, "")
// after
// localhost is private and always rejected by design —
// call the internal endpoint through a trusted kernel helper instead of the agent HTTP tool.
model.ProbeInternalHealth() Defensive patterns
Strategy: validation
Validate before calling
func hostIsPublic(host string) bool {
ips, err := net.LookupIP(host)
if err != nil { return false }
for _, ip := range ips {
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() {
return false
}
}
return true
} Prevention
- Do not try to bypass SSRF — call internal services from trusted kernel code instead of the agent HTTP tool.
- Beware split-horizon DNS: a 'public' hostname may resolve internally inside the deployment.
When it happens
Trigger: HTTPRequest or web_fetch targets a host that resolves (or also resolves) to 127.0.0.0/8, 10/8, 172.16/12, 192.168/16, 169.254/16, ::1, fc00::/7, or an IPv6 transition address embedding one of those.
Common situations: An agent (or user) pointing the tool at localhost or an internal service to probe the kernel host; a public hostname with a split-horizon DNS that returns an internal IP inside the deployment; link-local 169.254.x.x (cloud metadata, e.g. AWS 169.254.169.254); a NAT64/6to4 hostname used to disguise a private v4 address.
Related errors
- failed to resolve host: %s
- ip address [%s] is prohibited
- invalid custom emoji URL
- checksum manifest is too large
- URL must start with http:// or https://
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/80138dc6243591f0.
Report an issue: GitHub.