[审计]vn_postname(马甲发帖)插件审计报告

👑Lv.11 元老 🌞 骨干会员
2026-10-25 00:35:31

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:WWW/docs/vn_postname插件审计报告_2026-09-23.md。

vn_postname(马甲发帖)插件审计报告

  • 审计日期:2026-09-23
  • 审计对象:plugins/vn_postname/(v1.1.0,plugin.json 声明版本)
  • 审计依据:docs/插件开发规范.md(2026-09-22 版)
  • 审计方式:只读审计 —— 读源码 + 实测取证,未改动任何插件文件
  • 结论:不规范,需修复。共 17 项问题(P0×3 / P1×4 / P2×4 / P3×6),其中 P0-2 为设计级安全缺陷。

一、结论速览

级别数量一句话
P03① 生命周期类名推导失效(卸载留孤儿表)② 身份冒用 —— 可用 @@管理员名 以管理员身份发帖 ③ 发帖提示钩子是空实现,用户不知道有此功能
P14④ 三语语言包缺键 ⑤ init_after 每请求跑 DDL ⑥ 不校验用户封禁状态 ⑦ 自动建号零护栏
P24⑧ 全文件 CRLF ⑨ 发帖数双计、马甲积分不同步 ⑩ 搜索索引条件性不同步 ⑪ 后台视图双重转义
P36版本号不同步、uninstall() 无异常保护、strip 留空段落等

值得肯定(实测通过,见 §四):钩子名与参数全部核实正确、后台路由挂 admin_route_register、CSRF 生效、不直写核心用户表、双写改写成功、strip_marker 生效、零内联、无残留文件。

最要紧的一条:P0-2。当前后台配置 allowed_groups = [](仅管理员可用),风险被默认配置临时兜住;但插件卖点正是"按用户组授权",一旦管理员勾选任何普通用户组,该组所有人即可冒用管理员身份发帖。


二、插件概况

plugins/vn_postname/
├── plugin.json          762B   version 1.1.0, hooks×5, permissions[route:admin]
├── Plugin.php         16518B   配置存取 / 身份解析 / 作者改写 / 日志
├── AdminController.php 3045B   后台设置页(继承 Admin\BaseController)
├── hook/
│   ├── init_after.php            建表兜底
│   ├── admin_route_register.php  后台路由
│   ├── thread_create_after.php   发帖后改写作者
│   ├── post_create_after.php     回帖后改写作者
│   └── post_create_extra.php     发帖表单提示 ← 空实现
├── views/admin.php     6893B
├── lang/{zh,en,zh_tw}.php
├── assets/style.css
└── data/vn_postname.sqlite  + .htaccess + web.config(核心自动生成)

工作机制:核心在 PostController / ThreadController 以「当前登录用户」为作者写入,只提供 thread_create_after / post_create_after 两个"创建后"钩子(不传正文)。插件在 after 钩子里读回刚建的帖子 → 解析 @@用户名 → 解析/创建马甲 → 改写作者(main_index 索引 + 分桶真相源双写)→ 按需去掉 @@ 标记。

核心 API 依赖核查(全部存在,签名匹配):

调用位置结论
User::find()app/Core/Model.php:24✓ 继承自基类,SELECT * 含 role/group_id
User::findByUsername()app/Models/User.php:27✓
User::create(u,p,e)app/Models/User.php:489✓ 不写 role → 落表默认值,不会造管理员
User::updateAvatar() / defaultAvatarPath():183 / :205✓
User::incrementPostCount()app/Models/User.php:156✓
User::batchGetNames()app/Models/User.php:351✓ 静态
Thread::find() / update()app/Models/Thread.php:187 / :440✓ update 白名单含 title/content
Post::find() / update()app/Models/Post.php:218 / :328✓
Database::getInstance()->fetchAll()—✓ 用对了(Database 无 prepare(),此处未误用)

表结构核查:data/vn_postname.sqlite 实际结构与代码 DDL 完全一致(无手工改动痕迹,§11.21-2 通过);vnpn_log 有 1 行真实记录,说明插件已在生产使用过。


三、问题清单

P0-1 生命周期类名推导失效 → 卸载留孤儿表

规范依据:§4.1 / §11.12 / §11.21-4「目录名含下划线 → 必须在 Plugin.php 末尾注册 class_alias」

证据(实测):

Plugin\vn_postname\Plugin          class_exists = false
Plugin\VnPostname\Plugin           class_exists = true

