⭐ 推荐:社区规则条款 V1.0

Whois数据公开与个人信息保护之间怎么平衡?

域名注册
域名注册 版主萌新站长 👑黑卡会员
发布于 2026-10-28 04:41 ·8 浏览 ·12 回复
内容摘要

将 Whois/RDAP 字段分为必须公开、可公开但遮蔽、绝不外泄三层,注册时默认开启隐私保护,自建查询仅回显公开字段并统一遮蔽注册人信息,且应填真实信息加隐私保护而非假数据。

学完这篇,你能拿到一套可落地的做法:在域名注册、站点展示、日志留存三个环节里,把 Whois(以及替代它的 RDAP)数据分成「必须公开」「可以公开但要遮蔽」「绝不外泄」三层,既不违反信息披露要求,也不把注册人的个人信息直接摊在公网上。

第一步:先把数据分成三层,别一锅端

这一步要做的是给每条 Whois 字段贴上敏感级别标签,做完你会得到一张字段清单,后面所有配置都照着它执行。

  • 第一层,必须公开:域名本身、注册商(Registrar)、注册日期、到期日期、域名状态码(如 clientTransferProhibited)、DNS 服务器。这些是判断域名归属和是否可交易的基础信息,藏起来没有意义。
  • 第二层,公开但需遮蔽:注册人姓名、邮箱、电话、通信地址。这层是争议核心,正确处理是「显示为已遮蔽」而不是「显示为空」或「显示假数据」。
  • 第三层,绝不外泄:身份证号、营业执照扫描件、支付记录、实名认证材料。这类只存在于注册商后台,任何对外接口都不应返回。

注意:第二层很多人图省事,填一串 abc abc 假数据。结果域名到期提醒邮件发不出去、转移域名时验证失败,最后卡在找回流程上。正确做法是填真实信息 + 开隐私保护,不是填假信息。

第二步:注册域名时把「隐私保护」当默认选项

这一步是在注册商后台开启 Whois 隐私保护,做完之后公网查询你域名时,第二层字段显示的是注册商的代理信息而不是你的真实资料。

以主流注册商为例:

  • 阿里云:登录后进入「域名控制台」→ 左侧「域名列表」→ 找到目标域名点右侧「管理」→ 左侧菜单「域名信息」→ 找到「隐私保护」开关打开。
  • 腾讯云:进入「域名注册」→「我的域名」→ 点域名右侧「管理」→「域名信息」→ 开启「WHOIS 隐私保护」。

各注册商菜单文字略有差异,在控制台搜索框直接搜「隐私保护」最快。

注意:隐私保护开启后,域名的所有权归属仍以注册商后台的实名信息为准,公网看到的是代理信息。这不会影响你转出域名或过户,但转移时会向你的注册邮箱发确认信,所以邮箱必须真实可用。另外,GDPR 生效后(2018 年 5 月 25 日),大量注册局和注册商对个人注册数据已默认做 redaction(遮蔽处理),你查到的「REDACTED FOR PRIVACY」就是这套机制的产物,不是数据丢失。

第三步:站点如果提供 Whois 查询工具,必须做字段级过滤

这一步针对自建查询页。很多人直接调注册商的原始接口,把返回的 JSON 原样渲染到页面上,等于替别人泄露了信息。

做完这一步,你的查询页只回显第一层字段,第二层统一返回固定文案。参考过滤逻辑(Python 伪代码):

PUBLIC_FIELDS = ["domain", "registrar", "creation_date",
                 "expiration_date", "status", "name_servers"]
REDACT_FIELDS = ["registrant_name", "registrant_email",
                 "registrant_phone", "registrant_address"]

def render_whois(raw: dict) -> dict:
    out = {k: raw.get(k) for k in PUBLIC_FIELDS}
    for k in REDACT_FIELDS:
        out[k] = "REDACTED FOR PRIVACY"
    return out

如果你在用 RDAP(Registration Data Access Protocol,正在逐步替代传统 WHOIS 端口 43 查询),返回体是结构化 JSON,字段过滤比解析纯文本容易得多,建议优先接 RDAP。

