24 KiB
总体审计报告(2026-09-22)
本报告是产品级汇总,汇总逐微服务审计结论。逐服务明细见
docs/(21 篇)。
一、审计范围与方法
| 项 | 内容 |
|---|---|
| 审计对象 | BSM Infra Full 后端 Go workspace,go 1.27.1 |
| 覆盖模块 | module/ 18 个业务服务 + pkgs/all、pkgs/ecmall 2 个聚合入口 + scripts 与工作区配置 |
| 代码量 | module/ 903 个 .go(177 个 .http、66 个 .proto、58 个 .yaml)、pkgs/ 54 个 .go |
| 方法 | 只读静态分析:逐文件读 cmd/ internal/ service/ proto/ etc/,生成代码 pb/ 仅看接口签名 |
| 输出 | 每服务一份 docs/<服务名>.md;本文与《产品与服务目录》为产品级汇总 |
审计约定:结论均带 文件:行号;无法确认处以【信息不足】标注;修复建议只给最小、直接动作,不含过度设计/过度封装方案。
二、结论摘要
架构组织清晰(服务域划分、service.Expose 注入、聚合入口只做注册表取舍),但实现质量与完整性存在系统性缺口:本仓库当前状态更接近"可编译的接口骨架",而非可上线的生产系统。
- 认证与鉴权:至少 5 个模块的验证码校验形同虚设(passport、mall);多处接口只校验"是否登录"而不校验"是否有权操作该资源"(address、cms、group、relation、feed、mall、market、feedback、order);
- 资金链路:wallet 支付回调不验签、提现不扣减、余额扣减读后写,order 模拟支付/发货生产可达且优惠券计算写反,存在直接资金损失路径;
- 数据正确性:多处引用模型中不存在的列名(
relation_id/creator_id/createor_id/enabled等)与错误键名,导致接口恒失败或静默返回空; - 上线阻断:聚合入口无生产配置、JWT/session 密钥默认值可直接启动、首批部署不建表不播种、
scripts/api-docgen无法构建; - 测试:全仓仅 21 个
*_test.go,base 多数模块、wallet、social 三服务零单测,上述缺陷几乎无自动化拦截。
三、严重问题台账
P0 · 严重(认证绕过 / 资金损失 / 数据越权 / 上线即故障)
| 编号 | 服务 | 问题 | 证据 | 影响 |
|---|---|---|---|---|
| S1 | passport | 验证码登录/快捷登录/注册/找回均不校验验证码 | login/code.go:16-59、login/quick.go:20-64、register/code.go、forget/verify.go |
任意手机号 + 任意 code 登录/批量建号,账号体系失守 |
| S2 | passport | 找回密码仅凭 identity 重置,且写未加盐 MD5 |
forget/reset.go:17-41,31 |
任意账号接管;重置后密码无法通过 bcrypt 登录 |
| S3 | mgt | AppKey 登录明文相等比对密码列 |
pub/login.go:147-159 |
pass-the-hash,哈希泄露即可登录 |
| S4 | mgt | dev 配置注入默认管理员 root/123456 |
etc/mgt_dev.yaml:16、internal/models/init_db.go:16 |
默认弱口令后台 |
| S5 | wallet | 支付回调不验签,用调用方传入状态置成功 | internal/logic/payment/callback.go:16-31 |
客户端自证支付成功;渠道回调 WxCallback 反为 // Todo 不落库(wechat/wx_callback.go:38) |
| S6 | wallet | 提现只校验余额不扣减、无幂等键 | internal/logic/basic/apply_cash.go:35-49 |
余额可重复开单超额提现;ChargeWallet 全仓无调用点→充值链路不通 |
| S7 | wallet | 余额扣减为读后写、WHERE identity=? 无条件更新 |
internal/logic/basic/wallet.go:53-63 |
并发双花 |
| S8 | order | 模拟支付/发货/收货生产可达且硬编码支付参数 | logic/summary/simulate_pay.go:25、proto/summary.proto |
直接伪造支付并推进订单状态 |
| S9 | order | OrderApprove 完全无鉴权 |
logic/mgt/order_approve.go:17 |
匿名审批退款并改写库存 |
| S10 | order | 优惠券金额做加法加入订单总额 | logic/summary/confirm.go:72 |
用户应付变高;归属/有效期/门槛不校验 |
| S11 | order | 扣库存不检查 RowsAffected |
logic/summary/submit.go:209-217 |
超卖 |
| S12 | mall | 员工验证码登录不校验验证码 | internal/logic/staff/login.go:58 |
任意手机号接管店铺账号 |
| S13 | mall | 店铺支付/邮件凭据任意登录用户可读改 | logic/store/get_payment.go、set_payment.go、get_email.go、set_email.go、get_setting.go |
渠道凭据外泄/被篡改 |
| S14 | market | 审核无鉴权、开号/删号鉴权被整段注释 | agency/approve.go:15-46、agency/create.go:20-23、agency/delete.go:17-21 |
自助开号 + 自助审核 + 匿名封停他人 |
| S15 | market | 登录令牌用 AES-CBC、鉴权用 HS256 JWT,互不兼容 | agency/login.go:53 vs ParseMetaCtx;SDK crypto/token/jwt.go:68 |
合法 token 过不了任何接口;解析失败后访问 nil Claims → panic 可打崩进程 |
| S16 | initial | os 参数字符串拼进 ORDER BY |
check/config_cache.go:21 |
SQL 注入 |
| S17 | initial | 版本判断用字符串不相等即"有更新" | check/updates.go:39 |
客户端版本高于服务端记录时被强制降级 |
| S18 | group | 入群审批无角色校验(丢弃调用者身份) | member/join_do_handle.go:19,24-52 |
任意登录用户可拉人入群/拒绝申请 |
| S19 | group | Modify 重写群 identity 且 UpdateColumns 不过滤零值 |
basic/modify.go:33-35 |
群成员/申请立即脱钩,"群消失" |
| S20 | relation | 通过好友申请时不与申请记录比对 | friend/apply_do_pass.go:24-47 |
单方面把任意陌生人加为好友 |
| S21 | cms | 文章/评论 Delete/Modify 不校验归属 |
post/delete.go:29、modify_comment.go:33、delete_comment.go:24 |
任意登录用户删改他人内容 |
| S22 | address | Get/Modify/Delete 无 owner 约束 |
library/get.go:19,29、modify.go:39、delete.go:19,24 |
遍历自增 id 获取他人姓名/手机号/详细住址并可改删 |
| S23 | cloud | 空间 KeyIdentifier 全局唯一 + 写入字面量 "default" |
models/cloud_space.go:12、space/get.go:29-50 |
首名用户之后所有人拿不到空间 |
| S24 | cloud | 私人资料"加密"名不副实(key 由客户端传入、服务端不加密) | logic/private/encrypt_data.go、create_private_data.go:54 |
服务端明文存"加密"数据 |
| S25 | ads / feedback / logs | 三个模块业务接口全挂匿名组 | logs/internal/routers/register.go:21-27、ads、feedback |
可伪造写入、全量读取 |
| S26 | logs | fetch 键名不一致 + level 类型断言双 panic,分页参数失效,create IP 过滤写反 |
logic/log/fetch.go:42,45,33-53、create.go:21 |
接口 500 / 全表返回 / 防护失效 |
| S27 | feed | 动态越权删改 + 私密动态泄露 | post/remove.go:25、change.go:40、models/query.go:30-37,60-71 |
删除他人动态;is_open=false 内容仍被返回 |
| S28 | pkgs/all · ecmall | JWT 密钥默认值恰为 32 字节,直接通过长度校验 | etc/default_dev.yaml:22、internal/config/config.go:66-69 |
可自行签发任意身份 token |
| S29 | pkgs/all · ecmall | grpc-gateway 404 与业务 NotFound 无法区分 | internal/server/server.go:63-71 |
所有"记录不存在"分支的真实响应被 Gin 纯文本 404 覆盖 |
| S30 | sender | 腾讯云短信空实现 return nil,nil |
internal/logic/sms/send.go:151-153 |
接口返回 200 + null,调用方误判成功 |
P1 · 高
| 编号 | 服务 | 问题 | 证据 |
|---|---|---|---|
| H1 | sender | 日发送量限制与短信黑名单从未生效(只有读、无写入点) | sms/send.go:37,42-51 |
| H2 | sender | 验证码 SetNX 忽略返回值却发码;Verify 成功不删码可重放;用 math/rand |
sms/send.go:62-65,81,169、verify.go:28-32 |
| H3 | passport | 密码哈希用 bcrypt.MinCost(cost=4);Token 缓存键 /TOKEN/ 与 /token/ 不一致 |
register/do.go:30、account/set_password.go:35、vars/token.go:3 vs register/do.go:84 |
| H4 | cloud | 容量配额只统计不限制;分享密码明文;CreateShare 不校验 resource_id 归属;根目录 *ParentID 空指针 panic |
space/*、logic/share/*、get_dir.go:91、get_dir_tree.go:97 |
| H5 | cms | 评论发布 authName["name"].(string) panic;赞踩无去重可刷负;Search 无分页上限 |
add_comment.go:35-40、query.go:346、incr_post_like.go:38 |
| H6 | address | Fetch 命名返回值未赋值恒返回空;默认地址唯一性无事务保障 |
library/fetch.go:23-40、create.go:41-45 |
| H7 | market | Offset 在归一化前计算、Limit(0) 致待审核列表恒空;三处 Count 结果被丢弃 |
agency/fetch.go:19,27-32、pending.go:31 |
| H8 | group | creator_id/createor_id 列不存在→核心接口恒返回 ErrDB、免验证入群永久失效 |
modify.go:35、do_quit.go:24、do_set_manager.go:25、models/query.go:39-43 |
| H9 | relation | TagFetch 必然 panic;Fetch/ApplyFetch 返回 (nil,nil);7 个接口引用不存在的 relation_id 列;Undo/DoIgnore 无归属删除 |
tag_fetch.go:22、friend/fetch.go:47-49、delete.go:27 等、follow/undo.go:18,26、match/do_ignore.go:19,27 |
| H10 | feed | 分页写成 Limit(int(page)) 且未回写;DeleteComment 是空函数;action_op 误写 action_type |
models/query.go:67,122-125,50 |
| H11 | wallet | 支付宝分支 AliPay.Body 为 nil 直接调用→必 panic;改支付密码不校验旧密码;微信 APIv3 密钥/商户号明文入库;wallet_prod.yaml 字段名不匹配无法启动 |
alipay/alipay.go:46-53、basic/set_pay_password.go:23-29、etc/wallet_prod.yaml:56 |
| H12 | order | 跨服务直连同库(mall_product/mall_product_spec/mall_store/address_library) |
8 个文件,散见 logic/summary/* |
| H13 | mall | 12 处写操作无店铺归属校验(商品/规格/图片/分类/广告/公告/运费/员工) | product/item_modify.go:29、spec_delete.go:28 等 |
| H14 | initial | areas_cache.go 查询模型不存在的列且 DB 错误被覆盖→静默空列表并缓存 30 分钟 |
areas_cache.go:33-35 |
| H15 | pkgs/all · ecmall | 无 prod 配置(BSM_RuntimeMode=prod 时 log.Fatalf);build-all-linux.sh 不覆盖聚合入口 |
pkgs/all/etc/ 仅 default_dev.yaml;build-all-linux.sh:7,30 |
| H16 | pkgs/ecmall | 聚合路径不建表不播种,Mgt.InitRootUser 在聚合下不生效 |
service/*.go 无 NewImpl/AutoMigrate/models.New 调用 |
| H17 | mock/order | OrderModify 占位但返回成功;social 三服务未接入聚合且 Mux 恒为 nil |
order/logic/mgt/order_modify.go;social/*/internal/server/new.go |
| H18 | scripts | scripts/api-docgen 当前无法构建,且与工作区依赖版本长期漂移 6 处 |
scripts/api-docgen/generate.sh:5-7;实测 GOWORK=off go build ./... 报 updates to go.mod needed |
| H19 | workspace | go.work 与 20 处 go.mod 把 SDK 指向仓库外相对路径且 go.sum 无校验和 |
go.work:26、各 go.mod replace |
| H20 | scripts | verify-workspace.* 的 gofmt 门禁漏 scripts/、对空模块列表静默成功;缺 .gitattributes 致 CRLF 文件使门禁失败 |
verify-workspace.ps1/sh;实测 gofmt -l 报 module/base/fts/internal/routers/register_test.go |
P2 · 中
- feedback / market / order / mall 等多处列表接口无分页上限(可全表扫描/拖库):
feedback各方法、mgt/internal/tool/tool.go:5-14、mall、cms等。 - 占位实现仍返回成功:group
Basic.Disband(disband.go:26-31)、orderOrderModify、feed 4 类 Timeline +Tag.PostList+Post.CommentList+Setting.Info/Rights(9 个占位 RPC)、wallet 7 个占位 RPC、mall 入驻/授权/小程序码/运费、marketData全 5 文件、cmsSite全系列、cloud 部分——统计见《产品与服务目录》。 - 云解析/错误吞没:passport
account/get.go:44未处理 error;relationtag_member_fetch;groupcreateor_id.Scan错误丢弃。 - GORM 链式未回写
tx/First+Group语义不确定:logs、feedback 等。 - 调试残留:
logs/routers/register.go:20fmt.Println、feed 多处fmt.Print。 - 鉴权口径不一致:模块 yaml 匿名白名单、聚合
Authorization.Anonymous白名单与模块实际声明三者互不吻合(ads/feedback/passport 均出现)。
P3 · 低
- 死代码与未用初始化:ads
RedisService/MemorySerice零引用;logsRedisService/EtcdService未用、Dependencies.Cache未应用;relationfollow.Do死代码。 internal/excode/ex.go为空文件;多处 crate 级常量与配置注释不一致。test/grpc/main.go硬编码明文 JWT。
四、按服务域的问题分布
flowchart LR
A["认证与账号<br/>passport · mgt · mall.staff"] --> A1["验证码不校验<br/>默认密码<br/>pass_hash"]
B["资金与订单<br/>wallet · order"] --> B1["回调不验签<br/>提现不扣减<br/>优惠券算反<br/>超卖"]
C["资源越权<br/>address · cms · group · relation · feed · mall · market · feedback"] --> C1["只校验登录<br/>不校验归属/角色"]
D["聚合与上线<br/>pkgs/all · pkgs/ecmall · scripts"] --> D1["密钥默认值<br/>404 分流<br/>无 prod 配置<br/>docgen 不可构建"]
五、共性根因
- 鉴权只到"认证"不到"授权":大量写接口只判断调用者已登录,缺少"该资源是否属于调用者 / 调用者是否为管理员"的条件。越权类问题占本次 P0 的近一半,且分布在 9 个模块,属同一模式。
- 验证码体系形同虚设:passport、mall 的登录/注册路径根本不调用验证码比对;sender 的码生成(
SetNX忽略返回值)、校验(成功不删码)、限额(无写入点)三处同时失效。即使调用方正确接入,也无法形成有效校验。 - 资金链路缺三要素:无验签、无事务、无幂等。
wallet与order各自独立缺失,且模拟支付接口在生产可达。 - 配置与代码长期漂移:yaml 字段名与
SrvConfig不匹配(wallet/feed 需log.Fatalf/panic才能启动)、匿名白名单三处口径不一致、占位实现返回成功、生产配置缺失。这些在"能编译/能 dev 跑"阶段不会暴露。 - 列名/键名错误被吞没:模型与实际表列不一致(
relation_id/creator_id/createor_id/enabled/show_town/sort_order),叠加.Scan错误被丢弃、命名返回值reply未赋值,形成"接口 200 但返回空/报错被掩盖"的静默故障。 - 测试覆盖极低:全仓 21 个
*_test.go集中在fts/mgt/sender/mall/market/order/pkgs;base多数模块、wallet、social三服务零单测。上述缺陷几乎没有任何自动化拦截。
六、修复优先级路线(务实)
按"止血 → 阻断上线 → 修恒失败 → 补护栏"推进,全部为对现有函数/配置的最小改动:
- 资金与认证止血(最高优先)
- wallet:回调按渠道验签、金额与服务端记录核对、加幂等键;提现改为事务内扣减/冻结;余额扣减改条件更新(
WHERE balance >= ?)。 - passport / mall:登录、注册、找回路径补回验证码比对;
Forget.Reset必须校验一次性令牌。 - mgt:移除默认管理员密码,改必填环境变量;
AppKey登录改为哈希比对。 - order:
Simulate*与OrderApprove加鉴权与生产开关(默认关闭);优惠券改为减法并校验归属/门槛/有效期;扣库存检查RowsAffected。
- wallet:回调按渠道验签、金额与服务端记录核对、加幂等键;提现改为事务内扣减/冻结;余额扣减改条件更新(
- 越权批量化修复
- 统一给"按主键操作"的写接口补条件:address/cms/group/relation/feed/mall/market/feedback 分别补
passport_id/owner/店铺归属/群管理员角色。
- 统一给"按主键操作"的写接口补条件:address/cms/group/relation/feed/mall/market/feedback 分别补
- 上线阻断项
- pkgs:修复 404 分流判定(先读业务响应再回退);JWT/session 密钥去除可用默认值;补 prod 配置;聚合路径补建表/播种。
- scripts:修复
api-docgen构建、纳入工作区或固定版本、gofmt 门禁补齐,SDK 改为可解析的版本依赖或明确文档化本地前提。
- 恒失败接口修复
- 修正不存在的列名/键名(group/relation/initial/feed/logs),修复命名返回值
reply未赋值(address/relation),补齐main.go的 HTTP Mux 与聚合注册(social)。
- 修正不存在的列名/键名(group/relation/initial/feed/logs),修复命名返回值
- 补护栏
- 优先为认证、权限、支付、订单状态机、库存、验证码写单测;CI 增加 workspace 验证、凭据扫描、proto 一致性与依赖漏洞扫描。
七、正向结论
- 密码哈希:market、mall 的账号创建/登录/改密已从 MD5 加盐迁移到 bcrypt(历史 MD5 需通过重置流程迁移)。
- mgt 鉴权:管理路由已正确挂载
RequireAdmin→IsSuperAdmin(按 DB 回查超级管理员角色),模块内未见绕过;其短信重置密码实现正确校验 Redis 验证码,与 passport 形成对比。 - cloud 数据隔离:所有 DB 查询均为 GORM 参数化(无 SQL 拼接/注入),接口以
passport_id = auth.ID隔离,未发现跨用户越权读取他人目录/相册/笔记/书签/私人资料。 - pkgs 动态 RPC:具备 4 MiB 请求体上限、头部白名单透传、方法描述符缓存;两个聚合入口有针对鉴权/动态 RPC/配置/路由的单元测试。
- 2026-08-10 审计的整改:绝对路径
replace、样例固定 secret 已处理(本次替换为仓库外相对路径,可移植性问题转为 P1)。
八、与 2026-08-10 审计报告的关系
wiki/audit-2026-08-10.md 聚焦"workspace 结构、样例凭据、进程与端口防护",其列出的 P0/P1 多数已整改。本报告在其基础上下沉到业务实现层,发现的 P0 数量与严重度显著高于前次——原因是前次未逐接口审计业务逻辑。两份报告不冲突,应合并为同一整改清单跟踪。
九、整改结果(2026-09-22)
按第六节的优先级路线,已对全部 18 个业务服务 + 2 个聚合入口 + 工作区/工程脚本执行整改,覆盖各服务报告中标注为「高」的全部条目(132 项,含本报告 P0/P1 台账)。逐服务的「处理结果」见 docs/<服务名>.md 的「整改记录」章节。
整改原则
| 约束 | 执行情况 |
|---|---|
| 不做过度设计/过度封装 | 未引入新框架、抽象层、DTO/VO、事件总线、消息队列;未拆分服务边界;未引入 DDD/CQRS |
| 加密统一 bcrypt | 口令类摘要全部为 bcrypt(DefaultCost):passport、mgt、mall、market、wallet 支付密码、wallet 改密校验旧密码。验证码等短时效一次性凭证仍按原有 Redis 明文比对链路存储(用 bcrypt 会破坏跨模块链路) |
| 注释中文化 | 新增/修改的注释、配置注释、脚本提示均为中文 |
| 契约冻结 | 未修改任何 proto/*.proto 与生成的 pb/*.go(本机无 protoc);少量需新增字段才能完整实现的项目做了安全降级,已在对应报告中标注 |
关键整改落点
| 域 | 结果 |
|---|---|
| 资金链路 | wallet:回调改渠道验签+金额核对+幂等、充值入账接通、余额与提现改条件更新+事务+流水、支付密码改 bcrypt 且须校验旧密码、渠道凭据不再明文入库、配置字段名修正。order:模拟接口加 dev 开关与鉴权、OrderApprove 补鉴权与状态校验、优惠券改减法、扣库存原子更新防超卖、取消订单回补库存、store_id/单价取值修正、五类越权补归属校验 |
| 认证与会话 | passport:四条路径接入真实验证码校验、找回密码改一次性票据、Token 键前缀统一、密码哈希 bcrypt。mgt:AppKey 登录改 bcrypt 比对、移除 root/123456 弱口令。mall:员工验证码登录补校验 |
| 越权批量化 | address、cms、group、relation、feed、mall、market、feedback 共 8 个模块按「主键操作必带归属条件」补齐;涉及改删他人地址/内容/动态/群成员、单方面加好友、无鉴权审核开号等 |
| 上线阻断 | pkgs/all、pkgs/ecmall:补 prod/test 配置、构建脚本覆盖聚合入口、拒绝公开默认 JWT/session 密钥、修复 gateway 404 与业务 NotFound 的分流;聚合启动补建表/播种。scripts:api-docgen 恢复可构建并纳入门禁、依赖版本对齐、verify-workspace.* 空列表改为报错、新增 .gitattributes 保证 LF 检出 |
| 恒失败接口 | group、relation、initial、feed、logs 的列名/键名/占位问题修正;address 命名返回值修复;social 三服务的 gateway 接线与聚合注册补齐 |
| 静默故障 | sender 腾讯云短信空实现改为显式报错、日限额与黑名单真正生效、验证码 SetNX 与重放问题修正;logs 的 IP 过滤方向、分页、panic 修正 |
校验
全部 21 个 module(18 业务服务 + pkgs/all + pkgs/ecmall + scripts/api-docgen)均通过 GOWORK=off go build ./...、GOWORK=off go vet ./...、gofmt -l;pkgs/all、pkgs/ecmall、module/base/fts 的既有单测通过(fts 新增的路径穿越防护用例由失败转通过)。module/base/sender 的 test/grpc/*_test.go 带 integration 构建标签、需要真实服务,不在 go test ./... 范围内执行。
说明:在仓库根以 workspace 模式编译会报 genproto 拆包的
ambiguous import,属本机既有的依赖解析问题;各模块内以GOWORK=off模块模式编译正常,本轮即以此校验。
部署前置(本次整改新增/强化的环境变量)
以下变量未提供时服务将拒绝启动或明确报错,不再回退到仓库中的公开样例值:
| 环境变量 | 用途 | 未配置后果 |
|---|---|---|
BSM_JwtSecretKey |
聚合入口 JWT 签名密钥(16/24/32 字节,须为私有值) | pkgs/all、pkgs/ecmall 启动即 log.Fatalln 并给出中文提示;公开样例值同样被拒 |
BSM_SECRET_KEY |
聚合入口 session cookie 的 HMAC 密钥 | 启动即拒绝(空值或 CHANGE_ME 均不可用) |
BSM_MGT_SECRET_KEY |
聚合内 mgt 子配置的密钥 | prod/test 配置以 ${BSM_MGT_SECRET_KEY} 占位,需部署时注入 |
BSM_CloudPrivateKey |
cloud 私人资料内容的 AES-GCM 加解密密钥(≥16 字节) |
私人资料写入/读取返回内部错误,不再以公开默认密钥「假加密」 |
BSM_MGT_ROOT_PASSWORD |
mgt 初始超级管理员口令 |
不再创建 123456;未设置时 root 口令为空、不可登录(日志提示) |
BSM_DB_DSN、BSM_MINIO_*、BSM_ALIPAY_*、BSM_JUMIO_* 等 |
数据库、对象存储、支付与 KYC 渠道凭据 | 各模块 etc/*.yaml 已统一改为 ${...} 占位,不再硬编码真实凭据 |
仓库中原有的真实库口令(
Weidong1214)、弱口令(123456)与公开 JWT 样例密钥(Cblocksmesh2022C)已从module/、pkgs/、scripts/全量清除。
仍未处理
- 各报告中「中」「低」级别项:分页上限、死代码/死配置、README 与实现不符、reflection 与 TLS、XFF 可信性、登录限流、CI 接入、单测覆盖等。
- 需新增契约字段才能完整实现的项目:feed/cms 的互动去重(缺「谁点过」记录表)、mall 许可码校验(缺数据源)、部分角色的细粒度权限判定。
- 历史数据迁移:库中此前的 MD5 / 明文口令记录无法被 bcrypt 校验,需通过「重置密码」流程迁移(整改后重置将写入 bcrypt)。
- 全仓单测覆盖仍然薄弱,第六节第 5 条「补护栏」尚未执行。