app/Helpers/Plugin.php:699 用 "\\Plugin\\{$name}\\Plugin" 拼主类名,$name = 目录名 vn_postname;实际命名空间是 Plugin\VnPostname。下划线不是大小写差异,class_exists 恒 false → activate() / deactivate() / uninstall() 三方法静默跳过。

影响面(按规范 §4.1 口径):

  • activate():≈ 无影响 —— 有 init_after 建表兜底
  • deactivate():无影响(空实现)
  • uninstall():唯一真实后果 —— 卸载后 vnpn_config / vnpn_log 两张表残留,且完全静默

修法(零核心改动,本机已有 6 个先例:mod_system / vn_block / quiz_duel / third_party_login / points_mall / bitiful_storage):

// Plugin.php 末尾
if (!\class_exists('Plugin\\vn_postname\\Plugin', false)) {
    \class_alias(Plugin::class, 'Plugin\\vn_postname\\Plugin', false);
}


P0-2 身份冒用:可用 @@管理员用户名 以管理员身份发帖 ★最严重

问题:Plugin::resolveUser()(Plugin.php:175-211)对任意已存在的用户名直接返回其 uid,没有任何"只能用自己的马甲"或"禁止冒用高权限账号"的约束:

$user = (new \app\Models\User())->findByUsername($username);
if ($user && (int)$user['id'] > 0) {
    return [(int)$user['id'], false];   // ← 直接采纳,不校验目标身份
}

实测证据(只读探测,allowCreate=false,未创建任何账号):

@@flinthub             -> uid=1     ← 管理员
@@虚拟化大师            -> uid=2
@@浏览器内核工程师      -> uid=3
@@GitHub潜水员         -> uid=4
@@新手导师             -> uid=5
@@会议演讲者           -> uid=6
不存在(allowCreate=false) -> uid=0

风险链:

  1. 管理员在后台按插件设计勾选「普通会员」(group_id=1)
  2. 任一普通会员发帖时输入 @@flinthub
  3. isAllowed(操作者) = true(组已授权)→ resolveUser('flinthub') = uid 1
  4. reattributeThread() 把 topic_index.uid 与 topic.uid 双写为 1
  5. 前台帖子作者显示为管理员,管理员的 post_count 也被 +1

这不是配置失误,是插件功能设计的必然结果 —— 插件把"任意用户名"当作可自由指派的马甲池,而系统中「用户名」同时是登录凭据与身份标识。同一机制下,普通用户也可以 @@ 另一个普通用户来栽赃。

当前为何未爆:实测配置 {"enabled":1,"allow_create":1,"strip_marker":1,"allowed_groups":[]} → allowed_groups 为空 → isAllowed() 仅对 role='admin' 返回 true(实测 uid1=true,uid2~6=false)。风险被"仅管理员可用"这个默认值兜住了。

可选修法(需你拍板,涉及产品语义):

方案做法代价
A(推荐,最小改动)resolveUser() 增加拒绝名单:目标 role='admin' 或 group_id 属于管理组 → 拒绝保留"马甲"本意,杜绝提权;但用户仍能冒用普通用户
B只允许使用自己名下的马甲:马甲表记 owner_id,resolveUser 校验 owner_id === operatorId语义最干净,但要加表加字段、改交互(需"创建马甲"入口)
C仅禁止冒用 role='admin'(等价 A 的简化版)与 A 相近,实现更粗
D保持现状,仅在后台勾选区加醒目警告零代码改动,风险自担

我倾向 A:一行判定即可堵住提权,且不动现有数据模型。但这属于产品语义取舍,我不替你拍板。


P0-3 发帖提示钩子是空实现 → 用户完全不知道有 @@ 功能

规范依据:§9.5「渲染钩子只输出提示,不做重定向/中断」—— 但本钩子什么都没输出。

证据:hook/post_create_extra.php 全文 20 行,末尾(:20)为:

if (!\Plugin\VnPostname\Plugin::isAllowed((int)$user['id'])) {
    return;
}
// ← 文件到此结束,无任何 echo

文件头注释却写着「渲染钩子:只输出提示,不做重定向/中断」。

实测(php -S + 管理员会话):

GET /post/new  -> http=200 size=34983
  钩子锚点 id="file-name" 存在 : 1     ← 证明钩子确实在该页触发
  马甲提示出现次数             : 0     ← 钩子输出为空

