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
- Enable the task's 'allow private network' option if reaching an internal endpoint is intended (security-reviewed)
- Target a public endpoint instead of the internal address
- 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
- Treat any 127./10./172.16-31./192.168./169.254. target as intentional only when allowPrivate is set
- Document which tasks are allowed to hit internal endpoints and review them periodically
- Check DNS resolution from the server host, not your laptop — split-horizon DNS often resolves public names internally
- Prefer explicit internal hostnames with allowPrivate over reusing 'localhost' in shared environments
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.