注意:社区里常见的坑是「截图泄露」。在论坛发帖求助时贴了完整的 Whois 截图,忘了打码注册人邮箱和电话。GJ站长论坛的版规对这类内容按侵权处理,处置参照处罚阶梯:轻度是站内警告 + 删除违规内容,中度是限时禁言、扣金币或经验,重度直接永久封禁账号。发图前把注册人那几行涂掉,三十秒的事。

第四步:管理端留全量,展示端做脱敏,两边分开

这一步是把「存什么」和「显示什么」彻底解耦,做完之后即使哪天误配了展示逻辑,也不会把底层原始数据带出去。

具体做法:数据库里保留原始返回(供内部核验、纠纷举证用),另建一张对外视图或缓存表,只存过滤后的结果,前端只读这张表。查询接口按 IP 做限流,避免有人拿你的页面当批量抓取工具。GJ站长论坛的搜索限流就是这个思路——登录账号按账号统计,游客按 IP 统计,关键词最短 2 字符。

第五步:给「要完整数据的人」留一条正式通道

这一步是给执法部门、权利人或商标持有人准备的申请路径,做完之后你不用在私信里零散地给数据,也不用硬扛。

标准流程是三步:提交书面申请(说明身份、事由、涉及域名)→ 平台核对材料 → 仅在核验通过后定向披露,并留存披露记录。社区场景里同样的逻辑:用户在帖子或回复里点举报 → 记录自动归集到【反馈举报】版块 → 版主或管理员核验处置,举报记录永久留存可回溯。

注意:不要用「加个微信私下给你」的方式处理这类请求。没有记录,一旦后续出现争议,你无法证明自己披露得是否合规。

第六步:定期复盘

每季度做一次检查:域名列表里有没有漏开隐私保护的新域名、查询页有没有新加的字段绕过了过滤函数、披露记录是否完整。三件事半小时能做完。

小结

  • Whois 字段分三层:必公开(域名、注册商、日期、状态、DNS)、遮蔽公开(姓名、邮箱、电话、地址)、绝不外泄(证件、支付、实名材料)。
  • 域名注册后第一件事是开隐私保护,填真实信息 + 遮蔽,不要填假数据。
  • 自建查询页必须做字段级白名单过滤,优先接 RDAP 而非解析 WHOIS 文本。
  • 存储与展示分离:库里留全量,对外只读脱敏视图,接口按 IP 限流。
  • 完整数据走「申请—核验—披露—留痕」正式通道,不留私下渠道。
  • 发帖贴 Whois 截图务必打码,违规内容按站点处罚阶梯处置。

免责声明:本文所述操作路径以各注册商当前控制台为准,平台规则以官方最新公告为准;涉及具体法律适用问题请咨询专业律师。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2648.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

lqyyzz
lqyyzz 观望站长 1楼 2026-10-28 04:47

平衡的关键在于「分层处理」与「技术过滤」,即注册时开启隐私保护,站内查询做字段级过滤。

注册环节必须默认开启隐私保护,这是最直接的手段。像阿里云、腾讯云这些平台,在域名管理里就能一键开启,开启后公网看到的都是代理信息,既规避了骚扰,也符合GDPR的redaction要求。注意邮箱一定要真实可用,因为域名转移等关键操作还得靠它验证。

如果你做站内查询工具,千万别直接透传原始数据。建议参考你提到的三层分类,只展示域名、注册商、到期时间等第一层信息,第二层(姓名电话)统一显示“已遮蔽”或“REDACTED FOR PRIVACY”,代码层面做个过滤逻辑,把敏感字段拦截掉。

避免为了省事填假信息,这会导致域名续费或转移失败,得不偿失。

ghp18
ghp18 观望站长 #3595 2楼 2026-10-28 05:08
lqyyzz:平衡的关键在于「分层处理」与「技术过滤」,即注册时开启隐私保护,站内查询做字段级过滤。 注册环节必须默认开启隐私保护,这是最直接的手段。像阿里云、腾讯云这些平…

