Help us shape the future of Go! Take the 2026 Go Developer Survey

urlguard

package
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package urlguard 提供出站 URL 的 SSRF(Server-Side Request Forgery)防护原语, 供「出站目标是用户自配 URL」的服务(webhook 接收器:钉钉/飞书/企微机器人、 告警接入通道等)复用([oekit/httpx#11](https://github.com/oekit/httpx/issues/11))。

两层防护缺一不可:

层 1 CheckURL——URL 字面量校验:host 直接写私网/loopback IP、 .internal/.local/localhost 约定域名、非 http(s) scheme 一律拒绝,挡住低成本探测。

层 2 DialFunc——拨号层包装:host 解析出的**每一个** IP 都过 CheckIP(DNS rebinding / 劫持场景,URL 层对它无能为力),全部放行才调底层拨号;建连成功后再核对实际 RemoteAddr——预检与真实端点之间的重解析窗口(TOCTOU)被端点核对闭合, Happy-Eyeballs 双栈竞争的胜出路径同样被覆盖。

默认阻断范围:未指定地址(0.0.0.0/8、::)、loopback(127.0.0.0/8、::1)、 RFC1918 私网(10/8、172.16/12、192.168/16)、链路本地(169.254/16——含云元数据端点 169.254.169.254、fe80::/10)、CGNAT(100.64/10)、ULA(fc00::/7)、组播/保留 (224/4、240/4、ff00::/8、192.0.0.0/24、TEST-NET 192.0.2.0/24·198.51.100.0/24· 203.0.113.0/24、基准测试 198.18.0.0/15);域名 .internal / .local / .localdomain / localhost(含子域)。 IPv4-mapped IPv6(::ffff:10.0.0.1)先归一化为 IPv4 再判定。

Guard 在 New 之后只读,可并发共享。仅依赖标准库(pkg 零第三方依赖红线)。

用法(webhook 出口一行接入,[httpx](https://github.com/oekit/httpx) 的 WithDialFunc 直接组合):

g, err := urlguard.New()                       // 缺省全阻断;测试环境
// g, err := urlguard.New(urlguard.WithAllowPrivate()) // 打本机 mock server 用
if err != nil { ... }
if err := g.CheckURL(webhookURL); err != nil { ... }   // 层 1:入参校验
hc := &http.Client{Transport: &http.Transport{ DialContext: urlguard.SafeDialFunc(nil) }}
// 或与 httpx 组合:httpx.WithDialFunc(g.DialFunc(nil))

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsBlockedURL

func IsBlockedURL(raw string) error

IsBlockedURL 用缺省 Guard 做层 1 字面量校验(等价 New().CheckURL)。 nil 返回 = 放行。

Types

type DialFunc

type DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)

DialFunc 与 httpx/net 的拨号函数同形(标准库形态),可直接互相包装。

func SafeDialFunc

func SafeDialFunc(next DialFunc) DialFunc

SafeDialFunc 用缺省 Guard 包装拨号函数(等价 New().DialFunc(next))。 next 为 nil 时用 net 的默认拨号。

type Guard

type Guard struct {
	// contains filtered or unexported fields
}

Guard 是 SSRF 防护器:New 之后只读,可并发共享。

func New

func New(opts ...Option) (*Guard, error)

New 构造 Guard。CIDR 文法非法时报错(fail-fast,不带病运行)。

func (*Guard) CheckHost

func (g *Guard) CheckHost(host string) error

CheckHost 判定主机名/IP 字面量是否可放行(nil 返回 = 放行)。 host 不带端口;大小写与尾点不敏感。

func (*Guard) CheckIP

func (g *Guard) CheckIP(ip net.IP) error

CheckIP 判定单个 IP 是否可放行(nil 返回 = 放行)。 IPv4-mapped IPv6 先归一化为 IPv4 再判定。

func (*Guard) CheckURL

func (g *Guard) CheckURL(raw string) error

CheckURL 做层 1 字面量校验(nil 返回 = 放行):scheme 必须在白名单内, host 走 CheckHost。仅校验字面量——DNS rebinding 场景必须配合 DialFunc(层 2)。

func (*Guard) DialFunc

func (g *Guard) DialFunc(next DialFunc) DialFunc

DialFunc 返回层 2 拨号包装:解析出的每个 IP 先过预检(任一命中阻断即不建连, fail-fast 且错误可读),建连成功后再核对实际 RemoteAddr——两道校验之间的 重解析窗口被端点核对闭合。next 为 nil 时用 net 默认拨号。

type Option

type Option func(*Guard)

Option 配置 Guard。

func WithAllowCIDRs

func WithAllowCIDRs(cidrs ...string) Option

WithAllowCIDRs 按网段显式豁免(如内网 webhook 端点 "10.20.0.0/16")。 网段文法非法时 New 报错。

func WithAllowDomains

func WithAllowDomains(domains ...string) Option

WithAllowDomains 按域名豁免(小写化后保存;命中语义 = 等于或点分后缀, "example.com" 同时放行 "api.example.com";不支持通配符前缀,写根域即可)。

func WithAllowPrivate

func WithAllowPrivate() Option

WithAllowPrivate 放行全部内网/loopback 地址——仅供测试环境打本机 mock server; 生产构造不要使用。

func WithAllowedSchemes

func WithAllowedSchemes(schemes ...string) Option

WithAllowedSchemes 覆盖 CheckURL 的 scheme 白名单(缺省 http/https)。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL