[anon_board]匿名区 · 树洞热
- 匿名区 · 树洞ID anon_board版本 5.3.0插件制作者 cxk123售价 30 积分4380 行 / 263.3 KBHook / 路由 / 后台页5.3.0:补「运行期结构自愈」—— 覆盖文件升级(把新 plugin.php 拷进站点、不跑 install)时,各入口先按「内存副本 → settings 标记 → 才 DDL」三道门幂等补齐缺表缺列,老库不会再因为 PDOException 被核心整插件停用;结构标记抬到 5(只加代码不抬标记 = 老站永远不会补列)。5.1.0:遮蔽面全面普查后的隐私再收紧 —— 修掉「身份销毁后最后回复列仍指向真人」的泄露(销毁路径自动重算 + 渲染层按「最后回复是否为匿名内容」兜底遮蔽)、新增可选「彻底抹除」(连核心表作者列一起抹平,数据库层面也无法反推)、后台插件列表新增「停用/卸载即曝光」警示、痕迹体检新增两项边界计数;并把 12 个消费方的调用形态逐条核对后写成 README「契约使用须知」。在指定版块开启匿名发帖与匿名回复:同一位用户在同一个主题里始终是同一个匿名号(跨主题互不关联),匿名身份带独立头像与匿名昵称,看不到真实昵称、头像与个人主页;匿名广场把全站匿名主题聚到一处,匿名内容可以互相「同感」而不暴露是谁点的,也能一键举报(举报人不会暴露给作者);版块可设积分门槛、每日条数上限与自曝护栏档位;匿名自证码让作者能在不暴露身份的前提下证明「这是我写的」;留存天数到期或作者自助销毁后,身份映射彻底销毁、连管理团队也无法解匿。收费插件安装说明购买后点击“授权码”获取一次性授权码;在后台 > 插件广场点击在线安装或更新,并填写该授权码。已购买用户更新免费:每次在线安装或更新前,重新点击“授权码”获取新的一次性授权码即可,无需再次购买;也可直接下载新版后用后台“插件上传”更新。尊重并感谢他人劳动,请勿传播代码。app/plugins/anon_board/plugin.php插件预览5 张开发日志最新 10 条
此项目接受所有登录用户协助维护和提交更新。
主楼点赞与点评1123456789投币1个表示赞同2026-09-30
初次上架:匿名区 · 树洞 1.0.0
插件质量报告
插件:
anon_boardHook 功能范围 循环 使用频率 文件读写 修改系统表 读写系统表 读写自己的表 page.footer 全站页面页脚 否 5 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_posts, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks reply.after_render 主题查看页/回帖列表逐条渲染 是 5 - - - - reply.before_render 主题查看页/回帖列表逐条渲染 是 5 - app_replies, app_topics 修正❗️ app_replies, app_topics 修正❗️ 写 plugin_anon_board_posts, plugin_anon_board_aliases, plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks 修正❗️ sidebar.feature_links 首页/侧栏快捷功能 否 5 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_posts, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks topic.after_render 首页/版块列表/主题查看逐条渲染 是 5 - - - - topic.before_render 首页/版块列表/主题查看逐条渲染 是(列表跳过) 5 - app_replies, app_topics app_replies, app_topics 写 plugin_anon_board_posts, plugin_anon_board_aliases, plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks topic.index_data.loaded 首页/版块/用户主题列表查询后 回调内 5 - app_topics, app_replies 修正❗️ app_topics, app_replies 修正❗️ 写 plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_posts, plugin_anon_board_aliases, plugin_anon_board_meta, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks 修正❗️ topic.replies_data.loaded 主题查看页回帖查询后 回调内 4 - app_topics, app_replies 修正❗️ app_topics, app_replies 修正❗️ 写 plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_posts, plugin_anon_board_aliases, plugin_anon_board_likes, plugin_anon_board_meta, plugin_anon_board_audit, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks 修正❗️ user.menu_links 个人卡片/移动端我的菜单(展示位置:个人卡片) 否 4 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_posts, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks notification.after_create 通知及未读数写入后 否 3 - app_notifications app_notifications, app_replies, app_topics 读 plugin_anon_board_posts, plugin_anon_board_aliases reply.after_save 回帖保存后 否 3 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_meta, plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_aliases, plugin_anon_board_audit, plugin_anon_board_posts, plugin_anon_board_fingerprints, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_blocks reply.before_save 回帖发布/编辑表单提交 否 3 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_posts, plugin_anon_board_blocks, plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints topic.after_save 主题保存后 否 3 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_meta, plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_aliases, plugin_anon_board_audit, plugin_anon_board_posts, plugin_anon_board_fingerprints, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_blocks topic.before_save 主题发布/编辑表单提交 否 3 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_posts, plugin_anon_board_blocks, plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints content.before_delete 内容删除操作 否 2 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_likes, plugin_anon_board_reports, plugin_anon_board_fingerprints, plugin_anon_board_audit, plugin_anon_board_posts, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_proofs, plugin_anon_board_blocks reply.form_extra 回帖编辑表单 否 2 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_posts, plugin_anon_board_forums, plugin_anon_board_reports, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks topic.before_delete 主题删除操作 否 2 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_likes, plugin_anon_board_reports, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_posts, plugin_anon_board_aliases, plugin_anon_board_meta, plugin_anon_board_audit, plugin_anon_board_forums, plugin_anon_board_blocks topic.form_extra 主题发布/编辑表单 否 2 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_posts, plugin_anon_board_forums, plugin_anon_board_reports, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks admin.plugin.actions 非系统hook 否 1 - - app_topics, app_replies 读 plugin_anon_board_posts anon_board.mask_rows 非系统hook 回调内 1 - app_topics, app_replies 修正❗️ app_topics, app_replies 修正❗️ 写 plugin_anon_board_posts, plugin_anon_board_aliases, plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks 修正❗️ diy_layout.components 非系统hook 否 1 - - app_topics, app_replies 读 plugin_anon_board_posts post.ops_actions 非系统hook 否 1 - app_topics, app_replies app_topics, app_replies 写 plugin_anon_board_reports, plugin_anon_board_forums, plugin_anon_board_meta, plugin_anon_board_aliases, plugin_anon_board_posts, plugin_anon_board_audit, plugin_anon_board_likes, plugin_anon_board_proofs, plugin_anon_board_fingerprints, plugin_anon_board_blocks 颜色说明:黄色表示有数据读写,红色表示 Hook 在系统循环中执行,浅蓝色表示有文件写入。“列表跳过”表示回调在列表路径会提前返回,“回调内”表示操作位于回调自身或其帮助函数的循环体内。“文件批处理”表示逐个处理用户一次提交的文件,属于必要操作,不标记修正。只有操作实际处于需要优化的循环路径时,才会标记“修正❗️”。
数据字典
plugin_anon_board_forums字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_meta字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_aliases字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_posts字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_audit字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_likes字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_reports字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_proofs字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_fingerprints字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 plugin_anon_board_blocks字段 类型 可空 默认值 约束 - 动态定义 - - 无法静态解析 初次上架:匿名区 · 树洞 1.0.0
- flinthub2026-09-30
你上架哪里了?
- bbs1org2026-09-30
嗯?出现树洞?
初次上架:匿名区 · 树洞 1.0.0
版本 1.0.0 更新:
【1.0.0】首次上架:匿名区 · 树洞 —— 让指定版块支持匿名发帖与匿名回复一、新增
- 版块级匿名开关 + 逐帖匿名
- 后台逐版块开启匿名区;发帖与回帖表单里出现「匿名发布」开关,勾选才匿名。
- 「强制匿名」版块:表单不再给开关,版块内所有发言一律匿名。
- 未开启匿名区的版块勾选匿名会被明确拒绝并给出说明,不会静默建帖。
- 稳定匿名身份(同主题固定、跨主题不关联)
- 同一主题内同一位用户的匿名号始终不变(匿名 #1、匿名 #2…),换一个主题就是另一个号,
无法通过匿名号把同一个人在多个主题里的发言串起来。
- 匿名身份带独立头像;页面上不显示真实昵称、真实头像、个人主页链接与 UID 徽标。
- 楼主在主楼显示「匿名 #N(楼主)」;匿名号由原子自增分配,多人同时发帖也不会重号。
- 治理:解匿核查 + 审计 + 取证导出
- 只有管理团队能解匿,且必须填写理由(≥4 个字)才会显示匿名号与真实用户的对照表;
每次解匿都会留下「谁、什么时候、什么理由」的审计记录。
- 后台「匿名区」页提供全站匿名发言审计清单与四项统计(匿名主题 / 匿名回复 / 已开启版块 / 解匿次数)。
- 需要交给上级或法务时,可导出取证 CSV(含匿名号、真实用户、版块、标题与时间,已做公式注入中和)。
- 门槛与限额(只约束匿名发言)
- 版块可设置积分门槛、每日匿名主题上限、每日匿名回帖上限(按站点时区的当天零点计算)。
- 版块可禁止匿名内容带外链或带图片;实名发帖不受这些限制。
- 关闭总开关或关闭某个版块后,历史匿名内容仍按匿名显示,不会因为关一次开关就全部曝光。
- 隐私自查:我的匿名发言
- 个人菜单与侧栏新增入口,列出自己在匿名区发过的主题与回复(只有本人可见),方便自查。
二、设计说明(为什么可以放心用)
- 数据库里始终保留真实作者:内容照常写进论坛的主题/回帖表,插件只记录「这条内容是匿名的、匿名叫什么」,
因此通知、权限、编辑、统计、搜索索引等原有功能全部照常工作。
- 匿名发言产生的站内通知会把发送者改写成系统并加【匿名】前缀,收件人看不到真实昵称。
- 页面请求不额外增加逐行查询:列表页与详情页都是整页一次批量取回,随后在内存里改写;
站点上还没有匿名内容时,所有渲染钩子直接短路,一条额外查询都不发。
三、安装与升级说明
- 首次启用会创建 5 张插件表与 7 个索引,并给主题表与回帖表各补 1 个插件前缀列(卸载时会一并清理)。
- 未开启匿名区的版块与既有内容完全不受影响:不勾选匿名就是正常发帖,显示与之前一模一样。
- 92026-09-30
最近很高产啊
版本 2.0.0 更新:
【2.0.0】匿名区更好用了:匿名广场 + 匿名「同感」+ 匿名举报,并补掉 5 处真实缺陷(含一处会泄露身份的跨插件漏洞)一、新增
- 匿名广场(新页面「匿名广场」,个人菜单与侧栏都有入口)
- 把全站匿名主题聚合到一个入口:最新 / 热议两种排序、按版块过滤、可翻页。
- 页面上只出现「匿名 #N」与版块名,不会出现任何真实昵称、头像或主页链接。
- 只有你有权限浏览的版块才会出现在广场里。
- 匿名「同感」(匿名内容的点赞)
- 匿名主题与每个匿名楼层都有「同感」按钮,点了即时加一、再点取消,不用刷新页面。
- 不显示是谁点的:计数公开,名单只有管理团队在后台能查;同一位用户对同一条内容只能同感一次。
- 实名内容不参与匿名同感(点了会被拒绝),避免把匿名互动和实名互动混在一起。
- 匿名举报
- 每条匿名内容旁边都有「举报」入口,原因可选(违法违规 / 人身攻击 / 泄露隐私 / 广告灌水 / 其它)并可补充说明。
- 举报人不会暴露给内容作者:举报直接送到管理团队,后台有独立清单,处理完可「标记已处理」,处理动作同样写进审计。
- 后台新增「待处理举报」计数,与匿名主题 / 匿名回复 / 匿名同感 / 已开启版块 / 解匿次数并列。
- 后台匿名活跃趋势
- 新增「近 7 日匿名发言」柱状图(主题 + 回复),一眼看出匿名区是否真的有人用、以及是否被滥用。
- 跨插件匿名联动(给「关注与动态」这类插件用)
- 提供标准的批量遮蔽接口:任何自己渲染主题/回帖行的插件,只要在渲染前把整页行交给它,
匿名内容就会按匿名身份展示(详见插件说明的「跨插件联动」一节)。未安装本插件时行为完全不变。
二、修复(本轮实测复现的真实缺陷,每条都补了回归断言)
- 动态流泄露匿名作者:
关注与动态插件的动态流是自己取数、自己渲染行,页面里仍然显示真实昵称、
真实头像与主页链接 —— 匿名主题在动态流里等于没匿名。修法:新增
anon_board.mask_rows批量遮蔽接口
(整页一次调用,内部按整页IN批查),并在该插件的动态流渲染前调用它。- 异步通知泄露匿名作者:粉丝收到的「发布了新主题」通知是队列 → 计划任务在另一次请求里发出的,
原先的匿名改写只在同一次请求内生效,于是通知里带着真实发送者 —— 收件人一眼就能看到谁匿名发了什么。
匿名主题正文里 @某人产生的提及通知也有同样问题。修法:通知改写补齐「主题维度」判定
(主题是匿名的、发送者就是该主题的匿名楼主 → 一律改写成系统并加【匿名】前缀)。- 后台改配置「点保存没反应」:2.0.0 新增了数值项,旧表单(或第三方脚本)不提交这些字段时,
服务端会把它当成非法值并拒绝整次保存 —— 表现是开关也改不动。修法:字段完全缺席时沿用当前值,
只有提交了非法值才拒绝(与核心的兼容口径一致)。- 匿名头像可能撞脸:同一个主题里两个匿名号有约 1/48 的概率显示同一个头像,读者会把两个身份看成同一个人。
修法:头像改为「风格 × 种子」组合(36 种风格 × 48 个种子),撞脸概率降到约 1/1728。
- 删除主题留下孤儿数据:主题被删除后,匿名登记、匿名身份、同感、举报、审计仍留在库里,
既占空间也会让「我的匿名发言」出现指向不存在主题的条目。修法:删除主题时一并清理,
并新增每日清理任务兜底(直接删库、单条回复删除等场景),后台统计也不再受孤儿影响。三、性能
- 列表页(首页 / 版块 / 搜索 / 个人主页)仍只多 2 条整页查询;匿名内容详情页多 4 条整页查询
(匿名主题标记、匿名身份映射、本页回帖匿名信息、同感聚合各一条),都与行数、楼层数无关
(套件里追加上百行数据后仍不变)。- 站点上还没有匿名内容时,所有渲染钩子直接短路:一条额外查询都不发。
- 匿名广场与后台趋势都是「一条聚合查询 + 内存分桶」,不随数据量放大。
四、升级说明
- 原地升级:首次启用 2.0.0 会自动补两张表与三枚索引(匿名同感、匿名举报),并新增每日清理任务。
- 未开启匿名区的版块、实名内容、既有匿名内容全部不受影响;关闭新增开关(同感 / 广场 / 举报)即可完全停用对应能力。
版本 2.0.0 更新:
【2.0.0】匿名区更好用了:匿名广场 + 匿名「同感」+ 匿名举报,并补掉 5 处真实缺陷(含一处会泄露身份的跨插件漏洞)一、新增
- 匿名广场(新页面「匿名广场」,个人菜单与侧栏都有入口)
- 把全站匿名主题聚合到一个入口:最新 / 热议两种排序、按版块过滤、可翻页。
- 页面上只出现「匿名 #N」与版块名,不会出现任何真实昵称、头像或主页链接。
- 只有你有权限浏览的版块才会出现在广场里。
- 匿名「同感」(匿名内容的点赞)
- 匿名主题与每个匿名楼层都有「同感」按钮,点了即时加一、再点取消,不用刷新页面。
- 不显示是谁点的:计数公开,名单只有管理团队在后台能查;同一位用户对同一条内容只能同感一次。
- 实名内容不参与匿名同感(点了会被拒绝),避免把匿名互动和实名互动混在一起。
- 匿名举报
- 每条匿名内容旁边都有「举报」入口,原因可选(违法违规 / 人身攻击 / 泄露隐私 / 广告灌水 / 其它)并可补充说明。
- 举报人不会暴露给内容作者:举报直接送到管理团队,后台有独立清单,处理完可「标记已处理」,处理动作同样写进审计。
- 后台新增「待处理举报」计数,与匿名主题 / 匿名回复 / 匿名同感 / 已开启版块 / 解匿次数并列。
- 后台匿名活跃趋势
- 新增「近 7 日匿名发言」柱状图(主题 + 回复),一眼看出匿名区是否真的有人用、以及是否被滥用。
- 跨插件匿名联动(给「关注与动态」这类插件用)
- 提供标准的批量遮蔽接口:任何自己渲染主题/回帖行的插件,只要在渲染前把整页行交给它,
匿名内容就会按匿名身份展示(详见插件说明的「跨插件联动」一节)。未安装本插件时行为完全不变。
二、修复(本轮实测复现的真实缺陷,每条都补了回归断言)
- 动态流泄露匿名作者:
关注与动态插件的动态流是自己取数、自己渲染行,页面里仍然显示真实昵称、
真实头像与主页链接 —— 匿名主题在动态流里等于没匿名。修法:新增
anon_board.mask_rows批量遮蔽接口
(整页一次调用,内部按整页IN批查),并在该插件的动态流渲染前调用它。- 异步通知泄露匿名作者:粉丝收到的「发布了新主题」通知是队列 → 计划任务在另一次请求里发出的,
原先的匿名改写只在同一次请求内生效,于是通知里带着真实发送者 —— 收件人一眼就能看到谁匿名发了什么。
匿名主题正文里 @某人产生的提及通知也有同样问题。修法:通知改写补齐「主题维度」判定
(主题是匿名的、发送者就是该主题的匿名楼主 → 一律改写成系统并加【匿名】前缀)。- 后台改配置「点保存没反应」:2.0.0 新增了数值项,旧表单(或第三方脚本)不提交这些字段时,
服务端会把它当成非法值并拒绝整次保存 —— 表现是开关也改不动。修法:字段完全缺席时沿用当前值,
只有提交了非法值才拒绝(与核心的兼容口径一致)。- 匿名头像可能撞脸:同一个主题里两个匿名号有约 1/48 的概率显示同一个头像,读者会把两个身份看成同一个人。
修法:头像改为「风格 × 种子」组合(36 种风格 × 48 个种子),撞脸概率降到约 1/1728。
- 删除主题留下孤儿数据:主题被删除后,匿名登记、匿名身份、同感、举报、审计仍留在库里,
既占空间也会让「我的匿名发言」出现指向不存在主题的条目。修法:删除主题时一并清理,
并新增每日清理任务兜底(直接删库、单条回复删除等场景),后台统计也不再受孤儿影响。三、性能
- 列表页(首页 / 版块 / 搜索 / 个人主页)仍只多 2 条整页查询;匿名内容详情页多 4 条整页查询
(匿名主题标记、匿名身份映射、本页回帖匿名信息、同感聚合各一条),都与行数、楼层数无关
(套件里追加上百行数据后仍不变)。- 站点上还没有匿名内容时,所有渲染钩子直接短路:一条额外查询都不发。
- 匿名广场与后台趋势都是「一条聚合查询 + 内存分桶」,不随数据量放大。
四、升级说明
- 原地升级:首次启用 2.0.0 会自动补两张表与三枚索引(匿名同感、匿名举报),并新增每日清理任务。
- 未开启匿名区的版块、实名内容、既有匿名内容全部不受影响;关闭新增开关(同感 / 广场 / 举报)即可完全停用对应能力。
版本 2.0.1 更新:
【2.0.1】跨插件匿名联动补充:动态流里的「最后回复人」同样按匿名显示一、修复
- 动态流 / 收藏页的「最后回复人」可能露出真名:匿名主题被别人匿名回复后,列表里的「最后回复 X」
仍会显示真实昵称(跨插件遮蔽接口只处理了作者,没有处理最后回复人)。
现在改为与核心列表页一致的保守遮蔽:只要 TA 在这篇主题里以匿名身份出现过,就显示对应的匿名号。二、说明
- 本条只影响「与匿名区插件联动」的那条批量接口(
anon_board.mask_rows)的行为,不改数据结构、不改界面文案。 - 未安装匿名区插件时行为与之前完全一致(Hook 无人注册、原样返回)。
厉害啊,制作了这么多有趣的插件,大工程👍🏻
- 96天前
优秀
版本 3.0.0 更新:
3.0.0 大版本更新:匿名判定 fail-closed(隐私加固)+ 数据自检与一键补齐
一、修掉的缺陷:缺数据就泄露真身(fail-open → fail-closed)
现象(实机复现):一条帖子在核心表上标着「匿名」(
plugin_anon_board_anon=1),但插件的身份行 / 别名行缺失时,前台按实名渲染——他人主页的主题列表里直接出现真实头像、昵称与主页链接。触发场景很现实:导入的匿名帖、被清理过的身份表、早期版本留下的半截数据。根因:
- 匿名判定只认插件自己的表,不看核心表上的匿名标记列;
- 列表上下文里若整页映射缺这一条,逐行钩子直接放行原始行。
两处叠加即是「缺数据 ⇒ 泄露真身」。
修法:
- 判定来源改为并集(核心匿名列 或 插件身份表),仍然是一次 LEFT JOIN 查询,页面查询数与原来完全一致;
- 身份行缺失时用派生匿名号兜底(
crc32(主题 id + 用户 id) % 48,纯函数、同帖同人稳定、跨帖不可关联),照常遮蔽头像、昵称与主页链接; anon_board_has_posts()增加存在性自愈:站点标记被清成 0 时用一条 LIMIT 1 查回真身并补回标记(否则整页预载短路,又会走实名渲染);- 回帖侧(批量与单条两条路径)同口径改写。
二、新增:匿名数据自检 + 一键补齐
- 后台「匿名数据自检」面板:统计「匿名主题 / 匿名回帖缺身份行」的条数。
- 「一键补齐匿名身份」:按派生匿名号把缺失的身份行与别名行补写回插件自己的两张表(一次取回待补清单 → 内存算号 → 两条多行 INSERT,循环体内零查库;一次最多 200 条,可重复点)。
实测:11 条缺失 → 点一次后自检归 0,且补齐用的匿名号与渲染层派生号完全一致(补数据不会让匿名号变化)。
三、验证
- 专项套件扩到 166 项(新增 3.0.0 结构、fail-closed 回归、标记自愈、自检与补齐共 18 项断言)。
- 六道静态门禁全绿;其中循环内查库审计在首版实现里抓到「补齐函数循环体内调用 q()」,已改为「先收集、再一次性写入」。
- 市场登记原地更新(topic 1635),
verify_market.sh输出「市场登记与本地源码完全一致」。
版本 4.0.0 更新:
【4.0.0】先修五处真缺陷(每处都在实验室做了双版本实拍取证),再补四项能力。一、修掉的缺陷(现象 → 根因 → 修法)
- 跨插件遮蔽 fail-open(隐私):后台把「总开关」关掉后,关注动态流 / 悬赏列表 / 收藏页等其它插件自己渲染的页面里,匿名主题立刻显示真实昵称与主页链接(实测出现真名 1 次),而核心渲染管线仍按匿名显示——同一个站点的两套页面自相矛盾。根因:给别的插件用的批量遮蔽契约第一句是「插件未启用就直接返回原行」。修法:与核心侧统一口径,改成「只要站上还有匿名内容就一直遮蔽」,总开关只决定新发言能不能匿名(符合「关闭总开关后历史匿名内容仍按匿名显示」的既有承诺)。实测真名出现 1 → 0 次。
- 后台开关「匿名号在主题内保持稳定」是死的:文案写着「关闭后每次发言都会换号」,实际关掉后同一个人在同一主题里仍然两次拿到同一个号。根因:这个配置键从 1.0.0 起只写不读。修法:关闭稳定时每次发言分配新号(就地更新本人别名行),渲染与批量映射改为按帖子行取号,历史发言的号冻结不变;种子与号同源,不会出现「同号两张脸」;点「同感」不再消耗发言编号。实测两次发言号 1/1 → 2/3。
- 匿名广场「总数」与「列表」不同源:显示「共 2 篇」却只列出 1 条,被隐藏版块(只对管理员组可见)的匿名主题被计入总数、还占掉第一页名额。修法:同一个可见性条件同时加在 COUNT 与列表查询上(纯内存算可见版块,零查库)。实测普通用户总数 2 → 1,管理员仍为 2。
- 删除一条匿名回复会留下 4 张表的孤儿行(匿名登记 / 同感 / 举报 / 审计),清理任务也清不掉:后台统计、我的匿名发言、每日限额都继续算这条已经不存在的内容。根因:核心删除路径只发 content.before_delete,插件只接了 topic.before_delete。修法:接上 content.before_delete,并给清理任务补齐「回复级孤儿」兜底。实测残留 1/1/1/1 → 全 0。
- 举报通知写死发给 uid 1:站点上有第二位管理团队成员时他一条都收不到(实测 0 条),而 uid 1 未必是管理员(换过站长的站点会把举报内容发给普通用户)。修法:按用户组权限取全部管理团队成员,一条多行 INSERT 批量写入(不在页面路径上循环建通知)。实测另一位管理者 0 → 1 条。
二、四项新能力
· 匿名自证码:匿名区把「谁说的」抹掉,代价是作者自己也没法证明那是自己写的。自证码补上这一环——码由站点私盐对(主题×内容×本人账号×匿名号)做 HMAC 得到,只有本人能签发、任何人可校验,校验页只显示内容类型 / 匿名号 / 主题 / 时间 / 身份状态,不查用户表、不输出任何身份信息;支持 ?code= 分享链接,重复签发得到同一个码。
· 举报处置工作流与处置留档:待处理 → 处理中(认领,CAS 防并发重复认领)→ 已处置(结论 + 说明都必填);每次认领与处置都写审计留档(含操作人、结论、说明原文),并给举报人发一条系统通知(作者看不到)。后台新增处置留档面板与结论分布;旧版「一键标记已处理」保留为快捷处置入口;状态语义升级带一次性迁移,历史「已处理」不会被读成「有人正在处理」。
· 到期归档与身份销毁(新计划任务 retire):明确不做「到期自动揭示」(那等于制造「等得够久就能看到真名」的定时炸弹)。做的是相反方向:匿名内容超过留存期后销毁身份链路——匿名号冻结不变、页面渲染完全一样,但真实用户列抹除、别名行与自证码删除、审计里的作者与举报人抹掉,此后连管理团队也无法再解匿(解匿页标注已销毁,取证 CSV 多一列「身份状态」)。留存天数全局可配、可逐版块覆盖,0 = 永久保留。
· 逐版块匿名配额 + 风控面板:顺带修正一处口径缺陷——过去的「本版每日上限」实际按全站统计,在 A 版用掉的额度会吃掉 B 版的;现在按版块统计,并新增跨版块全站闸门。风控面板给出近 24h 逐版块匿名量、今日被挡次数(只在写入路径落库,打开表单不留记录)、逐版块配额与留存天数、疑似重复内容组数(只存正文哈希、绝不存正文)、举报量 / 处置量与平均处置时长,全部由 5 条聚合查询算出。三、兼容与升级
结构标记 2 → 3(幂等补 3 张表、6 个索引、6 列,并迁移举报状态);原有 5 张表、匿名号分配、fail-closed 判定与 14 个页面钩子全部不变;新增钩子 1 个、路由 1 个、计划任务 1 个。后台配置写入器改为「以当前配置为基线」逐键覆盖,避免整体覆盖把新键抹掉。页面路径仍是零循环内查库,详情页 / 列表页恒定查询数不变。验收:tools/lab_verify_anon.sh 254 项断言 / 0 失败;六道静态门禁全绿;缺陷探针在 3.0.0 与 4.0.0 上各实拍一遍。
版本 4.0.1 更新:
4.0.1 修复:后台与解匿/自证页面多个按钮在真实浏览器里点不动- 根因:核心
assets/index.js对所有method="post"表单统一preventDefault改走fetch+JSON.parse,唯一豁免是data-no-ajax="1";而本插件这几处响应是整页 HTML 或 CSV 附件。 - 命中并修复 7 处:解匿「解匿并记录审计」·「一键补齐匿名身份」·「保存设置」·「保存版块策略」·「导出全部匿名取证 CSV」· 匿名自证「生成自证码」·「校验」。其中 4 处此前写的是
data-anon-board-no-ajax="1"—— 核心不认这个属性名,等于没写。 - 修法:一律改成核心认的
data-no-ajax="1"(原生提交)。写操作本身一直是对的,但用户过去看到的是「操作失败」。
- 根因:核心
版本 4.0.2 更新:
补丁版本:给「页面组件 DIY」加一个原生组件(未装 layout_diy 时行为逐字节不变)
- 新增只读组件注册(私有 Hook
diy_layout.components):具名渲染回调 + 声明查询成本 + 无数据时零查询降级(靠站点标记短路)+ 拿不到数据返回空串由宿主降级成占位(不抛异常,单块坏天气不会带走整页)。 - 查询形状与行数无关:列表/映射/计数各一条批查,条数参数只改
LIMIT的绑定值。 - 组件自带参数白名单(条数 / 显示哪些字段),后台可视化编辑、越界值拒绝保存。
- 未装「页面组件 DIY」时这个 Hook 无人调用:本插件的行为、查询数、页面字节与上一版逐字节一致(用「旧版 vs 新版、两边都停用 layout_diy」的 A/B 比对守着,含查询数)。
- 新增只读组件注册(私有 Hook
版本 4.0.3 更新:
4.0.3:给「页面组件 DIY」的原生组件补上站点标记自愈路径
修的是一处真缺陷(本仓库「七类缺陷模式」里的「缓存当当前状态」):
「匿名广场」组件(
anon_board:plaza)此前直接读站点标记plugin_anon_board_has_posts,
而同名的侧栏卡片与列表钩子走的是带自愈的anon_board_has_posts()。于是标记被清成 0、
或匿名内容是从别处导入的而标记还是 0 时,库里明明有数据、这一块却永远显示空 ——
而且没有任何提示,管理员只能靠自己想到「去把标记改回来」。- 组件改用
anon_board_has_posts():标记为'1'时零查询;否则做一次存在性自愈,
确有数据就把标记补回
'1'(下一次请求起恢复零查询路径)。- 存在性探针从「
||串起来的三条语句」合并成一条语句:原先站点上一片匿名内容都没有时要发 3 条,
现在任何情况下都只花 1 条。宿主(layout_diy)按「组件实测查询条数」判超预算,
一次自愈探针的成本必须是可预期的一个数。- 声明成本 3 → 4(= 1 条自愈 + 3 条渲染的最大值)。声明成本是契约上限,
报小了会让一次自愈就把块顶成「超预算降级」。
其余行为不变:只读、不写对方的表、拿不到数据返回空串由宿主降级成占位。
- 组件改用
版本 5.0.0 更新:
5.0.0:匿名自曝护栏 · 作者自助销毁(被遗忘权)· 匿名痕迹体检 —— 外加八处真缺陷修复【三项新能力】
- 匿名自曝护栏(三档、逐版块可调)
匿名发帖/回帖时扫描正文:手机号、邮箱、身份证号、银行卡号、QQ/微信号命中即当场挡下,
并告诉作者命中了哪一类;表单页还有实时提示(输入时标红,服务端仍是最终判定)。
实测:含「13800138000 + 微信」的匿名帖被拒且不落库,拒绝文案点名「手机号、QQ / 微信号」;
同样正文实名发帖完全不受影响(对照断言)。档位 0 关闭 / 1 标准 / 2 严格(再加「正文出现你自己的昵称」),
后台可逐版块设置;拦截计入风控面板新增的「自曝护栏挡下 N 次」。- 作者自助销毁匿名身份(被遗忘权)
「我的匿名发言」里每条都有「销毁身份」,也有「销毁我的全部匿名身份」。销毁后内容与匿名号完全不变
(页面照常匿名显示),但真实用户列被抹除、别名与自证码删除、同感与举报人抹 0、审计里的作者抹 0 ——
此后连管理团队也无法再把内容与你关联,且不再出现在你的自查列表里。不可逆,前端二次确认。
只作用于「本人 × 该主题」,同主题里别人的匿名身份一行都不动。- 匿名痕迹体检 + 一键中和
后台新面板把「渲染之外的关联痕迹」体检成四个数字(通知里的真实发送者 / 正文指纹 / 已签自证码 /
已销毁身份的内容),并给两个幂等按钮:中和残留通知(发送者改系统 + 【匿名】前缀,与 2.0.0 起的新通知
口径一致)、清空正文指纹。判定与运行时同源,实名通知不会被误伤(有假阳性对照断言);
另有一条零查询的跨插件提示:站点装了记录发帖设备信息的插件时,提醒设备指纹的关联风险(不写别人的表)。【跨线缺陷修复(影响全站前端)】
未加引号的 heredoc(return <<<JS)等同双引号串:JS 源码里的 '\n' 被 PHP 解释成真换行,
拼进全站 plugins.js 后是语法错 —— 会让该页面上所有插件的前端脚本都不执行
(真机症状:按钮点了没反应、控制台 SyntaxError,而服务端一切正常)。
本轮把 JS/CSS 两段前端资产改成 nowdoc(<<<'JS' / <<<'CSS'),并加了三条回归断言
(nowdoc 静态守卫 + 插件 JS 片段 node --check + 站点构建产物 plugins.js node --check)。
说明:4.0.3 也是未加引号的 heredoc,但那版 JS 里没有转义序列,所以上架版本没有真的打坏;
触发是本轮新加的护栏 JS 引入的,隐患从 1.0.0 就在。【修掉的八处真缺陷(前四处是隐私)】
P1 匿名广场 ?forum=<隐藏版块> 越权读取:任何访客都能用 URL 枚举「只对管理团队可见」版块的匿名主题标题。
现在不可见/不存在的版块一律 404(不给空态,避免泄露「该版块存在」)。
P2 AJAX 发表匿名回复后,引用按钮带 data-username="<真名>":点「回复」会把真名插进正文并公开
(整页渲染没有这个问题 —— 两套口径)。现在在 after_render 上把已遮蔽行的 data-username 收口成匿名名。
P3 匿名行仍渲染作者的用户组徽标(管理团队 / VIP)与禁言、禁访标记:小人群徽标把匿名身份缩到几个人。
现在遮蔽时把 group_id / is_banned / is_muted 一并归零。
P4 逐行渲染的「点查预算用尽」被当成「不是匿名」→ 行原样放行(真名上屏)。现在行上标着匿名就一律
用派生匿名号兜底遮蔽(缺数据、超预算都不再泄露)。
P5 渲染数组里带一份从 1.0.0 起只写不读的真实昵称 plugin_anon_board_real_name —— 已删除(最小数据)。
F1 「实名主题 + 匿名回复」时同感按钮渲染出来了、点击必然报错(只判主题是否匿名)—— 现在主题或该回复
任一匿名都允许,实名内容仍拒绝。
F2 跨插件遮蔽契约对「消费方行里缺 user_id」静默放行(hot_rank/rate_pick 各自踩过同款)—— 缺字段一律
按匿名处理;契约的入参/出参/字段名一律未变,对现有 6 个消费者是更安全的方向。
F3 每日清理任务漏掉「版块已删除」的策略孤儿行 —— 已补(幂等)。
另修一处自家新特性的交互:作者自助销毁楼主身份后,同一主题里其他参与者的身份映射仍会被留存策略销毁
(候选条件不再只看楼主那一行的 retired_at)。【升级与兼容】
结构标记 3 → 4:版块策略表补 privacy_guard 列(升级期以 DEFAULT 1 补齐 → 老站点自动获得标准档)。
新增 Hook 2 个(topic.after_render / reply.after_render)、审计动作 1 个(self_retire)、配置 2 项。
核心表不新增列、插件不新增表;页面路径查询预算不变(列表 +2 / 详情 ≤ +4 / 无匿名内容 0)。版本 5.1.0 更新:
5.1.0:遮蔽面 21 条出口全面普查 + L1 隐私修复 + 可选「彻底抹除」+ 停用/卸载警示 + 契约使用须知【本轮修掉的隐私缺陷:L1「身份销毁后『最后回复人』仍会露真名」】
到期销毁或作者自助销毁会删除匿名别名行,而核心冗余列 app_topics.last_reply_user_id 仍指向真人 ——
列表页那一行的「最后回复 X」于是重新露出真名(首页 / 版块 / 搜索 / 个人主页 / 所有消费榜单的页面)。
修法两层:
① 写路径:销毁时顺带重算受影响主题的「最后回复」列(一条带关联子查询的 UPDATE,三驱动可移植);
② 读路径(fail-closed):主题旗标里多取一个同语句内的标量子查询「最后一条回复本身是否匿名内容」,
别名查不到时兜底显示不带序号的「匿名」——存量数据(5.0.0 及更早留下的)也自动遮蔽。
套件实测:到期销毁 / 自助销毁后列表页整行不出现真名(对照:实名主题照常显示真名);
并额外断言「即使把核心列人为改回真人,渲染层仍不显示真名」。【新能力:彻底抹除(连核心表作者列一起抹平,默认关闭)】
本插件的设计红线是「只在渲染层改写身份、数据库里始终是真人」,于是「销毁后连管理团队也解不出」
原先只对插件自己的表成立。5.1.0 把最后一层做成可选能力:
· 后台开关「自助销毁时连核心作者列一起抹平」(self_erase_core_author,默认关);
· 「痕迹体检」面板新增两个边界计数(已销毁但核心表仍带作者列 / 销毁后最后回复列仍指向真人的存量),
并给一键动作「抹平已销毁内容的作者列」——只处理身份已被销毁的行,实名内容一行都不碰。
代价已写在开关文案与文档里:内容会失去归属(作者不再出现在「我的主题/我的回帖」,也不能再自行编辑)。【如实化的架构边界:停用 / 卸载即曝光】
遮蔽发生在渲染层:一旦停用(钩子不再注册)或卸载(插件表与核心匿名标记列一并删除),
站上匿名内容会立刻按实名渲染。插件内部无法兜住(被停用的代码不会运行)——
因此后台「插件」列表里本插件那一行新增醒目警示(data-anon-board-disable-warn),
并在 README 第十四节把这条边界与「核心表作者列」的边界一并写清楚。【契约使用须知(anon_board.mask_rows,消费方必读)】
把 12 个消费方(user_follow / hot_rank / rate_pick / layout_diy / qa_bounty / report_center /
achievements / daily_quest / edit_history / draft_box / insight_board / webhook_hub)的调用点逐条实读后,
在 README 里写下 M1–M8 八条硬要求与边界:必须带内容 id(否则无从查起)、不要把别的 id 当内容 id、
缺 user_id 一律按匿名处理、kind 必须传对、出参同序同长、一次传整页(禁止逐行调用)、
未安装时原样返回、契约不做可见性判定也不写库。每条都配了套件断言(含调用方形态模拟与边界反例)。【门禁】
六道门禁全绿,硬规则门禁首次达到 0 条待人工确认:把散落的 $_POST 读取收敛成
① 一个同时做 CSRF 复核的取值 helper;② 回帖表单的版块回退改用核心 id();③ 配置解析改成接收入参的纯函数。
入口回调整页响应从 3 项降到 2 项(仅剩既有的后台页设计)。本轮无结构变更(结构标记仍 4)。版本 5.2.0 更新:
5.2.0 · 隐私遮蔽两处根因- 修复一(匿名者被实名):通知路径此前的「这条回帖是否匿名」判定受请求级点查预算(2 次/请求)约束,预算耗尽即返回空、被判「非匿名」——批量扇出(订阅动态的队列任务一轮 20 条)时第 3 条起匿名回帖的通知会带真实发送者(真名 + 头像 + 主页链接)。现在通知路径改走批量谓词(与列表/详情同一条 SQL、同一种判定),不再受预算约束。
- 修复二(一次异常会让整站匿名内容实名化):核心在钩子抛异常时会自动停用插件,停用后所有遮蔽钩子立刻不再注册。而策略表的逐版块列(留存天数 / 自曝护栏档位)在老库上可能不存在,原写法会把整张策略表退化成空。现在按「实际存在的列」取数,缺列不再抛错,且缺失项一律回落到全局默认(修正了原先落到 0 = 「本版块显式关闭」的静默失效)。
- 结构戳保持 4:无数据结构变更。
版本 5.3.0 更新:
5.3.0 · 覆盖文件升级自愈- 修复(升级路径):此前建表与补列只发生在
install里。而核心的在线更新/手工覆盖路径不会重跑 install,CREATE TABLE IF NOT EXISTS对已存在的表又是空操作 ⇒ 老站覆盖文件后缺列,一读就抛异常,核心随即把整个插件自动停用(前台表现为匿名区整体消失)。现在各写入口先过三道门(内存副本 → 设置标记 → 才真正 DDL)幂等补齐表 / 列 / 索引,并在补完后再落标记;两条一次性数据迁移各自带结构标记守卫,只在标记落后时跑一次。 - 口径:稳定状态下为纯内存比较、零查询;不挂任何读钩子,页面查询预算 A/B 与停用态对比不变;本版不改任何前台文案与版式。
- 修复(升级路径):此前建表与补列只发生在