连带问题:en.php / zh_tw.php 里都写好了 plugin.vn_postname.form_hint 文案(「提示:内容或标题里输入 @@使用者名称 即可用该身分发佈」),只有 zh.php 没有 —— 明显是"提示没写完就搁置"留下的痕迹(见 P1-1)。

另::10 的 if (($target_type ?? '') === 'blog') 是死代码 —— 规范 §9.1 :676 明确 post_create_extra 注入参数为「无」,且全仓库只有 app/Views/post/create.php:72 一处挂载(已 grep 核实)。$target_type 永不存在,该判断恒为 false。

修法:补上提示输出(<div class="form-hint"> + I18n::get('plugin.vn_postname.form_hint')),并删掉死代码分支;同时补 zh.php 的键(P1-1)。


P1-1 三语语言包键集合不一致

规范依据:项目 i18n 基线(三语键集合零差异 + 键序一致);规范 §8 亦要求"建议同步"

实测:

zh=22  en=23  zh_tw=23
zh - en  = []                              en - zh  = ['plugin.vn_postname.form_hint']
zh - zh_tw = []                            zh_tw - zh = ['plugin.vn_postname.form_hint']
en - zh_tw = []                            zh_tw - en = []

唯一缺键:plugin.vn_postname.form_hint(中文简体缺失)。

当前未显现的原因:该键当前没有任何视图引用(因为 P0-3 的钩子是空的)。一旦修 P0-3,中文用户就会在发帖页看到键名原文 plugin.vn_postname.form_hint。两条必须一起修。


P1-2 init_after 每请求跑 DDL,无戳文件门控

规范依据:§11.15 明文「别在控制器每请求跑 DDL;ensureSchema() 本身无缓存门控,要在插件侧加戳文件 / 会话守卫」

证据:hook/init_after.php 全文 9 行,无条件执行两条 DDL:

\app\Helpers\Plugin::ensureSchema('vn_postname', \Plugin\VnPostname\Plugin::ddlConfig());
\app\Helpers\Plugin::ensureSchema('vn_postname', \Plugin\VnPostname\Plugin::ddlLog());

app/Helpers/Plugin.php:86-89 确认 ensureSchema() 无门控:

public static function ensureSchema(string $name, string $ddl): void
{
    self::db($name)->exec($ddl);   // 直接 exec,无 marker 判断
}

实测开销(200 次循环均值):

ensureSchema ×2 每请求: 0.175 ms + 0.144 ms = 0.319 ms
(对照)戳文件 is_file: 0.142 ms

单看耗时差距不大(~0.18ms),但真正的代价是:每个前台请求都对插件 SQLite 库执行一次 DDL(CREATE TABLE IF NOT EXISTS 在 SQLite 中走 schema 写事务),在并发下会争抢该库写锁。

修法(本机既有范式,points_mall/hook/init_after.php 同款):

$__stamp  = __DIR__ . '/../../../protected/.vn_postname_table_cache';
$__dbFile = __DIR__ . '/../data/vn_postname.sqlite';
if (is_file($__stamp) && is_file($__dbFile)) { return; }
try {
    \Plugin\VnPostname\Plugin::activate();
    @file_put_contents($__stamp, '1', LOCK_EX);
} catch (\Throwable $e) {
    \error_log('vn_postname init error: ' . $e->getMessage());
}

多判 $__dbFile 存在是必要的:只判戳文件会在"戳文件在、库被删"时永久跳过建表。


P1-3 isAllowed() 不校验用户状态(封禁/禁用)

规范依据:§11.8 核心库访问边界精神(权限判定应基于权威状态)

证据:Plugin.php:148-169,判定链只有两条 —— role === 'admin' 或 group_id 在授权列表:

if ((string)($user['role'] ?? '') === 'admin') return true;
$gid = (int)($user['group_id'] ?? 0);
return $gid > 0 && in_array($gid, $cfg['allowed_groups'], true);

未检查 users.status(active / 封禁)。而 user_groups 表里存在 id=5「封禁账户」:

1 普通会员 (is_default=1)   2 VIP会员   3 版主   4 管理员   5 封禁账户

风险:若管理员在后台勾选了「普通会员」(id=1),而某用户 status='banned' 但 group_id 仍为 1 → 该封禁用户仍可用马甲发帖(是否真能走到钩子取决于核心发帖入口是否拦截,但插件侧自身没有任何防线)。

当前未显现:实测 users 表 285 行 status 全为 active。

修法:isAllowed() 增加 if (($user['status'] ?? '') !== 'active') return false;


P1-4 自动建号零护栏

