本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:暂存20260913/FlintHub_双模型交叉裁决报告_20260904.md。
FlintHub 双模型交叉裁决报告
- 裁决日期: 2026-09-04
- 裁决对象:
- 报告 A(Qwen):
FlintHub代码审计报告.md(深度代码审计,H1~H6 / M1~M5 / L1~L6) - 报告 B(MiMo):
FlintHub_深度架构审计报告_20260904.md(跨层架构审计,H-1~H-5 / M-1~M-4 / L-1~L-3) - 核实方式: 逐条对照源码(含控制器/模型/SplitDB/Helper/插件 20+ 文件),并结合已实施的 P0/P1/P2/P3 批次修复记录排除"已修复"项。
- 备注: 用户最初提到的
FlintHub代码漏洞审计报告.md为空模板(821 字节,各节均为"待补充"),无内容可供裁决,已按更正后的两份报告执行。
一、双模型确认的问题(合并去重,标注双模型确认)
两份报告独立命中的同一根因,合并为一条。
1. [双模型确认] 搜索降级路径读退役表 threads(Qwen M1 = MiMo H-1)
- 文件:行号:
app/Helpers/Search.php:312-321(FROM threads t的 COUNT 与分页查询) - 问题: SplitDB 迁移后
business.threads已退役(P4 起不再写入,实测 0 行),但searchLIKE()降级路径仍FROM threads t。倒排索引异常触发降级时,帖子搜索结果对新帖恒为空。 - 双模型表述差异: Qwen 侧重"读退役表导致结果恒空";MiMo 侧重"搜索在 SplitDB 后遗漏新帖"。本质同一问题,合并。
- 补充: 该问题与 Qwen H4(
:since崩溃)位于同一批 SQL,须一并修复(见 P0-2)。
2. [双模型确认] 附件清理读退役表 → 误删在用附件(Qwen M2 = MiMo H-2)
- 文件:行号:
app/Helpers/Settings.php:613-617(cleanupOrphanAttachments的$contentTables) - 问题: 用
SELECT content FROM threads/posts WHERE content LIKE :p收集正文内嵌图片以保护在用附件,但threads/posts均为退役空表 → 分片后主题/回复正文里的图片 URL 扫不到 → 后台"清理孤儿附件"会把仍被正文引用的上传文件误删(数据丢失)。blogs未分片,保留有效。 - 双模型表述差异: Qwen 侧重"误删在用附件(数据丢失)";MiMo 侧重"附件清理逻辑扫描遗漏新内容"。同一问题,合并。
二、仅单份报告提出、DS 已核实的问题
Qwen 单独提出(逐条核实)
| 编号 | 文件:行号 | 结论 | 裁决 |
|---|---|---|---|
| H1 | app/SplitDB/ExternStorage.php:48,156,166,182,270 | 属实。缓存键 extern_{id} 只用裸 ID;IDGenerator.php:35 中 topic/reply 两条独立自增序列(均从 1 起)→ ID 区间重叠,启用 APCu 后读主题 5 可命中回复 5 的正文缓存(跨内容串读)。当前主机未装 APCu,属休眠态。 | P0 |
| H2 | app/SplitDB/ExternStorage.php:30-61、app/SplitDB/ShardRouter.php:123-128、app/Models/Post.php:66、app/Models/Thread.php:58 | 属实。extern 路径 extern/{Y}/{Q}/{桶}/{id}.txt 用裸 ID:主题桶=id%32,回复桶=父帖桶(Post::insert 取父帖 bucket_path);当父帖桶==R%32 且同季度时,主题 R 与回复 R 写入同一文件路径,atomicRename 静默互相覆盖、delete 互相误删。当前小数据未命中属概率侥幸,规模化必现。 | P0 |
| H3 | app/Models/User.php:388-489 | 属实。$db->begin()/commit() 仅包裹 business 库,分片桶/main_index/ExternStorage 文件删除均在事务外(代码注释自认)。中途异常时跨库不一致且无补偿。属多文件 SQLite 架构固有短板。注:删除用户"他人主题下回复残留"部分已由 P1-2 批次修复(:440-468 兜底清理已存在)。 | P1 |
| H4 | app/Helpers/Search.php:313,315,321,334 | 属实且当前可触发。searchLIKE() 无条件绑定 ':since',但 SQL 仅 $since !== '' 时才含 :since 占位符;默认 timeRange='all' → $since=''。本工程 DBFactory.php:69 已强制 ATTR_EMULATE_PREPARES=false,原生预处理下多余命名参数抛 SQLSTATE[HY000] column index out of range → 降级路径自身 500。帖子与博客两条查询均受影响。 | P0 |
| M3 | app/Models/User.php:427-429 | 属实但不构成当前可利用漏洞。删除附件文件未做 Thread::hardDelete(:655)同款 preg_match('/^[a-zA-Z0-9_.\-]+$/') 白名单。但 attachments.filename 仅来源于 app/Helpers/Upload.php:82 的 bin2hex(random_bytes(16)).ext,无法含 ../。属防御一致性缺口。 | P2 |
| M4 | app/SplitDB/Schema.php:786-803 | 属实。bucketFromPath 归档桶文件缺失时静默 mkdir + ensureBucketTables 重建空表并返回连接,读取变"查不到=空",掩盖归档文件丢失。该问题豆包复核报告 L-04 已发现、至今未修,非本轮新增。 | P1 |
| M5 | app/Core/Model.php:104-119 | 属实但潜伏。基类 update() 仅做键名字符校验、无列白名单,属批量赋值面。当前未发现调用方把 $_POST 原样传入基类 update(),但任何新调用点直接传请求数组即可改 role/group_id/status/points。 | P2 |
| L1 | app/Core/Model.php:106-117 | 属实。$data 为空或全部键被过滤时 $sets='',拼出 UPDATE "t" SET WHERE ... 语法错误。豆包 P1-6 建议过、P1 批次实际未实施,现码仍无防护。 | P2 |
| L2 | app/Core/Model.php:189-200 | 属实。buildWhere 数组分支全非法键时 return '1'(全表匹配)。注:字符串分支已由 P2-① 改 fail-closed,数组分支是剩余的不一致点。 | P2 |
| L3 | app/Core/Model.php:133-143 | 属实。count() 传数组条件时 $conditions !== '' 成立但 is_string 为假 → 拼出 WHERE Array。当前无此类调用方,属死路径。 | P2 |
| L4 | app/SplitDB/IDGenerator.php:47-96 | 属实(可用性提示)。busy_timeout=5000ms × 最多 3 次重试,高写争用单请求最坏约 15s 阻塞;取号原子性本身正确。 | P2 |
| L5 | app/Helpers/PageCache.php:42-49,107 | 误报。报告称游客 lang 值可制造无界缓存文件,但 I18n::detect()(I18n.php:206-210)对 Cookie/URL/Session 全部要求 in_array($l, available())(仅已安装语言包),非法语言一律回落默认,缓存键有界,无法制造无界文件。 | 误报 |
| L6 | app/SplitDB/ExternStorage.php:69-81 | 误报(目标运行环境不成立)。目标为 PHP 8.2.9,rename() 可直接覆盖已存在目标,atomicRename 首分支必然成功,unlink+rename 兜底分支不会执行(该分支已由 P2-⑥ 统一抽离)。 | 误报 |
MiMo 单独提出(逐条核实)
| 编号 | 文件:行号 | 结论 | 裁决 |
|---|---|---|---|
| H-3 | app/Helpers/Plugin.php:112-121 | 属实。permission_enforce 默认 '0'(警告模式),未声明权限的插件仍放行 net:fetch/route:admin/system:settings。属安全模型默认策略偏松,非当前被利用漏洞。 | P2 |
| H-4 | plugins/mod_system/Plugin.php:377-380,405-408 | 属实。封禁/解封直接 UPDATE users SET group_id,绕过 User Model 旁路逻辑(缓存/审计)。当前无实际破坏(group_id 变更即生效),属架构一致性。 | P2 |
| H-5 | plugins/red_packet/Plugin.php:89-92 | 属实。创建红包直接 UPDATE users SET points = points - :p,绕过 Points::deduct() 的条件原子扣减(AND points >= :p)与 points_deduct_after 钩子。事务内先读后减存在窄竞态面。 | P2 |
| M-1 | 13 个插件直查核心库 users 表 | 属实。均为读操作 SELECT id, username FROM users WHERE id IN (...),安全风险低,属架构规范问题。 | P2 |
| M-2 | plugins/mod_system/AdminController.php:270-271 | 属实。原生 DELETE FROM blog_comments 绕过 Blog Model 级联。影响有限(博客评论无独立计数列需递减)。 | P2 |
| M-3 | plugins/seo/plugin.json:16 | 属实。activate() 调 Settings::update 写全局设置,但 permissions 仅 ["route:admin"] 缺 system:settings;仅 permission_enforce=1 时才受影响。 | P2 |
| M-4 | plugins/notifications/plugin.json:9 | 属实但当前无害。permissions 为空数组 [],但未注册 admin_route_register,现有钩子不受限。 | P2 |
| L-1 | plugins/seo/FrontController.php:33 | 误报(代码卫生)。未使用实例变量,无安全影响。 | 误报 |
| L-2 | 4 处 file_get_contents | 误报。均为本地固定路径读取,非网络请求,不涉及 SSRF;code_repo 已有 realpath 边界校验。 | 误报 |
| L-3 | app/Helpers/Search.php:313 | 并入 P0-2。即"搜索未走分片索引全文匹配",与 Qwen M1/H4 修复方向重叠,不单列。 | 并入 |
三、最终修复清单
必须修(P0)
P0-1 主题/回复 extern 命名空间隔离(H1 + H2 合并)
- [app/SplitDB/ExternStorage.php:48,156,166,182,270](H1 APCu 键串读)
- [app/SplitDB/ShardRouter.php:123-128](H2
.txt路径互相覆盖) - 问题:主题与回复共用裸 ID 的 APCu 缓存键与
.txt文件名,ID 区间重叠导致跨内容串读/互相覆盖/误删。APCu 未启用时休眠,启用或数据上量后必然触发正文损坏。 - 修复建议(必须具体):
ExternStorage::write/read/delete增加type参数('topic'|'reply'),APCu 键改为extern_t{id}/extern_r{id};所有调用方(Thread::insert/update/hardDelete、Post::insert/update/softDelete、User::deleteWithCascade、Thread::batchDelete/deleteRepliesOf)同步传入。ShardRouter::externRel()增加 type 参数,文件名改为t{id}.txt/r{id}.txt(目录可共用)。- 存量迁移:新增
cli/migrate_extern_namespace.php——遍历data/extern/**/*.txt,按 main_index 判定归属:id存在于topic_index且桶号==路径桶号 → 主题,重命名t{id}.txt;否则存在于reply_index→ 回复,重命名r{id}.txt;两者皆无则记日志待人工核对(防歧义误判)。迁移后删除旧文件。
P0-2 搜索降级路径修复(H4 + M1 合并)
- [app/Helpers/Search.php:312-321](M1 读退役表恒空)
- [app/Helpers/Search.php:313,315,321,334](H4 多余
:since崩溃) - 问题:倒排索引异常触发
searchLIKE()降级时,帖子查询读退役threads恒空;且默认timeRange='all'下多余:since绑定参数在ATTR_EMULATE_PREPARES=false下抛错,帖子与博客两条查询均 500,安全网完全失效。 - 修复建议(必须具体):
- 仅当
$since !== ''时才把':since'加入绑定数组(帖子 COUNT、博客 COUNT、帖子列表、博客列表 4 处同步改),或始终在 SQL 保留:since并用AND (:since = '' OR created_at >= :since)。 - 帖子分支去掉
FROM threads t:改读main_index.topic_index(title LIKE :q+status=0 AND deleted_at IS NULL+ category 白名单 + 时间过滤),列表行用Thread::getByIds()+Thread::fillExternContent()补正文/用户名/分类(与已修好的索引路径同口径);降级仅覆盖标题全文、正文匹配明确声明为降级能力(或复用 SearchIndexStore)。 - 移除
t.content LIKE与content_snippet对 threads 的依赖。
建议修(P1)
P1-1 删用户跨库非原子(H3)
- [app/Models/User.php:388-489]:
begin/commit仅包 business,桶/main_index/文件在事务外;中途失败产生跨库不一致且无补偿。 - 修复建议:异常路径把未完成清单(
$threadIds/$replyIds)写入error_log+ 审计表并抛"可重跑"提示;或改分阶段幂等收敛(先桶/索引/文件,最后删用户行,用户行删除失败时记待清理清单供后台重跑)。
P1-2 归档桶缺失静默建空表(M4)
- [app/SplitDB/Schema.php:786-803]:归档桶缺失静默重建空表,掩盖归档丢失,前台静默显示空帖。
- 修复建议:
bucketFromPath区分 active/archive——active 首次写入可建;archive 缺失抛RuntimeException或error_log计数告警,不静默返回空连接。
P1-3 附件清理误删在用附件(M2,双模型确认)
- [app/Helpers/Settings.php:613-617]:
threads/posts退役表扫不到分片正文内嵌图,后台清理可能误删仍在用的上传文件。 - 修复建议:删除
threads/posts两条查询;受保护文件名改从main_index.topic_index.excerpt_images(已落库的正文图片列)+blogs.content收集;如需覆盖回复正文图片,可在低频后台任务中遍历活跃桶reply.extern_path读取匹配(性能可接受)。
可以后置(P2)
- [app/Models/User.php:427-429](M3)附件文件删除补
basename()+ 与Thread::hardDelete同款preg_match('/^[a-zA-Z0-9_.\-]+$/')白名单。 - [app/Core/Model.php:104-119](M5)基类
update()增加$fillable列白名单,越界键忽略并记日志。 - [app/Core/Model.php:106-117](L1)
$sets === ''时返回 0 / 抛明确异常,避免非法 SQL。 - [app/Core/Model.php:198](L2)
buildWhere数组分支全非法键改抛异常(fail-closed),与字符串分支一致。 - [app/Core/Model.php:133-143](L3)
count()非字符串条件显式走buildWhere或抛类型错误。 - [app/SplitDB/IDGenerator.php:47-96](L4)适当下调
busy_timeout/重试次数,对取号超时埋点告警。 - [app/Helpers/Plugin.php:116](MiMo H-3)生产环境将
permission_enforce置 1,或在框架层对net:fetch/system:settings默认强制。 - [plugins/mod_system/Plugin.php:377-380,405-408](MiMo H-4)封禁/解封改走 User Model 统一封装(新增
User::ban()/unban())。 - [plugins/red_packet/Plugin.php:89-92](MiMo H-5)积分扣减改走
Points::deduct()/award()统一封装。 - 13 插件直查核心库 users 表(MiMo M-1)新增
User::batchGetNames()封装 + 请求级缓存。 - [plugins/mod_system/AdminController.php:270-271](MiMo M-2)博客评论删除改走 Blog Model 封装方法。
- [plugins/seo/plugin.json:16](MiMo M-3)permissions 补
"system:settings"。 - [plugins/notifications/plugin.json:9](MiMo M-4)如需后台入口补
"route:admin",否则保持空并在文档标注。
误报(忽略)
- [app/Helpers/PageCache.php:42-49,107](Qwen L5):
I18n::detect()已用in_array(available())将语言限定为已安装语言包,缓存键有界,无磁盘耗尽 DoS。 - [app/SplitDB/ExternStorage.php:69-81](Qwen L6):目标 PHP 8.2+
rename()直接覆盖,unlink+rename 兜底分支不会执行。 - [plugins/seo/FrontController.php:33](MiMo L-1):未使用变量,纯代码卫生。
- 4 处
file_get_contents(MiMo L-2):均为本地固定路径读取,非 SSRF。
四、与豆包/DS 已修复项的重叠核对
已对照 P0/P1/P2/P3 批次修复记录逐条排除,本轮清单不含任何已修复项:
- 附件下载权限(P0-1)、模板 RCE(P0-2/P0-3)、Dashboard 读退役表(P1-1)、删用户他帖回复残留(P1-2)、Category fallback(P1-3)、旧 extern 文件清理(P1-4)、User::search LIKE 转义(P1-5)——均已修复,两份报告均未重复提出。
- P2/P3 已修项(buildWhere 字符串分支、Search 索引路径 N+1、ViewCounter、DBFactory、Upload finfo、ExternStorage atomicRename 统一等)中,仅有两处"相邻但未完全覆盖"的残余:
buildWhere字符串分支已 fail-closed,但数组分支(L2)仍是残余不一致点;searchLIKE的 N+1 未在 P2-③ 范围内(该批只改了searchByIndex索引路径),故降级路径的 H4/M1 仍待修。- 豆包已发现但尚未修复(本轮予以确认并列入):Schema bucketFromPath 归档桶静默建空表(=Qwen M4,豆包 L-04)、Model::update 空集(=Qwen L1,豆包 P1-6)。
五、结论
- 双模型确认 2 条:搜索降级读退役表(M1/H-1)、附件清理读退役表(M2/H-2)——两份报告唯一实质重叠区,均属实。
- Qwen 独有且最有价值:H1/H2(主题/回复裸 ID 命名空间冲突,正文串读/覆盖,休眠但必现)、H4(
:since崩溃)为豆包/DS 未覆盖的新发现,是本次裁决最重要的产出。 - MiMo 独有项:全部为架构一致性/权限策略类(P2 级),无当前可利用漏洞;L-1/L-2 为误报。
- 误报共 4 条:L5、L6、L-1、L-2,均已给出反证。
- 优先级建议:先 P0-1(数据损坏风险,启用 APCu/扩容前必须落地 + 存量迁移)与 P0-2(搜索安全网),再 P1-1~P1-3,其余 P2 按批次排期。