{"record":{"id":"d9e5bb4aff8b9d13","repo":"siyuan-note/siyuan","slug":"access-to-private-internal-ip-is-prohibited-d9e5bb","errorCode":null,"errorMessage":"access to private/internal IP is prohibited","messagePattern":"access to private/internal IP is prohibited","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/util/net.go","lineNumber":206,"sourceCode":"\t\t},\n\t}\n}\n\n// ssrfSafeDialContext 返回智能体出站请求专用的拨号函数：拨号时自行解析主机名并拒绝私网地址，\n// 同时直接连接解析出的公网 IP，使 CheckHostSSRF 的守卫结果与拨号目标一致，\n// 从根上杜绝 DNS 重绑定导致的 TOCTOU 绕过。\n// 与 SSRFSafeDialer 不同，本拨号函数不依赖 SafeMode，始终强制执行。\n// https://github.com/siyuan-note/siyuan/security/advisories/GHSA-x8gv-g2g3-65fj\nfunc ssrfSafeDialContext(timeout time.Duration) func(ctx context.Context, network, addr string) (net.Conn, error) {\n\tdialer := &net.Dialer{Timeout: timeout}\n\treturn func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\thost, port, err := net.SplitHostPort(addr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif ip := net.ParseIP(host); ip != nil {\n\t\t\tif isPrivateIP(ip) {\n\t\t\t\treturn nil, errors.New(\"access to private/internal IP is prohibited\")\n\t\t\t}\n\t\t\treturn dialer.DialContext(ctx, network, addr)\n\t\t}\n\t\tips, err := net.DefaultResolver.LookupIPAddr(ctx, host)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tvar lastErr error\n\t\tfor _, ipAddr := range ips {\n\t\t\tif isPrivateIP(ipAddr.IP) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tconn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))\n\t\t\tif err == nil {\n\t\t\t\treturn conn, nil\n\t\t\t}\n\t\t\tlastErr = err\n\t\t}","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/net.go#L188-L224","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\nreqURL := \"http://127.0.0.1:11434/api/generate\" // access to private/internal IP is prohibited\n// after: use the public endpoint of the service\nreqURL := \"https://llm.example.com/api/generate\"","handlingStrategy":"validation","validationCode":"// Go: verify the URL host is a public literal IP before issuing an agent request\nu, _ := url.Parse(reqURL)\nif ip := net.ParseIP(u.Hostname()); ip != nil && (ip.IsPrivate() || ip.IsLoopback()) {\n    return fmt.Errorf(\"endpoint %s uses a private IP and will be blocked\", reqURL)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["go","network","ssrf","security"],"backgroundTag":"private-ip-blocked","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}