规范依据:§11.8「禁止直写核心库用户表」—— 本插件已合规(走 User::create()),但业务护栏缺失。

证据:Plugin.php:191-206,allow_create 开启时对任意 @@用户名 建号:

$password = bin2hex(random_bytes(16));
$email    = 'vnpn_' . bin2hex(random_bytes(6)) . '@noreply.local';
$uid      = (new \app\Models\User())->create($username, $password, $email);

缺失的护栏:

  1. 无用户名黑名单:可创建 admin / administrator / 系统 / 客服类易混淆名(User::create() 只校验唯一性,不校验语义)
  2. 无频率限制:单用户可无限建号(无 RateLimiter 调用)
  3. 无数量上限:未限制单用户/全站马甲总数
  4. 不写插件关联表:user_level 插件的 ul_users.map_lv(信任等级)不会为新马甲建行 → 马甲在等级体系里缺失;其他以 users 为外键的插件同理
  5. created_at 被设置,但 email_verified=0、level/points 走表默认 —— 与正常注册流程产出的用户不完全等价

实测已有产出:users 表 id=285 小助手 / vnpn_3dc45b8733bd@noreply.local / avatar=seed_avatars/2026-09/285.svg(头像补齐生效 ✓),post_count=1。

修法建议:至少加用户名黑名单(拒绝与 role='admin' 用户重名、拒绝保留字)+ RateLimiter 节流;关联表补齐可作为后续项。


P2-1 全部 12 个文本文件为 CRLF 行尾

规范依据:§11.19「插件内所有文本文件统一 UTF-8 无 BOM + LF。BOM 与 CRLF 都会被打包 / 卫生门禁判为 FAIL」

实测(Python 数 b"\r\n",规范指定手法):

AdminController.php       CRLF=  90  LF=  90  BOM=False
Plugin.php                CRLF= 430  LF= 430  BOM=False
plugin.json               CRLF=   0  LF=  19  BOM=False   ← 唯一 LF
assets/style.css          CRLF=   8  LF=   8  BOM=False
hook/admin_route_register.php  CRLF=11  LF=11  BOM=False
hook/init_after.php            CRLF= 9  LF= 9  BOM=False
hook/post_create_after.php     CRLF=15  LF=15  BOM=False
hook/post_create_extra.php     CRLF=20  LF=20  BOM=False
hook/thread_create_after.php   CRLF=13  LF=13  BOM=False
lang/en.php                    CRLF=30  LF=30  BOM=False
lang/zh.php                    CRLF=29  LF=29  BOM=False
lang/zh_tw.php                 CRLF=30  LF=30  BOM=False
views/admin.php                CRLF=126 LF=126 BOM=False

BOM 全部干净 ✓;CRLF 12/13 文件命中(plugin.json 是 LF)。修法:批量转 LF 后必须用 Python 复核(grep/od 在本环境不可靠)。

注意:plugins/mod_system/ 下 4 个文件是规范明文例外(必须保持 CRLF),本插件不在例外清单内。


P2-2 发帖数双计、马甲积分不同步

证据:Plugin.php:297-298(发帖)与 :340(回帖):

// 马甲发帖数 +1(走核心封装;操作者发帖数不回退,避免直写核心 users 表)
(new \app\Models\User())->incrementPostCount($uid);

问题:核心在 PostController / ThreadController 里已经给操作者 post_count + 1;插件再给马甲 +1 → 一次发帖,全站发帖总数虚高 1(两个人各记一笔)。

代码注释说"操作者发帖数不回退,避免直写核心 users 表"—— 这个取舍合规(§11.8 禁止直写),但结果是计数失真。

另:核心对发帖调用 Points::award(...) 给操作者加分(ThreadController.php:140),插件未给马甲加积分 → 马甲 points=0(实测 id=285 points=0)→ 等级/积分体系对马甲失效。

修法选项:① 操作者 decrementPostCount() 对冲(走 Model 方法,合规)② 或接受现状并在文档中说明"马甲发帖计入马甲名下,操作者同时计入"。积分是否补给马甲需你拍板(涉及经济体系)。


P2-3 搜索索引条件性不同步

证据链:

  • app/Controllers/PostController.php:155 Search::indexThread($threadId); → :157 Plugin::hook('thread_create_after', ...)
  • 即 索引时标题仍带 @@用户名
  • 插件随后 Thread::update(['title' => stripMarker(...)]),而 Thread::update()(app/Models/Thread.php:527-530):
