Repository navigation
Closed
Description
结论
层 1(CheckURL/CheckHost 字面量判定)对 host 尾部的分隔符变体整体失明:127.0.0.1。(单 ASCII 尾点)被正确阻断,但只要在尾部再多一个点(ASCII ..)或把尾点写成全角形态(。.。),同一目标全部从「BLOCKED」翻成「PASS」——回环 / 云 metadata / localhost 约定域三类层 1 专门要挡的目标全部漏过。包内 127。0。0。1(内部全角)被 #266 正确阻断,尾部形态是那次修复的漏网。
实测(当前 main,42d836e)
① ASCII 尾点:单点拦、双点放
CheckURL "http://localhost/" => BLOCKED: urlguard: blocked domain suffix: host "localhost" uses blocked domain "localhost"
CheckURL "http://localhost./" => BLOCKED: urlguard: blocked domain suffix: host "localhost" uses blocked domain "localhost"
CheckURL "http://localhost../" => PASS
CheckURL "http://localhost.../" => PASS
CheckURL "http://127.0.0.1/" => BLOCKED: urlguard: address in blocked range: 127.0.0.1 is in blocked range 127.0.0.0/8
CheckURL "http://127.0.0.1./" => BLOCKED: urlguard: address in blocked range: 127.0.0.1 is in blocked range 127.0.0.0/8
CheckURL "http://127.0.0.1../" => PASS
CheckURL "http://169.254.169.254./" => BLOCKED: urlguard: address in blocked range: 169.254.169.254 is in blocked range 169.254.0.0/16
CheckURL "http://169.254.169.254../" => PASS
CheckURL "http://foo.localhost./" => BLOCKED: urlguard: blocked domain suffix: host "foo.localhost" uses blocked domain "localhost"
CheckURL "http://foo.localhost../" => PASS
② 全角/半角尾分隔符:内部被拦、尾部全放
CheckURL "http://127.0.0.1./" => BLOCKED: urlguard: address in blocked range: 127.0.0.1 is in blocked range 127.0.0.0/8
CheckURL "http://127.0.0.1。/" => PASS
CheckURL "http://127.0.0.1./" => PASS
CheckURL "http://127.0.0.1。/" => PASS
CheckURL "http://localhost。/" => PASS
CheckURL "http://localhost./" => PASS
CheckURL "http://169.254.169.254。/" => PASS
CheckURL "http://foo.localhost。/" => PASS
CheckURL "http://0.0.0.0。/" => PASS
CheckURL "http://127。0。0。1/" => BLOCKED: urlguard: address in blocked range: 127.0.0.1 is in blocked range 127.0.0.0/8
CheckURL "http://127.0.0.1。../" => PASS
③ WHATWG 客户端(node)确实连得上——与 #266 同款威胁口径
node:
"127.0.0.1。" => hostname: "127.0.0.1"
"127.0.0.1." => hostname: "127.0.0.1"
"169.254.169.254。" => hostname: "169.254.169.254"
"127。0。0。1" => hostname: "127.0.0.1"
dns.lookup("127.0.0.1") => 127.0.0.1
dns.lookup("169.254.169.254") => 169.254.169.254
即:判定放行 + WHATWG 解析器归一后连回环/metadata ——与 #266 被接受时的论证逐字同构(「浏览器与 node 的 URL 解析器会把 …归一成 127.0.0.1 再连接,纯 ASCII 判定面对它们全部失明——判定放行 + 客户端连回环」)。
根因
canonicalHost用strings.TrimSuffix(host, ".")——只剥一个 ASCII 尾点,localhost..剥完还剩localhost.;且它在checkHostLiteral里先于foldHostForGuard执行,全角尾分隔符(。.。→.)折叠发生在 trim 之后,折叠产物再也得不到剥点。- 折叠后的
127.0.0.1.两个判据都接不住:net.ParseIP("127.0.0.1.")= nil(不认尾点);looksLikeExoticIPv4对它split(".")得 5 段 →len(parts) > 4直接return false(4 段空串变体127.1.也因空段parseIPv4Part("")早退而漏网)。 - 于是走到「普通域名」分支 → 无后缀命中 →
checkHostPolicy(未注入钩子恒 nil)→ PASS。
边界(诚实说明)
- Go 拨号链路今天碰巧挡住:
net.DefaultResolver(纯 Go 与 GODEBUG=netdns=cgo 两种模式都实测)与 macOSgetaddrinfo、nodedns.lookup对未归一的127.0.0.1../127.0.0.1。均ENOTFOUND;层 2DialFunc里resolve同样失败。当前可证实的连接路径是 WHATWG 归一后的客户端(③)。 - 但层 1 的契约就是包文档写的「host 直接写私网/loopback IP …一律拒绝,挡住低成本探测」与「看不懂的名字一律不允许指向内网判定放行」——同一 host 加一个尾点/换一个全角点即从拒到放,是判定面 fail-closed 口径的直接破口;[urlguard] CheckURL 放行 exotic IPv4 字面量形态(0x7f000001 / 127.1 / 0177.0.0.1)——回环绕过 #73(exotic)、[pkg] urlguard: 全角数字与 Unicode 句点形式的回环地址全部放行(判定面只覆盖 ASCII) #266(全角)、urlguard: WithAllowPrivate 跳过 exotic IPv4 字面量闸门,allowPrivate 消费方可经十进制整型字面量可达云 metadata(实证) #236(allowPrivate 跳闸门)都是按这个口径修的,本条是同一条纪律在尾部维度的漏网。
修法方向
- fold 之后重新执行尾点归一:
canonicalHost改strings.TrimRight(host, ".")(剥全部 ASCII 尾点),并在checkHostLiteral拿到folded之后再剥一次尾点; looksLikeExoticIPv4补空段/尾点形态(任一段为空 → 按候选处理而不是return false);- 建议加回归表:
{localhost, localhost., localhost.., localhost。, 127.0.0.1, 127.0.0.1., 127.0.0.1.., 127.0.0.1。, 127.0.0.1., 169.254.169.254.。, foo.localhost..}全组断言 BLOCKED。
探针:urlguard.New() + CheckURL 逐 URL 打印 verdict;node 侧 new URL().hostname + dns.lookup。复现零依赖,贴上的输出均为本机 GOWORK=off go run 实跑。
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
- StatusShow more project fieldsDone
Activity
Merge pull request #421 from oekit/sweep/20261112-l1
zy84338719 commentedon Nov 12, 2026
已合入修复 ✅(无 SemVer 发布:本次改动不发版本)
变更内容
urlguard 层1 尾部分隔符变体收口——剥全部 ASCII 尾点 + 全角折叠后再剥 + 空段 fail-closed
合入:PR #421(https://github.com/oekit/pkg/pull/421),merge commit
63917515,2026-11-12T17:10:03ZCI:
link✅ /Lint & Test / Test✅ /Lint & Test / Lint✅ /Zero Third-Party Deps✅