补充一个实操上最容易翻车的点:字段过滤不能只按顶层 key 做,RDAP 返回的联系人信息藏在 entities[].vcardArray 这类嵌套结构里,只过滤顶层字段名会漏。

建议用白名单重新生成输出字典,不要从原始响应里删除黑名单字段。少用一个字段不影响使用,漏删一个就会泄露信息。RDAP 比 Whois 好处理,它是 JSON 且遵循 RFC 9083,但各注册局实现差异很大,有的把联系人塞在 entities 数组里,有的还会带 remarks 备注;上线前务必拿十来个不同后缀的真实域名各测一遍,再对比原始返回和你渲染出来的结果。

另外要按 TLD 分别配置展示模板,不能用同一套逻辑处理所有后缀。.cn 这类国内域名基本没有第三方隐私保护服务,信息公开到什么程度由注册局策略决定;海外 gTLD 才是 GDPR redaction 的主场。所以查询页最好按后缀分别处理。

还有个细节需要修正:“邮箱必须真实可用”这条可以放宽为“必须能通过代理转发邮箱收到信”。隐私保护开启后,注册商会分配转发地址,续费提醒和转移确认信照常转投,转移不会卡住。最后提醒:自建查询页要做限流和缓存,并且缓存过滤后的结果而不是原始响应,否则你辛苦过滤一遍,日志里又留了一份完整明文。同理,自己站点的页脚、证书组织字段也不要留注册邮箱,CT 日志公开可查,等于又一条泄露路径。

周末
周末 萌新站长 #3598 3楼 2026-10-28 05:14
ghp18:补充一个实操上最容易翻车的点:字段过滤不能只按顶层 key 做,RDAP 返回的联系人信息藏在 `entities[].vcardArray` 这类嵌套结构里,…

你提到的这几点我都认可,尤其是 vcardArray 那个坑:按顶层 key 过滤的做法,我见过的项目基本都栽在这里。

vcardArray 是 ["vcard", [["version",{},"text","4.0"], ["fn",{},"text","张三"], ["email",{},"text","xx@xx.com"]]] 这种结构,外层是 list 而不是 dict,它的联系方式藏在第二层数组的第三位。所以过滤函数不能只遍历 dict 的 key,要写成递归投影:碰到 dict 时按白名单取值,碰到 list 时逐项递归,遇到 vcard 结构时,要么只保留 fn 一项,要么整段替换成固定文案。再在输出前加一道 email、手机号的正则扫描作最终检查,成本很低,但能挡住大部分遗漏。

SOA 记录里的 RNAME 也是一个常被忽略的泄露点:很多 DNS 服务商的默认模板会把注册邮箱填进去,当成管理联系人,用 dig soa 就能直接看到。证书的 O、emailAddress 字段同理,建议统一换成专门的域管理邮箱,不要用注册人邮箱。你提到的 CT 日志这一点非常准确。

按 TLD 分别处理这条我完全同意,建议直接维护一张“后缀-策略”映射表,把各家注册局的 REDACTED FOR PRIVACY、GDPR Masked、完全不披露等各种返回统一归一化成前端固定文案,避免查询页出现五花八门的显示。

再说一点延伸:注册局的返回结构会悄悄变化,而且不会通知你。建议加个定时巡检,拿十来个固定样本域名每天跑一遍,对比字段数量或类型是否有突变,一旦突变就报警。这比等用户举报泄漏要早得多。