$mode = \app\Helpers\Settings::get('queue_mode', 'sync');
if ($mode !== 'sync') {
    Queue::push('rebuild_search', ['topic_id' => $id]);
}
// sync 模式:注释说"控制器已同步 Search::indexThread,此处不重复重建也不入队"

→ queue_mode='sync' 时索引永不重建,@@用户名 词条残留。

实测(当前部署 queue_mode = cron):

帖子 92 索引词条: [新闻] [马甲] [甲发] [发帖] [帖测] [测试]
全库含 @ 的词条: 0

结论:当前 cron 模式下由异步队列兜住,实测索引干净。缺陷为条件性(sync 模式 + 队列未消费时才会显现)。

修法:插件在 Thread::update() 后显式补一次 Search::indexThread($threadId)(幂等,代价可接受,且只在用到 @@ 标记的少数发帖上触发)。


P2-4 后台视图双重转义

证据:views/admin.php:102 与 :111

$tgName = $names[$tgId] ?? $this->e((string)$l['username']);   // :102 已转义
...
<?php echo $this->e((string)$tgName); ?>              // :111 再转一次

后果:当 batchGetNames() 未命中、回落到日志里的 username 时,含 < / & / " 的用户名会被双转义(如 A&B → 显示成 A&amp;B)。命中 $names 的正常分支只转一次 ✓ 正确。

修法::102 去掉 $this->e(),统一在输出点转义(符合项目"赋值时转义"约定的输出点单次转义原则)。


P3 级(6 项,不阻塞功能)

#问题证据影响
P3-1版本号不同步plugin.json = 1.1.0,Plugin.php:7 / AdminController.php:7 头注释 = 1.0.0排查时误导
P3-2uninstall() 无异常保护Plugin.php:50-51 裸调 self::db()->exec(),无 try/catch(对比 vn_block 修复后的范式)单表删除失败即中断,另一表残留
P3-3stripMarker 留空段落实测 <p>@@张三</p><p>正文</p> → <p></p><p>正文</p>标记独占一段时渲染出多余空行
P3-4__nav_active='vn-postname' 是死参数app/Views/admin/_sidebar.php 无插件子项;admin_url 仅 app/Views/admin/plugins.php:106 消费无害但无意义
P3-5handlePostCreated() 的 $threadId 形参未使用Plugin.php:307 收了 int $threadId,方法体内无引用轻微冗余(钩子文件仍正确传参)
P3-6规范文档与代码不一致(非插件缺陷)规范 §9.1 :691 注「post_create_after 此时仍在外层事务内」,实际 ThreadController.php:144 commit() → :153 if ($replyOk) → :175 hook实际在事务外。这条对审计有实质意义:插件在钩子里做跨库写 + 文件 I/O 是安全的,无需按"事务内"约束返工。建议顺手更新规范


四、合规项(实测通过)

