[审计]FlintHub 插件在线审计工具 —— 规则集验收报告(一期 12 + 二期 8 + 方案 §8 补齐 4 = 24 条)

👑Lv.11 元老 🌞 骨干会员
2026-10-26 10:59:05

80. [INFO] FH-DB-001 — plugins/props/AdminController.php:65

  • 症状:直接查询了核心表:user_groups。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 65 行的 SQL 字面量

81. [INFO] FH-DB-001 — plugins/props/Plugin.php:1507

  • 症状:直接查询了核心表:topic_index。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 1507 行的 SQL 字面量

82. [INFO] FH-DB-001 — plugins/seo/FrontController.php:44

  • 症状:直接查询了核心表:categories。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 44 行的 SQL 字面量

83. [INFO] FH-DB-001 — plugins/seo/Plugin.php:103

  • 症状:直接查询了核心表:settings、categories。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 103 行的 SQL 字面量

84. [INFO] FH-DB-001 — plugins/user_level/Plugin.php:278

  • 症状:直接查询了核心表:settings、topic_index、reply_index、thread_votes、post_votes。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 278 行的 SQL 字面量

85. [INFO] FH-DB-001 — plugins/user_profile/Plugin.php:71

  • 症状:直接查询了核心表:topic_index、blogs、blog_categories、reply_index。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 71 行的 SQL 字面量

86. [INFO] FH-DB-001 — plugins/username_change/Plugin.php:202

  • 症状:直接查询了核心表:settings、users。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 202 行的 SQL 字面量

87. [INFO] FH-DB-001 — plugins/vn_postname/AdminController.php:62

  • 症状:直接查询了核心表:user_groups。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 62 行的 SQL 字面量

88. [INFO] FH-DB-001 — plugins/vn_postname/Plugin.php:822

  • 症状:直接查询了核心表:topic_index、topic、reply_index、reply。插件与核心表结构强耦合:核心加字段/改名时插件会静默出错,而且会绕过核心的业务层过滤(软删除、权限、分片),典型症状是「读到已删除/越权的数据」
  • 修法:确认这处读取是只读且字段稳定的(如作者名、分类名)。若需要写入或依赖易变字段,优先调用核心提供的公共方法;无论哪种,都要在插件 说明.md 里注明「依赖核心表 X 的 Y 字段」,核心改表时才知道要同步检查这个插件
  • 原文:第 822 行的 SQL 字面量

三、逐条夹具明细

FH-PKG-001 [CRITICAL] 包结构必须 plugins/<name>/ 单根(含 Plugin.php + plugin.json)

