flipped-aurora/gin-vue-admin · error · errPrivateAddr

%w: %s

Error message

%w: %s

What it means

The SSRF guard rejects dialing targets whose resolved IP is loopback, private, link-local (unicast/multicast), or unspecified, wrapping the sentinel errPrivateAddr with the offending IP. This blocks timed HTTP tasks from reaching internal infrastructure (SSRF mitigation); the message explicitly tells you the task's 'allow private' option can exempt it.

Source

Thrown at server/service/system/sys_timed_task_http.go:37

}

// ssrfControl 在拨号阶段(DNS 解析后、连接建立前)校验目标 IP:
// 每次连接都过检, 天然覆盖重定向与 DNS rebinding(TOCTOU 安全)。
func ssrfControl(allowPrivate bool) func(network, address string, c syscall.RawConn) error {
	return func(_ string, address string, _ syscall.RawConn) error {
		if allowPrivate {
			return nil
		}
		host, _, err := net.SplitHostPort(address)
		if err != nil {
			return fmt.Errorf("解析拨号地址失败: %w", err)
		}
		ip := net.ParseIP(host)
		if ip == nil {
			return fmt.Errorf("非法拨号 IP: %s", host)
		}
		if isDisallowedIP(ip) {
			return fmt.Errorf("%w: %s", errPrivateAddr, ip)
		}
		return nil
	}
}

// newTimedTaskHTTPClient 定时任务专用 HTTP 客户端:
// 整体超时 + 禁用环境代理(防经代理绕过 IP 校验) + 拨号层 SSRF 防护
func newTimedTaskHTTPClient(allowPrivate bool, timeout time.Duration) *http.Client {
	dialer := &net.Dialer{
		Timeout: 10 * time.Second,
		Control: ssrfControl(allowPrivate),
	}
	transport := &http.Transport{
		Proxy:       nil, // 显式禁用代理
		DialContext: dialer.DialContext,
	}
	return &http.Client{Timeout: timeout, Transport: transport}
}

View on GitHub (pinned to 3136500ef3)

Solutions

  1. Enable the task's 'allow private network' option if reaching an internal endpoint is intended (security-reviewed)
  2. Target a public endpoint instead of the internal address
  3. If the target should be public but resolves internally, check DNS records / split-horizon DNS for that hostname

Example fix

// before
{"name":"ping-internal","executorType":"http","httpUrl":"http://127.0.0.1:8080/health","allowPrivate":false}
// after
{"name":"ping-internal","executorType":"http","httpUrl":"http://127.0.0.1:8080/health","allowPrivate":true} // after security review
Defensive patterns

Strategy: validation

Validate before calling

addrs, _ := net.LookupHost(u.Hostname())
for _, a := range addrs {
    ip := net.ParseIP(a)
    if ip != nil && (ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified()) {
        // internal target: set allowPrivate or change the endpoint before scheduling
    }
}

Type guard

func isPublicIP(ip net.IP) bool {
    return ip != nil && !ip.IsLoopback() && !ip.IsPrivate() && !ip.IsLinkLocalUnicast() && !ip.IsLinkLocalMulticast() && !ip.IsUnspecified()
}

Try / catch

resp, err := client.Do(req)
if err != nil {
    if errors.Is(err, errPrivateAddr) || strings.Contains(err.Error(), "SSRF") {
        // blocked by SSRF guard: enable allowPrivate (after review) or switch endpoint
        return
    }
    return err
}

Prevention

When it happens

Trigger: An HTTP-executor timed task targets a hostname/IP that DNS resolves to 127.0.0.1, ::1, 10.x/172.16-31.x/192.168.x, 169.254.x, or 0.0.0.0 while the task's allowPrivate flag is off.

Common situations: Pointing tasks at internal services (http://localhost:8080, http://192.168.1.10) during development; DNS inside the server resolving public-looking names to internal IPs; targets behind corporate NAT; on-host agents listening on loopback.

Related errors


AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31). Data as JSON: /api/errors/c46273d120bd960f. Report an issue: GitHub.