检查项规范依据实测结果
钩子名与参数核实§9.1 / §9.5✓ thread_create_after(thread_id,user_id) / post_create_after(post_id,thread_id,user_id) 全部与规范一致
后台路由挂对钩子§5.3✓ 注册在 admin_route_register,route:admin 权限已声明,核心校验通过
CSRF 校验§一五✓ 无 CSRF / 错 CSRF 均 403
未登录访问拦截—✓ 302 → /login
后台管理员校验—✓ 继承 Admin\BaseController,构造器自动校验 + 二次验证
零内联§11.4-7✓ `grep -rnE '<style\
php -l 语法§11.5-32✓ 12/12 通过
无残留文件§二禁止项✓ 无 .bak / .tmp / .revert / .orig
独立库 + 数据目录防护§十✓ Plugin::db() 自动生成 .htaccess + web.config
不直写核心用户/积分表§11.8-1✓ 全部走 Model 封装(create/incrementPostCount/updateAvatar)
批量取用户名§11.8-3✓ AdminController:84 用 User::batchGetNames()
核心 Model update() 白名单§11.8-4✓ Thread::update() / Post::update() 均为重写方法,字段在白名单内
后台视图区块§六✓ section('title') + section('content')
后台 CSS 路径—✓ __DIR__.'/../assets/style.css' 正确命中(实测 ?v=1786516878,非退化的 v=1)
后台 CSS 硬编码色§11.7✓ 有正当理由 —— 后台无 --mn-* 变量(规范 §11.10),文件头注释已明示;未污染前台样式
plugins_cache.json 同步§11.5-30✓ hooks 清单与 plugin.json 一致
双写改写正确记忆 §1✓ 实测帖子 92 topic_index.uid = 285(马甲)
头像补齐—✓ 实测 seed_avatars/2026-09/285.svg
strip_marker 生效—✓ 实测 /thread/92 零处 @@ 残留,标题渲染为 [新闻] 马甲发帖测试
后台页 i18n 无缺键§八✓ 实测页面 零处键名原文
配置 UPSERT§十✓ ON CONFLICT(id) DO UPDATE 单行幂等
user_groups 读法§11.8✓ 与 points_mall/AdminController.php:107 完全同款,符合既有范式
卸载干跑§11.5-34✓ 库副本上两表清空(但核心路径因 P0-1 不会真正执行)


五、实测记录(可复现)

环境:php -S 127.0.0.1:8899(PHP 8.2.9nts)+ 手写管理员会话(admin_verified_at + csrf + csrf_time),curl --noproxy '*'。

#探测结果
1class_exists('\Plugin\vn_postname\Plugin')false ← P0-1
2resolveUser('flinthub', false)uid=1 ← P0-2
3GET /post/new200,id="file-name" 锚点存在,马甲提示 0 处 ← P0-3
4三语键比对zh=22 / en=23 / zh_tw=23,缺 form_hint ← P1-1
5ensureSchema ×2 开销0.319 ms/请求(戳文件对照 0.142 ms)← P1-2
6GET /admin/vn-postname200,<title>马甲发帖 - 燧石社区</title>,5 个组复选框,1 条日志
7POST /admin/vn-postname/save(无/错 CSRF)403 / 403 ✓
8GET /admin/vn-postname(无会话)302 → /login ✓
9GET /thread/92200,作者「小助手」出现 6 次,@@ 残留 0 处 ✓
10帖子 92 搜索索引词条6 条,零 @ 词条(queue_mode=cron 兜住)
11卸载干跑(库副本)两表清空 ✓,临时库已删 ✓
12零内联 / php -l / 残留文件全 PASS

探针清理:测试会话(vnpnprobe%)已删;curl 产生的 3 个游客会话已按精确 id 删除(未触碰任何登录态会话);7 个临时文件已删;sessions 表恢复 167 行(与审计前一致);php -S 已停。

另发现(非本次探针,属你已有数据):帖子 id=92「[新闻] 马甲发帖测试」(作者 uid=285)+ 用户 id=285「小助手」+ vnpn_log 1 行 —— 是你自己测试马甲功能留下的,我未改动。是否清理请你决定。


六、修复优先级建议

批次项理由
第 1 批(必做)P0-1 class_alias、P0-3 补提示输出、P1-1 补 zh.php 键三条互相关联,改动小、风险低,且 P0-3 + P1-1 必须一起改
第 2 批(需你拍板)P0-2 身份冒用产品语义取舍,方案 A/B/C/D 见上。建议先定方案再动手
第 3 批(加固)P1-2 戳文件门控、P1-3 状态校验、P1-4 建号护栏独立可分批,P1-2 有现成范式
第 4 批(收尾)P2-1 转 LF、P2-2 计数策略、P2-3 索引补偿、P2-4 去双转义P2-2 涉及经济体系,需你定口径
第 5 批P3-1~P3-5顺手改
文档侧P3-6 更新规范 §9.1 :691 的过时注释避免下次审计误判


七、总体评价

架构判断:插件对核心约束的理解是到位的 —— 钩子名/参数核实准确、双写策略(索引 + 分桶真相源)正确、明确避开了"直写核心 users 表"这个 §11.8 红线(注释里还写明了取舍理由)、后台路由与权限声明规范。基础工程质量高于本机插件平均水平。

问题集中在两处:

  1. 收尾未完成 —— P0-3 的提示钩子写了一半(注释写了"只输出提示"、en/zh_tw 语言键备好了,但 zh.php 没补、echo 没写);P0-1 的 class_alias 漏了;P2-1 的行尾没统一。像是功能验证通过就停下了。
  2. 安全模型未闭环 —— P0-2 是设计层面的:插件把"用户名"当成可自由指派的标签,但在这个系统里用户名同时是登录凭据与权限标识。这不是编码疏忽,是需求定义时就该裁决的问题。

一句话:功能可用、基础规范扎实,但不能按现状开放给非管理员用户组使用 —— 开之前必须先解决 P0-2。


审计方式:只读 + 实测取证。本报告未改动任何插件文件、未修改任何业务数据。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 2 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录