规范 §11.35 ① 夹具 12 例

  • OK 正例①:标准单根包(plugins/<name>/ 下带 Plugin.php + plugin.json)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:子目录、assets、lang 全都在 plugins/<name>/ 之下(真实包形态)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:多个插件目录同时存在(CLI 语料回归把整个 plugins/ 当一个包喂进来)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:后台发布包(根级 改动说明.md 首行是 release_build 模板)—— 只发 INFO,不算违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:同名文件带 BOM + CRLF,首行仍是模板 —— 认得出来(真实包可能是 Windows 上生成的)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:cd plugins/xxx 再 zip -r —— 条目少了 plugins/xxx/ 前缀(§11.35 ① 反例表第 1 行)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:右键「压缩插件文件夹」—— 条目是 xxx/Plugin.php,少了 plugins/ 那一层(反例表第 2 行)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:包根混入手写的 改动说明.md(首行不是 release_build 模板 → 不降级,仍 CRITICAL)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:缺 plugin.json(err=missing_file,且后台插件列表里不出现)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:缺 Plugin.php(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑥:必备文件只是大小写写错(Windows 上好好的、Linux 上读不到)—— 必须报,且修法要点名真实文件名(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑦:模板 changelog(降级为 INFO)+ 根级 README.md —— 只静音那一个,README 仍报 1 条(期望 HIT,实际 HIT / 违规 1 条)

FH-PKG-002 [CRITICAL] 插件目录名必须匹配 ^[a-z0-9_]{2,40}$

规范 §11.35 ③ 夹具 10 例

  • OK 正例①:标准下划线目录名(本仓主流写法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:纯小写单词 + 数字(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:恰好 2 个字符(正则下界 {2,40})(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:40 个字符(正则上界,不能再多一个)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:CamelCase 目录名(Windows 上没事,Linux 上自动加载器跟着崩)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:连字符目录名(my-plugin 不是合法标识符片段)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:只有 1 个字符(预检 bad_name,最容易被当成「名字能有什么错」)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:中文目录名(还额外违反独立库路径约束)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:41 个字符(刚过 40 的上界)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑥:同包好坏混装 —— 只报坏的那个目录,好的不能连坐(期望 HIT,实际 HIT / 违规 1 条)

FH-PKG-003 [CRITICAL] 包内路径安全(无 .. / 反斜杠 / 盘符 / 空字节 / 全角斜杠 / bidi 控制符)

规范 §11.35 ① / Updater::normalizePath() 夹具 11 例

  • OK 正例①:干净的相对路径(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:中文文件名(规范 §11.35 允许,且升级器白名单已纳入 docs/ 的中文名)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:目录条目(zip 里正常的 plugins/x/views/ 尾斜杠条目)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:重复斜杠 / 前导 ./ —— 归一化后无害,刻意不报(否则刷屏)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:路径穿越 .. —— 前缀 plugins/adslot/ 完全对得上,能跳出目录覆盖站点根 config.php(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:反斜杠分隔符(Windows 右键压缩的产物,Linux 上文件根本不存在)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:前导斜杠(绝对路径)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:Windows 盘符(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:空字节截断扩展名(a.php\0.png 可绕过「禁止 .php」判断)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑥:全角斜杠 /(U+FF0F,Unicode 归一化后可能被当分隔符)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑦:bidi 控制符 U+202E(显示名被反转,伪装扩展名)(期望 HIT,实际 HIT / 违规 1 条)

FH-SYN-001 [CRITICAL] 包内 PHP 文件必须语法正确(子进程 token_get_all TOKEN_PARSE,带超时)

规范 §11.34 / 发布端门禁 #2 夹具 7 例

  • OK 正例①:正常 PHP 文件(含命名空间、类、方法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:模板文件(PHP 与 HTML 交替,?> 后接裸 HTML 是合法的)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:只有 HTML、没有任何 <?php 的文件(不该被误判成语法错)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:PHP 8.0 语法(命名参数 / 空安全运算符 / match)—— 生产底线是 8.0,不能误报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:缺分号(最常见的语法错)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:括号不闭合(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:同包好坏混装 —— 只报坏的那个,好的不能连坐(期望 HIT,实际 HIT / 违规 1 条)

FH-NS-001 [CRITICAL] 命名空间必须能被插件自动加载器解析(复刻 Autoloader 三条候选路径)

规范 §11.34 夹具 15 例

  • OK 正例①:目录名与命名空间首段逐字节一致(命中候选①)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:CamelCase 首段靠 camelToSnake 命中候选②(本仓 55 个插件的主流写法,最容易被误报的一类)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:首字母大写但无下划线,camelToSnake 后仍等于目录名(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:下划线目录名沿用既有先例 Plugin\user_level(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:子目录里的类(rest 参与候选路径,命中候选②)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:一个文件声明多个类,只检查与文件名同名的那个(真实案例 ai_assistant/SensitiveWordFilter.php)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑦:hook 文件不声明命名空间(本仓 673 个文件都是这样),必须不报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑧:app\ 命名空间不归本规则管(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑨:包结构不是 plugins/<name>/ 单根时整体跳过(交给 FH-PKG-001 报,避免重复刷屏)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑩:一个文件两个命名空间块,只检查类真正所属的那个(否则会把 Plugin\bar\A 这种不存在的类报出来)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:Plugin\AdSlot + 目录 adslot —— 2026-09-30 真实事故,三条候选全落空(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:Plugin\ADSlot —— camelToSnake 得到 a_d_slot,仍然落空(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:命名空间与目录大小写都对、但子目录大小写不一致(候选③会把整条路径小写)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:裸 namespace Plugin;(缺插件目录段,自动加载器永远拼出 /xxx/.php)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:同包好坏混装 —— 只报坏的那个,好的那个不能连坐(期望 HIT,实际 HIT / 违规 1 条)

FH-INL-001 [MAJOR] 无内联 <script> / <style> 块(src 引用与 JSON-LD 数据岛豁免)

规范 §7.3-1 / §7.3-6 / §12.2-4 夹具 14 例

  • OK 正例①:外链 <script src>(规范推荐写法;本仓 layout_head_end.php 的主流形态)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:<script defer src=…> / <script type="module" src=…>(带 src 即豁免)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:JSON-LD 结构化数据(seo 插件真实形态;搜索引擎要求必须在 script 标签里,无替代方案)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:注释里提到 <script> / <style> 不算违规(token 层天然排除)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:<script-loader> 这类自定义元素不是 script 标签(后接 - 必须排除)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:</script> 闭合标签与 <link rel=stylesheet> 都不算内联块(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑦:assets/lib/ 下第三方库整体豁免(口径对齐发布端门禁 #4)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑧:text/x-template 模板岛(是数据不是脚本,无替代方案)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:裸 <script> 块(reply_reward/views/admin/index.php:243 真实形态)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:type="application/json" 数据岛(§7.3-6 要求改成 data 属性,不是豁免项)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:控制器用 PHP 字符串拼出 <script>(§7.3-1 后半句明令禁止)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:内联 <style> 块(主题覆盖层改不动它)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:<script type="text/javascript">(有 type 但仍是可执行内联脚本)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑥:同文件里一好一坏 —— 只报坏的那个(期望 HIT,实际 HIT / 违规 1 条)

FH-INL-002 [MAJOR] 无内联 on 事件属性(addEventListener + data- 传参)

规范 §7.3-2 / §12.2-4 夹具 12 例

  • OK 正例①:data-* 承载参数、JS 里 addEventListener(规范推荐的整改后写法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:x-on:click(Alpine,本项目允许的写法)—— 前置 - 必须排除(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:data-onclick= / data-onchange= 是自定义属性,不是事件处理器(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:内联 JS 里的 el.onclick = "…"(在 <script> 块内容区,不是 HTML 属性)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:注释里提到 onclick(token 层天然排除)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:PHP 字符串里的 SQL / 纯文本含 on…=,前面没有 <(不是 HTML)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑦:assets/lib/ 下第三方库整体豁免(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:onclick(§7.3-2 点名的第一个)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:onsubmit / onchange 两个属性 = 2 条(期望 HIT,实际 HIT / 违规 2 条)
  • OK 反例③:onload 写在 PHP 字符串拼出来的 HTML 里(控制器路径也要抓)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:单引号属性写法同样要抓(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:值里含 PHP 块(onclick="<?php echo … ?>")—— 被 \x02 占位后仍要抓(期望 HIT,实际 HIT / 违规 1 条)

FH-I18N-001 [MAJOR] 三语字典键集合零差异 + 键序一致(token 硬解,不 include)

规范 §八 夹具 10 例

  • OK 正例①:三语键集合一致、键序一致(本仓 59 个插件的标准形态)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:没有 lang/ 目录 —— 不提供语言包的插件是合法的(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:只有一种语言 —— 无从比对,刻意不报(单语插件是合法形态)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:值里含嵌套数组与逗号(只有顶层键参与比对,嵌套键不算)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:array() 老写法 + 注释 + 尾随逗号,键集合一致(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:字典是非字面量(解析不了)—— 必须发 INFO 跳过,不能当成违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:en.php 缺一个键(切英文露出裸键名,不报错)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:en.php 多一个孤儿键(永远不会被读到,且让三语 diff 错位)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:键集合完全相同但键序不同(按行对齐做 diff 会从此处起全线错位)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:两种语言同时缺键 —— 缺 1 个 + 缺 1 个 = 2 条,各报各的(期望 HIT,实际 HIT / 违规 2 条)

FH-USE-001 [MAJOR] 同命名空间下调用未 import 的 app 类(跑到那行才 Class not found)

规范 §12.1-6 夹具 11 例

  • OK 正例①:正确 use 了 app 类(标准写法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:同类名在包内别的文件里声明过(Plugin::db() 属同命名空间,不是漏 use)—— 这是最容易误报的一例(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:显式限定 \app\Helpers\Csrf::(带前导反斜杠,不受 use 影响)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:$obj->Csrf() 是方法名、注释里的 Csrf:: 也不算(token 层天然排除)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:全局命名空间文件(没有 namespace)—— 短名本来就解析到全局,不受影响(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:短名不是核心类(MyThing::)—— 不归本规则管(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑦:没有 core_classes(未导入快照)→ 发 INFO 跳过,不能当成违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:漏 use —— Csrf::verify() 会去找 Plugin\demo\Csrf(不存在),跑到那行才 500(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:new Csrf() 漏 use(构造调用同样解析到当前命名空间)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:同一短名在文件里用了 3 次 —— 只报 1 条(不刷屏)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:同包好坏混装 —— 只报漏 use 的那个文件(期望 HIT,实际 HIT / 违规 1 条)

FH-SEC-001 [MAJOR] 红线函数(eval / unserialize / extract / assert / create_function / preg_replace+e)

规范 §一五 夹具 10 例

  • OK 正例①:普通函数调用(strlen / json_decode)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:注释与字符串里提到这些词(token 层天然排除)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:方法名 / 静态方法名($o->unserialize()、Foo::extract())不是红线函数(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:preg_replace 但没有 /e(正常写法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:assets/lib/ 下第三方库整体豁免(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:unserialize($s)(对象注入风险)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:eval() 是语言构造(T_EVAL),必须也能抓到(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:extract($arr)(可能覆盖关键变量)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:一行里两个红线函数 = 2 条(期望 HIT,实际 HIT / 违规 2 条)
  • OK 反例⑤:preg_replace + /e(PHP 7 起必崩)(期望 HIT,实际 HIT / 违规 1 条)

FH-SEC-002 [MAJOR] 写操作必须「先登录后 CSRF」(且 CSRF 不能晚于读参数)

规范 §11.28 夹具 10 例

  • OK 正例①:标准的「先登录 → CSRF → 读参数」(post_poll 的真实形态)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:后台控制器继承 BaseController(门禁在父类构造函数里),文件里只有 CSRF —— 不能因为「没有 Auth::」就报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:只读 GET 的控制器(不碰 $_POST)—— 不是写操作入口,不归本规则管(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:视图文件(不是 *Controller.php)里的 $_POST —— 不归本规则管(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:assets/lib/ 下第三方库豁免(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:API 控制器(token/签名鉴权,CSRF 不适用)→ 跳过(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑦:多方法文件(CSRF 在方法 A、登录判断在方法 B)—— 无法定位到方法,不判顺序(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:有写操作、完全没有 CSRF(写操作裸奔)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:CSRF 校验晚于读业务参数(校验在、但走不到)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:登录判断写在 CSRF 之后(规范要求先登录后 CSRF)(期望 HIT,实际 HIT / 违规 1 条)

FH-I18N-002 [MAJOR] 代码里字面量引用的语言键必须在字典里存在(缺键会显示裸键名)

规范 §八 夹具 10 例

  • OK 正例①:引用的键都在字典里(标准写法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:双引号插值的动态键("plugin.demo.{$x}")—— 词法层拿不到完整串,不该报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:引用核心键或别的插件的键 —— 不归本条管(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:动态键的前缀片段(plugin.demo.color. . $x)—— 以点/下划线结尾,不是完整键(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑦:不带分隔符的动态前缀(plugin.demo.lv . $n)—— 后面紧跟 .,靠源码位置识别(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:没有 lang/ 目录(不提供语言包)→ 跳过(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:字典是函数调用(解析不了)→ 发 INFO 跳过,不当违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:键名打错一个字母 → 页面显示裸键名(不报错)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:多个缺失键 —— 1 条(列前 5 个,不逐键刷屏)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:控制器里拼接用的键字面量同样要查(不只视图)(期望 HIT,实际 HIT / 违规 1 条)

FH-ENC-001 [MINOR] 文本文件编码:0 BOM / 0 CRLF / 全 UTF-8

规范 §11.5 / 发布端门禁 #3 夹具 10 例

  • OK 正例①:UTF-8 无 BOM + LF(本仓 1253 个文本文件全是这样)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:CRLF 例外清单内的文件(plugins/mod_system/说明.md 是有意保持 CRLF 的历史文件)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:非文本扩展名(二进制 / 图片 / sqlite)一律跳过(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:无扩展名的特殊文本文件 web.config / .htaccess 仍按文本检查(且这里是干净的)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:PHP 文件带 UTF-8 BOM(会在 <?php 前产生输出 → headers already sent)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:plugin.json 带 BOM(json_decode 返回 null,插件在后台列表里消失)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:CRLF 且不在例外清单(assets/script.js)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:GBK 编码的中文(非 UTF-8 → json_encode 返回 false)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:同一文件同时有 BOM + CRLF —— 合并成 1 条(不刷屏)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑥:同包好坏混装 —— 只报坏的那个文件(期望 HIT,实际 HIT / 违规 1 条)

FH-ENC-002 [MINOR] PHP 变量名不得含非 ASCII 字节(全角标点会被吞进变量名)

规范 §11.5 / 全角标点 夹具 10 例

  • OK 正例①:正确的写法 {$name}, —— 用 {} 隔开,词法器不会吞(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:单引号串里的 $name, —— 不做变量替换,天然没问题(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:注释里提到 $name, —— 不是代码(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:变量后紧跟半角标点 / 空格(词法器在此处正常断开)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:全角标点在变量前面(不会被吞)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:assets/lib/ 下第三方库整体豁免(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:"$name," —— 全角逗号被吞进变量名(本项目复发 6 次的原型)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:全角句号结尾($title。)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:heredoc 里被吞的变量(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:同一行两个坏变量 = 2 条(逐变量报,不合并)(期望 HIT,实际 HIT / 违规 2 条)

FH-ENC-003 [MINOR] 无调试残留(var_dump / print_r / console.log / debugger / [DBG])

规范 §11.5 / 发布端门禁 #4 夹具 9 例

  • OK 正例①:干净代码(不调用调试函数)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:注释与字符串里提到 var_dump / console.log(token 层 / 非 js 文件天然排除)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:assets/lib/ 下第三方库豁免(highlight.js 自带 console.log 属正常)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:用 error_log 记录(服务端日志,不输出到页面)—— 不算残留(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:PHP 里 var_dump($x)(会把内部结构暴露给访客)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:JS 里 console.log(整包规则才能查到 .js)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:JS 里 debugger(生产环境会直接断在断点)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:同一文件多处 —— 只报 1 条(列出来源,不逐行刷屏)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:[DBG] 临时标记(期望 HIT,实际 HIT / 违规 1 条)

FH-INL-003 [MINOR] 无静态 style="" 属性(CSS 变量注入豁免)

规范 §7.3-3 / §7.3-4 / §7.3-5 夹具 14 例

  • OK 正例①:内联 CSS 变量注入(§7.3-4 明文合规;取自 content_review/views/admin.php:69 原文)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:PHP 字符串拼接出来的 CSS 变量(announcements_display.php:242 原文形态),值被拼接截断也必须判合规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:多个 CSS 变量写在同一个 style 里(forum_required_tag/views/admin.php:180 原文形态)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:整条由 PHP 决定(style="<?php echo $s; ?>"),静态判不了,必须放过(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:注释里提到 style="" 不算违规(token 层天然排除)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:PHP 变量名与 SQL 列名里的 style($style / SET style = :style)不是属性(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑦:data-style / x-style 不是 style 属性(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑧:assets/lib/ 下的第三方库整体豁免(口径对齐发布端门禁 #4)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:静态边距样式(§7.3-3 明文违规)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:硬编码 display:none(§7.3-5,应改 class 或 x-cloak)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:混写 —— CSS 变量合规,但同属性里的静态声明必须报(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:PHP 字符串里拼出来的 HTML 内联样式(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:单引号属性的写法同样要抓(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑥:值里带 PHP 拼接的静态样式(置信度 medium,仍要报)(期望 HIT,实际 HIT / 违规 1 条)

FH-META-001 [MINOR] plugin.json 必填字段完整 + hooks 指向真实存在的文件

规范 §三 夹具 13 例

  • OK 正例①:字段齐全 + hooks 指向真实文件(本仓 59 个插件的标准形态)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:空 hooks 对象 {}(不声明钩子的插件是合法的)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:没有 plugin.json —— 交给 FH-PKG-001 报,本规则不重复刷屏(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:min_core / category / icon 缺失不报(规范标「否」;语料 52/59 都没填 min_core)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:带 -rc1 / +build 后缀的版本号(合法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:JSON 语法错(尾随逗号)—— json_decode 返回 null,插件在后台列表里消失(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:缺必填字段 hooks(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:activated 写成字符串 "true"(后台开关显示成「关」)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:hooks 指向的文件不存在(核心静默跳过 → 插件启用了但功能不生效)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑤:hooks 路径用反斜杠(Linux 上 hook\init_after.php 是一个文件名)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑥:版本号不是三段式(v1.0)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑦:name 是空字符串(等同没填,静默显示空白)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例⑧:hooks 写成数组而不是对象(期望 HIT,实际 HIT / 违规 1 条)

FH-META-002 [MINOR] plugin.json 的 category 必须在核心 10 类内

规范 §三 夹具 8 例

  • OK 正例①:category: "tools"(10 类之一)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:category: "entertainment"(10 类里最容易写错的边界值之一)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:坏 JSON —— 交给 FH-META-001 报,本规则不重复刷屏(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:没有快照(categories 为空)→ 发 INFO,不判违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:category: "tool"(少个 s —— 真实高频笔误)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:category: "plugin"(不是 10 类里的任何一类)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:完全没填 category → 归入「未分类」,后台分类 Tab 里看不见(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:category: ""(空串等同没填)(期望 HIT,实际 HIT / 违规 1 条)

FH-META-003 [MINOR] plugin.json 的 icon 必须在核心图标表内(否则渲染成问号)

规范 §6.4-4 夹具 7 例

  • OK 正例①:icon: "gift"(图标表里真实存在)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:没填 icon(规范标「否」,可选字段)—— 不能报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:空串 icon(等同没填)—— 不报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:没有快照(icons 为空)→ 发 INFO,不判违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:icon: "gif"(少个 t)→ 渲染成问号,商店里直接显示问号(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:icon: "fa-gift"(带 FA 前缀的写法,$map 里没有)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:icon: "积分"(中文)—— 一定不在表里(期望 HIT,实际 HIT / 违规 1 条)

FH-META-004 [MINOR] hooks 声明的钩子名必须在核心钩子表内(写错就永不触发)

规范 §9.1 夹具 8 例

  • OK 正例①:hooks 键全是核心真实钩子(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:空 hooks 对象(不声明钩子的插件合法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:没有 hooks 字段 —— 交给 FH-META-001,本规则不管(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:没有快照(hooks 表为空)→ 发 INFO,不判违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:插件间私有钩子(由本站另一个插件 Plugin::hook() 触发)→ 不报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:layout_head_ends 多了个 s(真实高频笔误,永不触发)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:完全自造的钩子名(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:好坏混装 —— 2 个未知 = 1 条(列在一起,不逐条刷屏)(期望 HIT,实际 HIT / 违规 1 条)

FH-META-005 [MINOR] 版本号多处一致(plugin.json / 说明.md / Plugin.php / assets)

规范 §12.2-7 夹具 10 例

  • OK 正例①:四处全一致(plugin.json / 说明.md / Plugin.php / assets)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:说明.md 里只有「最低 PHP 版本:8.2.9」—— 不能把 PHP 版本当插件版本(实测最容易误报的一类)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:说明.md 根本没有版本行 → 判不了,不报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:hook/ 与 views/ 里的文件级 @version 刻意不查(实测 461 处大多数故意不跟随)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:坏 JSON → 交给 FH-META-001,本规则不重复报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:version 不是三段式(v1.0)→ 由 FH-META-001 报,本规则判不了就不报(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:说明.md 版本行还是旧版本(升级页提示有新版,用户看说明以为没升成功)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:Plugin.php 的 @version 没跟着改(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:assets 的版本注释没跟着改(门禁查不到,属静默卫生债)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例④:多处不一致 —— 每个插件只报 1 条(不刷屏)(期望 HIT,实际 HIT / 违规 1 条)

FH-DB-001 [INFO] 插件直接查询核心表(跨层依赖,建议确认)

规范 §11.8 夹具 8 例

  • OK 正例①:只查插件自己的表(pm_products)—— 不归本规则管(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:SQL 在注释/文档里提到核心表 —— token 层不会把它当成 SQL 字面量(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:动态表名(FROM {$t})—— 静态判不了,不猜(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:没有 core_tables(未导入快照)→ 发 INFO,不判违规(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:assets/lib/ 下第三方库豁免(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:直接 SELECT … FROM users(跨层依赖核心表)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:一条 SQL 里 JOIN 两张核心表 —— 只报 1 条(列全部表名)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:写核心表(UPDATE users SET …)风险更高,更要提示(期望 HIT,实际 HIT / 违规 1 条)

FH-HOOK-001 [INFO] 钩子文件里出现 $this->(闭包作用域下不可用)

规范 §11.24 夹具 8 例

  • OK 正例①:钩子里用 htmlspecialchars / I18n::get(规范写法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:注释里提到 $this->e()(不是代码)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:视图文件里用 $this->e() 是正常的 —— 本规则只管 hook/ 目录(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:$this 后面不是 ->(如 is_object($this) 那种传参)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:assets/lib/ 下第三方库豁免(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:钩子里 $this->e($t) —— 闭包作用域下 fatal(本项目 §T.1 记的静默失败)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:多行多处 $this-> —— 一个文件只报 1 条(列出各处行号)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:$this?->e()(PHP 8 空安全运算符)同样要抓(期望 HIT,实际 HIT / 违规 1 条)

FH-HOOK-002 [INFO] 钩子文件里声明顶层函数/类(重复 include 会 Cannot redeclare)

规范 §9.5 夹具 9 例

  • OK 正例①:钩子只做「取数据 + 输出」(本仓主流写法)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例②:闭包(匿名函数)不是顶层函数声明(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例③:匿名类 new class 不是顶层类声明(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例④:视图文件里声明函数不归本规则管(只查 hook/)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑤:Foo::class 不是类声明(最容易误抓的一例)(期望 PASS,实际 PASS / 违规 0 条)
  • OK 正例⑥:生命周期钩子(route_* / init_after)里声明函数 —— 每请求只 include 一次,豁免(期望 PASS,实际 PASS / 违规 0 条)
  • OK 反例①:内容类钩子里声明顶层函数(第二次 include 就 Cannot redeclare)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例②:钩子里声明类(绕过自动加载器 + Cannot redeclare)(期望 HIT,实际 HIT / 违规 1 条)
  • OK 反例③:函数 + 接口 + trait 都有 —— 只报 1 条(列出全部符号)(期望 HIT,实际 HIT / 违规 1 条)
轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 9 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录