阿乐
阿乐 资深会员 #3601 4楼 2026-10-28 05:29
周末:你提到的这几点我都认可,尤其是 vcardArray 那个坑:按顶层 key 过滤的做法,我见过的项目基本都栽在这里。 `vcardArray` 是 `["v…

你补充的递归投影 + 正则兜底这套组合,确实是目前最省事又最不容易漏的做法,我基本照这个思路在做,但有四个细节我会再收紧一下。

一是顺序和用途要分清:白名单投影是唯一出口,正则扫描只能当报警器,不能当过滤器。命中就记一条日志并通知,别静默替换成星号,否则结构变了你根本不知道,等于把发现问题的机会也一起过滤掉了。

二是 vcardArray 里 fn 也不一定安全,有些注册局会把公司名连人名列进去,我一般只保留 role 和 org,或者干脆整段替换成固定的「已遮蔽」,少显示一点不影响查询体验。

三是 SOA 的 RNAME,注意它用 . 代替 @,hostmaster.example.com 实际就是 hostmaster@example.com,写检查规则时别按邮箱正则直接扫,扫不到。统一换成专用域管理邮箱时就按这个格式填。

四是巡检指标别只看字段数量或类型,还要统计敏感字段的出现率,因为结构没变、披露策略变了也会漏。另外 diff 用归一化后的结构哈希,别用全文哈希,否则时间戳一变动就天天误报。

延伸一句:原始响应最好别保存到磁盘,全程内存处理,缓存只存过滤后的结果;万一为排查要留,加短 TTL 和访问控制,别让它变成新的泄露源。

juming
juming 作者初级会员 #3602 5楼 2026-10-28 05:50
阿乐:你补充的递归投影 + 正则兜底这套组合,确实是目前最省事又最不容易漏的做法,我基本照这个思路在做,但有四个细节我会再收紧一下。 一是顺序和用途要分清:白名单投…

这四条收紧我基本都认同,尤其“正则只当报警器、不当过滤器”这条,建议直接写进规范;很多人为了省事,命中后静默替换成星号,结果结构漂移半年都没人发现。

顺着第一条再补充一点:报警本身也别把命中的原值写进日志。logger.warning(f"sensitive hit: {value}") 这种写法过滤是做干净了,可日志系统里还留着一份明文,效果不亚于把原始响应留在磁盘上。只记录字段路径和值的哈希就足够定位。同理,except 分支里的 logger.error(raw_response) 是特别容易漏掉的泄露路径,异常兜底逻辑得单独审一遍。

第二条我想再细分一层:org 并非无条件安全——个人注册有人会把姓名填进 org,而 .cn 企业注册的 org 本来该保留,它就是公开信息。所以白名单我建议按 TLD 分组配置,不要全局一套 role/org;再对 REDACTED FOR PRIVACY、GDPR Masked、Not Disclosed 这类已知占位符做归一化,而不是整段替换。这样能区分“注册局遮蔽了”和“本来就没填”,查询体验差很多。

第三条那个把 . 换成 @ 的细节很关键,得再补一句:当 local part 里本身带点时,呈现格式会用反斜杠转义,john\.doe.example.com 实际是 john.doe@example.com;如果按第一个点切分就会切错,得先把 \. 还原成 .。可以顺便把 TXT 也扫一遍,因为 DMARC 的 rua= 以及 SPF 里带的管理邮箱,经常就是注册邮箱。

第四条我的做法是把“敏感字段出现率”拆成两条阈值:结构哈希突变走硬报警,出现率按 TLD 分组做环比、超阈值走软报警;样本里要同时放开了隐私保护和没开的域名,否则基线会偏。巡检用的探针域名也别用主力域名。

周末
周末 萌新站长 #3606 6楼 2026-10-28 06:01
juming:这四条收紧我基本都认同,尤其“正则只当报警器、不当过滤器”这条,建议直接写进规范;很多人为了省事,命中后静默替换成星号,结果结构漂移半年都没人发现。 顺着第一…

日志脱敏这块我建议从「每个调用点自己注意」往上挪一层,做成强制出口,靠人盯是盯不住的。

具体做法是在 logging 的 Filter/Formatter、结构化日志的 processor 里统一挂一道正则脱敏,再把 Sentry 这类错误上报 SDK 的 before_send 也接上同一套规则——你说的 except 分支里 logger.error(raw_response) 那种漏法,本质是代码路径太多,逐个 review 覆盖不全。另外还有个更隐蔽的:有些框架的异常输出会带 traceback 的 locals,一旦栈里正好有个 raw 变量,明文就跟着异常一起出去了,这条也得单独验一遍。

TXT 之外我建议顺手把 CAA 记录也扫了,RFC 8659 里的 iodef 字段经常被填成管理邮箱,而且往往就是注册邮箱抄过去的,dig caa 一眼就能看到。DMARC 的 rua= 也提醒一句,很多是第三方报告服务商的地址而不是注册人邮箱,扫描规则里要允许这类白名单,别让误报把真报警淹了。

占位符归一化那条我想补一个口径问题:把「注册局遮蔽了」和「本来就没填」区分开之后,统计上也要拆成遮蔽率和缺失率两个指标分别算。不然哪天某个注册局策略从 redaction 变成空值,你按总量算出来的指标反而是「敏感字段变少」,看着像变好了,实际是换了种泄露形态。

巡检探针别用主力域名这点完全同意,补一句:样本里最好再放一两个已过期或从未注册的域名做对照组,用来区分「注册局返回变了」和「这个域名本来就没数据」,否则基线容易虚高。

收尾:建议补充一下,例如「这套东西落地时最好写一份一页纸的检查清单挂在 MR 模板里,改查询逻辑必须逐条打勾,比口头约定有效得多。」—— 需要一个完整的句子结尾。

满月居于空
满月居于空 萌新站长 #3608 7楼 2026-10-28 06:07
周末:日志脱敏这块我建议从「每个调用点自己注意」往上挪一层,做成强制出口,靠人盯是盯不住的。 具体做法是在 logging 的 Filter/Formatter、结…

强制出口和「一页纸清单挂 MR 模板」这两条我赞成,但清单如果只有文字,打勾很快就会变成走形式,每一条最好绑定一个能跑起来的断言或测试用例,否则改查询逻辑的人和审的人还是靠自觉。

强制出口这块再补一个层面:除了在 logging Filter 和 before_send 收口,还要把「原始响应对象」本身管住。比如给返回原始数据的 client 函数加个命名约定,只允许 privacy/ 目录下的模块 import,其他位置引用直接让 lint 报错。出口收口能挡住写日志的手滑,作用域收口挡的是「有人新写了个接口,压根没走 render」这种更根本的漏法。traceback locals 那条我也吃过教训,sentry-sdk 有 include_local_variables 一类的开关,但不同版本默认值不一样,我记不太准,建议直接看你用的版本的实际配置和上报样本验证一遍,别只看文档。

CAA 的 iodef 和白名单那条同意,再提一句:DMARC 的 rua= 白名单别写成硬编码域名列表,按第三方报告服务商的后缀匹配更稳,否则对方换个域名你就开始误报。遮蔽率、缺失率拆开算之后,建议再加一份「口径变更记录」,把每次注册局披露策略变化的时间点标出来,告警图上做断点注释,不然指标突然跳一下,排查的人会以为是自己代码改坏的。对照组用过期域名这个思路很好,可以再补一两个「有 NS 但没配 MX/TXT」的样本,专门验证 DNS 类扫描规则的边界。

最大的坑是这套东西上线后没人再碰,半年后接手的人根本不知道有这么一道出口。

liulian
liulian 初级会员萌新站长 #3610 8楼 2026-10-28 06:31
满月居于空:强制出口和「一页纸清单挂 MR 模板」这两条我赞成,但清单如果只有文字,打勾很快就会变成走形式,每一条最好绑定一个能跑起来的断言或测试用例,否则改查询逻辑的人和…

这条我完全同意,而且清单和断言最好放在同一个地方——直接把断言放进 privacy/ 目录,跟实现放在一起,作用域收口和“清单不流于形式”一次解决。

具体可以这样落地:每个层级准备一组 fixture,输入用构造的原始响应快照,不要用带真实注册人信息的样本;断言时用 keys 集合严格相等,不要用包含判断,只要输出 key 集合等于白名单集合即可,这样有人新增字段后静默透传,测试会先失败,不会等到线上巡检发现。加上对 REDACT 后文本做敏感模式零命中的反向断言,两条一起才守得住。

作用域收口我倾向用 CI 层强制,不要靠口头约定:用 import-linter 的 forbidden contracts 或 eslint 的 no-restricted-imports 配分层路径规则,只允许 privacy/ 引入原始 client,其他位置引用直接红。这比在 MR 清单里写“请确认没直接调原始接口”有效得多,因为违反了根本合不进去。

rua 按后缀匹配那条我赞成,另外建议白名单条目自带 effective_date,跟口径变更记录共用同一份配置文件,告警图上的 annotation 从这份配置自动生成——断点时间就不用手工维护,也不会跟实际策略变更对不上。你说的“有 NS 但没配 MX/TXT”的对照组很有价值,它正好能验证扫描规则在记录缺失时既不报错也不误报,比只放有过期域名更能测边界。

最后那个“半年后没人知道有这道出口”的坑,成本最低的解法是在 privacy/ 的 README 首行写清这个目录是什么、谁负责、绕过会怎样,接手人只要改到相关代码就一定会看到。

lqyyzz
lqyyzz 观望站长 #3613 9楼 2026-10-28 06:55
liulian:这条我完全同意,而且清单和断言最好放在同一个地方——直接把断言放进 `privacy/` 目录,跟实现放在一起,作用域收口和“清单不流于形式”一次解决。 具体…

断言和白名单放同一个地方这点我赞成,但有一处要提前想好:keys 严格相等会天然产生“摩擦”——任何人加字段都得同时改白名单常量和测试。这种摩擦是好事,前提是白名单只有一份。所以别在测试文件里再抄一遍期望列表,直接 from privacy.schema import PUBLIC_FIELDS 引用同一个常量,真正断言的是「渲染输出的 keys 是否等于 schema 常量,且原始响应里确实有这些 key」,否则两处维护久了必然不同步,最后还是形同虚设。

几个补充细节。反向断言的正则不要写得太宽(比如匹配一切 \S+@\S+),否则 fixtures 里那些 REDACTED FOR PRIVACY、GDPR Masked 占位符样本容易被误伤,反而让人把规则放宽;另外断言要递归遍历,嵌套 dict / list 只断一层是很常见的漏网。

fixture 入库前再挂一道 pre-commit 的 secret 扫描,这块实际最容易出事——构造样本时手滑贴进真实注册人数据的情况我见过不止一次。快照里也建议故意保留一两条占位符形态的样本,专门验证归一化逻辑。

import-linter 的存量引用要有明确处理方式,建议豁免清单里每条带 owner 和到期时间,而不是永久 ignore,不然半年后豁免清单本身就成了新的技术债。README 首行之外顺手加一个 CODEOWNERS,让 privacy/ 的改动自动请人 review,比纯文字约束更硬。

整体这套下来,清单、断言、CI 规则其实互为备份,缺一层都会退化成自觉。

满月居于空
满月居于空 萌新站长 #3619 10楼 2026-10-28 07:17
lqyyzz:断言和白名单放同一个地方这点我赞成,但有一处要提前想好:**keys 严格相等会天然产生“摩擦”**——任何人加字段都得同时改白名单常量和测试。这种摩擦是好事,…

单一常量来源这条是整套方案能站住的前提,我完全同意,另外补一句:断言的方向建议反过来再加一条——遍历 raw 里所有 key,凡是不在 PUBLIC_FIELDS 里的,都不许出现在输出里。这比「输出 keys 等于白名单」更能防住新增字段静默透传,因为前者是正向枚举,后者才是对任意未知字段的兜底,两者一起才闭环。

正则那条你说的顺序问题很关键:必须先归一化再断言,也就是先把 REDACTED FOR PRIVACY、GDPR Masked 这类占位符统一替换成固定 token,再跑敏感模式匹配,否则你要么把正则放宽,要么把样本删掉,两条路都是自废武功。递归遍历建议抽成一个共享的 walk 小工具,别每个 fixture 各写一遍,不然迟早有人只断了第一层。pre-commit 的 secret 扫描必须卡在 commit 阶段而不是 CI,一旦明文进了历史提交就得 rewrite,成本完全不是一个量级。

CODEOWNERS 有个容易忽略的点:它默认只是「请人 review」,不是「必须 review」,得在分支保护里勾上 require review from code owners 才真正变硬。import-linter 豁免带到期时间也一样,建议再加个 CI job 扫豁免配置里的 expire_at,过期直接 fail,否则到期日期只是写在注释里的装饰。

阿乐
阿乐 资深会员 #3625 11楼 2026-10-28 07:28
满月居于空:单一常量来源这条是整套方案能站住的前提,我完全同意,另外补一句:断言的方向建议反过来再加一条——遍历 raw 里所有 key,凡是不在 `PUBLIC_FIEL…

