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

  1. Target a genuinely public host whose DNS returns public IPs.
  2. 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.
  3. 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

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


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