测试与质量审计报告
审计日期:2026-07-11
范围:Go 后端 / Vue 前端 / Flutter 移动端 全栈
方法:工具实证(go test/vet/cover、vue-tsc、vitest、flutter analyze)+ 六维度并行代码审计 agent 交叉验证,所有发现均经代码路径确认
一、测试基础设施基线(实证)
| 工具 | 结果 | 说明 |
|---|---|---|
go test -short | ✅ 47 包全绿 | short 模式跳过真实网络用例 |
go vet ./... | ✅ 零告警 | |
go test -cover | 🟡 总 42.2% | 严重失衡,见下 |
vue-tsc --noEmit | ✅ 零错误 | |
vitest | ✅ 12 passed / 3 文件 | 覆盖极薄 |
flutter analyze | ✅ No issues |
覆盖率失衡(handler 层裸奔)
| 包 | 覆盖率 | 包 | 覆盖率 | |
|---|---|---|---|---|
| api/v1(所有 HTTP handler) | 4.5% | 🔴 | ws(WebSocket 广播) | 1.8% |
| app(装配/启动) | 3.1% | 🔴 | agent(RPC 文件管理) | 3.6% |
| db/oper | 9.7% | 🟠 | workflow/nodes | 13.2% |
测试投在「纯逻辑/中间件」(api 中间件 98.5%、event 95%、module 100%、keepseed 87.9%),而「HTTP handler + 并发」是真空地带——这与安全/契约 P1 全集中在 api/v1 高度吻合。
race detector 现状(重要)
- CI 已配置:ci.yml:19
go test ./... -race,ubuntu runner 默认CGO_ENABLED=1(自带 gcc),可正常运行 race detector。 - 但手动触发:ci.yml 触发条件是
workflow_dispatch,非push/pull_request,故不会随每次提交自动验证。 - 本地 Windows 不可用:项目
CGO_ENABLED=0(纯 Go SQLite 驱动),-race强制依赖 cgo,Windows 无 gcc → 本地开发无法跑 race。 - 建议:① 将 ci.yml 触发扩展为
on: [workflow_dispatch, pull_request]让 race 在 PR 自动跑;② race job 加-short跳过不稳的真实网络用例,聚焦并发逻辑。
二、六维度发现汇总
| 维度 | 评级 | P0 | P1 | P2 | P3 | 核心结论 |
|---|---|---|---|---|---|---|
| 并发与竞态 | B+ | 0 | 3 | 8 | 5 | mutex 纪律严明,3 个真实数据竞争潜伏 |
| 安全 | B+ | 0 | 3 | 4 | 7 | TMDB SSRF/插件签名/未签名安装 |
| 健壮性 | B | 0 | 7 | 18 | 3 | 抓取解析+HTTP超时零发现极扎实;chain 吞 error + 输入校验是短板 |
| 契约一致性 | B+ | 1 | 0 | 10 | 14 | Flutter 通知配置数据破坏 + Flutter 系统性滞后 Web |
| 测试质量 | C+ | — | — | — | — | 现有测试扎实但广度不足:前端3%/Flutter23%/后端65%无同级测试 |
三、本轮已修复(2026-07-11)
P0(功能必坏)
- Flutter 通知设置保存静默清空 DingTalk/Feishu/Email
- 根因:Flutter
NotifyConfig只定义 5 通道,_buildInput()漏发 3 通道 → 后端mergeNotifySettings用零值覆写。 - 修复:notification.dart 补全 3 通道模型(含
toJson)+ notification_settings_page.dart_buildInput()用 collection-if 回发 3 通道当前值。
- 根因:Flutter
P1(跨维度)
安全
- TMDB 越权:router.go
PUT /media/config加RequireAdmin()(原任何 VIP 用户可改 TMDB 域名指向攻击者服务器,泄露 API key + 内网扫描)。 - system/logs 非 admin:
GET/DELETE /system/logs加RequireAdmin()(原普通用户可读/清日志掩盖痕迹)。 - notif/test 非 admin:
POST /notification/test加RequireAdmin()。 - 插件签名信任模型(P1-S2/S3):market.go 新增
verifySignatureTrusted——用独立信任锚(内置官方公钥OfficialMarketPublicKey+ 用户配置plugin.trusted_public_keys)验证,不再用市场索引自带的idx.PublicKey(被验证数据本身,可篡改);未签名插件需plugin.allow_unsigned=true或MarketInstallOptions.AllowUnsigned显式 opt-in;新增GET/PUT /plugins/trust-config(RequireAdmin)管理配置。
并发(race detector 会标记,CI 可捕获)
Broadcaster.notice数据竞争:broadcaster.goSetNotice+detectDone改为经stateMu同步(消除 SetNotice 写与 Run goroutine 读的无同步访问)。- Webhook 脱离请求上下文:webhook.go 工作流执行 context 从
c.Request.Context()派生(原context.Background()10 分钟,客户端断开不取消)。
健壮性
- auth Encrypt 吞错:auth.go
issueForHxpt加密失败现中止登录(原落空串 → 定期 VIP 重验解密失败 → 用户静默掉 VIP + 脏数据)。 - 分页
limit无上限 DoS:notice/checkin_record/rss/workflow 4 处加上限封顶(1000/1000/500/500),checkin_record 补 offset 负数钳制。 - 公开端点无 body 限制:新增 body_limit.go
MaxBody中间件(64MB 兜底),挂载到 engine 全局链(防未认证POST /auth/hxpt/login塞 100MB JSON)。 - chain 层吞 DB error:workflow.go
Update/Delete在 DB 读失败时中止返回错误(防旧触发器残留致重复触发/ghost 触发器 panic);subscribe.go 去重链HitExists失败时跳过种子(宁可漏推不可重复下载),AddHit/MarkChecked/SetStatus改 slog 不静默。
回归验证
go build ./...exit 0;flutter analyze全量 No issues。- 全量
go test -short+go vet:唯一 FAIL 是TestExternalHTTPPluginAutoRestart(internal/plugins)——该测试启动真实子进程测自动重启时序,全包并行高负载下 39s 超时 fail,单独重跑 5.5s 绿,属预先存在的 flaky 时序测试,与本次改动无关(本次未改 plugins)。 - 建议给该 flaky 测试加
testing.Short()守卫或放宽内部 deadline,提升 race/负载下的鲁棒性。
四、未修与后续建议
建议单独评估的 P1(涉及信任模型或行为改变)
- P1 插件 zip 炸弹(market.go unzipSafe):
io.Copy无单条目/总解压上限。参照 ptd.go 的 16MB/条目、64MB 总量保护补上。
P2 节选
- 并发:groupchat 重试 timer 不可取消(卸载后仍发站消息)、Email.Send 取消时 goroutine 泄漏、RTSP 多处未同步写入、chain 所有内存缓存缺 singleflight(冷启动雪崩)、FlipWS 半开连接永久阻塞、Broadcaster.Run 用
context.Background()关闭不取消。 - 安全:
HXPT_EMERGENCY_ADMIN无密码后门、前端:href="javascript:"XSS(Rss/Freeleech/Reseed/Search 4 组件,建议加safeUrl()工具)、JWT+AES 密钥复用无 HKDF 隔离、SMTP 头注入。 - 健壮性:keepseed 默认值违反文档(保种不暂停)、assessment/medal
BatchUpsert吞错致数据不入库、多个 oper struct 缺max=binding 标签。 - 契约:Flutter 完全缺插件模块、缺 harem/assessment/messages 单站刷新、accept-host-key 两端无 UI(SSH host key 变更后永久报错)、RSS check 吞错误显示"新增0条"、harem
permanent_count/temporary_count未解析。
测试体系改进
- race detector 自动化:ci.yml 加
pull_request触发 + race job 加-short。 - 补测靶点(HIGH-RISK 未测 Top):plugins/external.go(993行)、market.go(784)、db/models.go(595)、chain/medal.go(580)、site/types.go(533)、api/v1 handler 层(覆盖率从 4.5% 起步)。
- 前端补测:
savept.ts(200 行正则启发式)、http.ts契约层、siteError.ts。
2026-07-12 落地:见文末「六、测试覆盖补强」——api/v1 4.5%→10.4%、site→71.4%、plugins→60.2%;前端 API 层补 40 例;并顺手补了关键
max=binding 硬化。db/models.go经核实为纯声明式(零方法零函数),无可测逻辑,审计按行数误判。
- 契约对齐自动化:考虑 OpenAPI 生成或三端契约快照测试,根治"Flutter 滞后 Web"。
五、值得肯定的防线
- 站点抓取解析(25 文件逐行验证):选择器
.Eq(N)前.Length()守卫、regexpFindStringSubmatch全检查长度、ParseUserInfoFields空提取不覆盖有效值——零 panic/nil 发现。 - HTTP 超时:site/entertainment/iyuu 全走受保护
httpclient.Client(30s 兜底 + SSRF 拨号守卫);media/cookiecloud 直用http.Client也都显式设 Timeout——零发现。 - mutex 规范:每个共享 map 都有专用锁,复制后上锁一致,无复制 mutex。
- SSRF/SQL注入/命令注入/路径穿越/JWT/CORS/CSWSH/Webhook HMAC/v-html 经多轮审计验证稳健。
六、测试覆盖补强(2026-07-12,测试覆盖专项)
落实第四节「测试体系改进 #2/#3」。六维度审计 agent 交叉测绘可测面后,以最低 DI 成本补齐裸奔的高价值靶点,并把审计新发现的
max=缺口顺手硬化。全量go test -short ./...+go vet ./...+vitest+vue-tsc全绿,零回归。
覆盖率变化(实证)
| 包 | 前 | 后 |
|---|---|---|
| api/v1(HTTP handler) | 4.5% | 10.4% |
| site | — | 71.4% |
| plugins | — | 60.2% |
| 前端 API 层 | 3% | 新增 40 例(http/savept/siteError) |
api/v1 的 nil 依赖早返回测试到 10.4% 即触顶——剩余 90% 的语句在调用 chain/DB 的深路径里,需 chain/DB 桩才能继续推(独立工程)。本次聚焦安全/契约/校验面(与 P1 集中区吻合)。
新增测试文件(9 个,275 Go 用例 + 40 前端用例)
HIGH-RISK 纯逻辑(零 DI)
- types_test.go:
parseRatioFloat、InviteStatus可购买算术、InviteeInfo.IsDead/IsPending/IsLowRatio/HasNoTrafficData穷尽表测(补全 i18n/边界;harem_test.go 原仅各 1 例)。 - external_test.go:ACL 核心
hasPluginPermission(*/host:*/plugin:*/X:*全分支)、replaceVars/restartDelay/resolveWorkDir/hostPathValue/cloneTimePtr、host 鉴权authorized、writeHostJSON/decodeHostJSON、RuntimeStatus/DefaultConfig/hostContext、pluginOutputWriter行切分(含 4000 截断)。
api/v1 handler 层
- helpers_test.go:16 个未导出纯 helper 表测——
clampDays、classifyCommandRisk(安全关键高/中/低分级)、parseLimit、splitCSV、maskSecret、mediaImageReferer、uniqueTargetIDs、commandRunSource/Status、atoiDefault/parseUintSlice、normalizedURLHost/Key、definitionScore、mergeStrings、parseMailbox、boolStr、pluginAssetScope。 - binding_test.go(扩展):
rssReq/wfReq/webhookReq全字段max=越界拒 + 9 个 required 结构体必填校验 +TestMaxHardenedBounds回归硬化。 - handlers_earlyreturn_test.go:nil/最小依赖构造 handler 命中早返回——
SystemHandler(nil update)、Subscribe.Create手动必填、Workflow/Webhook.Create坏 JSON、CommandRun.Create4 道门(空命令/空 targets/>50/高危缺确认)、Media.Recommendnil 优雅、Auth.HxptCaptcha503。 - handlers_httptest_test.go:全 gin 分派 e2e 校验——
Dashboard.History坏 id、Downloader.Add坏 id/缺 URL、Media.Search/Discover缺 keyword、Notification.Test缺 Title、KeepSeed.Find缺 Name。
前端
- savept.test.ts:状态元信息/MP 状态/站庆文案/公告 4 分区/
tokenizeNoticeText站点名高亮分词。 - siteError.test.ts:抓取错误友好化全分支。
- http.test.ts:JWT 注入 + 错误文案映射 + 401/403-VIP 清 token(adapter 注伪响应,stub location 规避 jsdom 导航)。
顺手硬化:关键 max= binding(审计「缺 max=」缺口)
给高危请求结构体补 max=/dive,max=(参照 subReq/rssReq 金标准选宽松上界,仅拒异常超长输入;[]string 用 dive,max= 限单元素,不限个数以免误伤批量操作,body_limit 中间件 64MB 兜底总量):
mediaConfigReq(5 个 secret)、notifyConfigReq(~20 嵌套字段)、hashesReq+ downloader_ops 全部Hashes/Tags/Names/URLs(dive,max=)、addReq(URL/Category/Tags/SavePath)、hxptLoginReq/hxptCookieLoginReq、configReq.IYUUToken、settingReq.SavePath、commandRunRequest(Command/Source/Note)、execRequest.Command/powerRequest.Action、pluginMarketSourcesReq.Sources/pluginTrustConfigReq.TrustedPublicKeys/pluginAction/Event/Bridge/Install各串字段。
binding_test.go::TestMaxHardenedBounds断言超长必拒、合法通过。其余低危结构体(entertainment 数值类、plugin map 类)记入 backlog,留给「binding 校验 max=」全量工程。
关键发现
db/models.go(595 行)无任何方法/函数,纯声明式 struct——审计「按行数排高危」误判,无逻辑可测,编译器已校验 tag。HIGH-RISK Top 应以「可测逻辑密度」而非行数排序。hasPluginPermission实际行为:*/host:*/plugin:*在 switch 直命中→对任意 required 全授权(超级授权);其余X:*走后缀前缀匹配仅授 X 类别。测试据此断言。parseRatioFloat的逗号是千分位剥离("1,000.5"→1000.5),非欧洲小数点("1,5"→15)。