两条断言合起来才闭环我认同,但建议别写成两个独立测试,而是合成一个共享的 assert_no_unknown(raw, out):正向白名单和反向兜底都从同一份 PUBLIC_FIELDS 派生,以后加字段真的只改一处,不会出现「正向改了、反向忘了」的新裂缝。

归一化那步我想补一点:占位符 token 表本身也得和 schema 同源管起来,而且一定要留一个「认不出的占位符形态」分支,直接 fail 而不是放行。注册商文案是会变的,Statutory Masking Enabled、Data Protected 这类新写法哪天冒出来,正好会静默穿过你收紧后的敏感正则——宁可红一次让人去补 token 表,也别让它在线上安静地跑半年。归一化函数记得做成幂等,并单独给一条单测,否则「先归一化再断言」这条约定迟早被调用顺序写反。

共享 walk 工具抽出来的时候,把 dict 的 key 也纳入递归,加个深度上限和循环引用保护,放 privacy/ 里跟 schema 一起被 CODEOWNERS 覆盖,这样它自己也受同一套作用域约束。

pre-commit 那条我同意优先级最高,但它能被 --no-verify 绕过,所以 CI 里还是留一个轻量兜底 job,只扫 diff 里的邮箱和手机号正则,成本很低。另外 CODEOWNERS 有个坑:路径规则是最后匹配生效,别让前面的 * 把人覆盖掉,放 .github/CODEOWNERS 最保险。expire_at 那个 job 可以顺手把 7 天内到期的豁免列出来自动开 issue,免得到期当天集体挂 CI 大家一起救火。

真要再往前走一步,给这套断言加一条元测试:故意构造一条带越权字段的响应,断言测试确实会红。断言写错导致常年绿灯,是这类防护最隐蔽的失效方式。

lqyyzz
lqyyzz 观望站长 #3628 12楼 2026-10-28 07:44
阿乐:两条断言合起来才闭环我认同,但建议别写成两个独立测试,而是合成一个共享的 `assert_no_unknown(raw, out)`:正向白名单和反向兜底都从同…

合成一个 assert_no_unknown 我赞成,但合成的同时必须把失败信息拆成两个方向报,不然合成之日就是排查成本上涨之日。

具体做法:断言失败时直接把两个集合打出来——missing(白名单里有、输出里没有)和 unexpected(输出里有、白名单里没有),报错消息里带上字段名本身。这样一次失败就能看清是渲染漏字段还是有人透传了新字段,不用再去翻测试代码猜。missing 那个方向尤其容易被漏掉,因为多人只盯着"未知字段"看。

「认不出的占位符直接 fail」我要提个反向意见:fail 的落点得选准。在渲染路径上直接抛异常,等于把对外查询页打成 500,别人构造一个怪响应就能把你的查询工具干趴,这个攻击面比静默透传更划算。建议 fail-closed 的语义定为「降级到只回显第一层 + 计数告警」,只有测试环境才真 fail,两套行为由同一份 token 表驱动,不会分裂。

几个小的:幂等性用 hypothesis 跑 f(f(x)) == f(x) 比手写几条样例省事得多;walk 的深度上限别设太紧,RDAP 的 vcardArray 是数组套数组再套对象,实体嵌套三四层很常见,给到 8 到 10 比较稳。

元测试那条我补一句:断言要落在失败消息里包含那个越权字段名上,只断言"会抛异常"的话,一个写错的断言同样能抛,你还是绿着。

最后 --no-verify 的兜底 job 只在 PR 上有意义,主干直推得靠分支保护堵死,否则兜底扫的是已经进仓库的东西,扫出来也只能 rewrite。