Files
full/docs/audit/README.md
yanweidong 63aeedc2fe docs: add per-module audit reports (18 modules)
Add static security/quality audit reports for all 18 Go service modules under module/, plus a consolidated index (docs/audit/README.md) with per-module statistics, top risks, cross-module systemic defects and a phased TODO list (T1-T19). No production code is modified.
2026-09-14 22:16:27 +08:00

165 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# module/ 全模块审计索引
> 状态:生成中(各模块审计文件完成后由主流程回填统计表与汇总 TODO
## 审计范围
- 审计对象:`module/` 下全部 **18 个含 `go.mod` 的独立服务模块**`go.work` 成员)。
- 每个模块由独立审计 agent 完成,产出 `docs/audit/module-<路径>.md`
- 未纳入审计(无 Go 代码,不属于运行时服务):
- `module/ec/supply`:仅 1 个 `README.md`,无 `go.mod`
- `module/finance/bill`:空目录。
- `module/finance/loan`:空目录。
## 审计维度
安全(鉴权/越权/注入/密钥/隐私)、正确性(错误处理/事务/并发/状态机/精度、性能N+1/索引/分页/缓存)、可维护性(重复/分层/TODO/空实现)、健壮性与可观测性(超时/重试/迁移耦合/日志/配置)、测试覆盖。
## 问题级别定义
| 级别 | 含义 |
| --- | --- |
| P0 | 可被利用的安全漏洞 / 必然导致数据损坏或资金损失 |
| P1 | 高概率线上故障、越权、性能瓶颈 |
| P2 | 可维护性、健壮性、可观测性缺陷 |
| P3 | 风格、文档、一致性改进 |
## 模块审计文件
| 模块 | 审计文件 | P0 | P1 | P2 | P3 | 合计 |
| --- | --- | ---: | ---: | ---: | ---: | ---: |
| `base/ads` | [module-base-ads.md](module-base-ads.md) | 0 | 3 | 11 | 7 | 21 |
| `base/cloud` | [module-base-cloud.md](module-base-cloud.md) | 5 | 15 | 14 | 5 | 39 |
| `base/cms` | [module-base-cms.md](module-base-cms.md) | 1 | 11 | 16 | 5 | 33 |
| `base/feedback` | [module-base-feedback.md](module-base-feedback.md) | 3 | 8 | 15 | 2 | 28 |
| `base/fts` | [module-base-fts.md](module-base-fts.md) | 2 | 5 | 10 | 7 | 24 |
| `base/initial` | [module-base-initial.md](module-base-initial.md) | 0 | 8 | 6 | 8 | 22 |
| `base/logs` | [module-base-logs.md](module-base-logs.md) | 2 | 13 | 6 | 5 | 26 |
| `base/mgt` | [module-base-mgt.md](module-base-mgt.md) | 2 | 9 | 15 | 4 | 30 |
| `base/passport` | [module-base-passport.md](module-base-passport.md) | 8 | 7 | 6 | 4 | 25 |
| `base/sender` | [module-base-sender.md](module-base-sender.md) | 4 | 12 | 12 | 6 | 34 |
| `ec/address` | [module-ec-address.md](module-ec-address.md) | 2 | 7 | 6 | 4 | 19 |
| `ec/mall` | [module-ec-mall.md](module-ec-mall.md) | 5 | 13 | 19 | 8 | 45 |
| `ec/market` | [module-ec-market.md](module-ec-market.md) | 2 | 8 | 9 | 5 | 24 |
| `ec/order` | [module-ec-order.md](module-ec-order.md) | 7 | 12 | 10 | 5 | 34 |
| `finance/wallet` | [module-finance-wallet.md](module-finance-wallet.md) | 5 | 11 | 8 | 6 | 30 |
| `social/feed` | [module-social-feed.md](module-social-feed.md) | 3 | 13 | 14 | 7 | 37 |
| `social/group` | [module-social-group.md](module-social-group.md) | 0 | 13 | 12 | 6 | 31 |
| `social/relation` | [module-social-relation.md](module-social-relation.md) | 1 | 15 | 14 | 4 | 34 |
| **合计** | 18 个模块 | **52** | **183** | **205** | **102** | **542** |
> 计数说明:`base/cloud` 的 P0 为 agent 原始上报,主流程复核后认为其中 P0-1/P0-2/P0-3 应下调(见下「主流程复核记录」);`social/feed` 经 agent 自查将 JWT 密钥一项由 P0 下调为 P1理由生产是否注入 `BSM_JwtSecretKey` 无法从仓库确认)。按上述修正,全仓 **P0 = 48**52 3 1其余级别不变。
> `base/mgt` 计数以报告第 6 节为准P0=2/P1=9/P2=15/P3=4报告正文编号 131其中 P2-17 为主流程通知后修正机制的一项。
### 各模块最高风险速览
| 模块 | 最高风险(摘要,详见各模块报告) |
| --- | --- |
| `base/passport` | `Forget.Reset` 无鉴权改他人密码并写裸 MD5登录/注册接口不校验 `code`JWT 密钥缺省为公开常量 `Cblocksmesh2022C` → 零成本接管任意账号 |
| `base/sender` | 短信/邮件接口无鉴权、日限额计数器只有读没有写、验证码可无限爆破 → 短信轰炸/费用损失/账号接管 |
| `ec/order` | 订单/购物车/优惠券定位与写入信任客户端入参且无归属校验 → 读他人收货信息、任意订单置为已支付、核销任意券;`confirm.go` 必然 panic |
| `finance/wallet` | 支付回调不入账 + `Payment.Callback` 由客户端决定"支付成功" + 支付宝渠道 `Body` 未初始化必然 panic |
| `ec/mall` | 仓库内公开的 32 字节占位 JWT 密钥可直接用于签发 HS256 token叠加"角色/店铺归属全采信 claims、写操作无租户隔离" → 接管任意店铺 |
| `base/mgt` | root 默认口令 `123456` 自动播种 + JWT 密钥默认值 + 50 个管理接口仅粗粒度 `RequireAdmin` → 接管最高权限 |
| `base/cloud` | 照片表无 `passport_id`、照片接口靠 `album_id` 反查(**已复核为非越权**);分享口令明文存储与比较、加解密密钥完全由客户端决定 |
| `social/feed` | 动态写操作无属主校验 + 可见性控制被默认值击穿 → 读写删全站动态 |
| `social/relation` | 命名返回指针未初始化必然 panic无 recover→ 远程打挂进程;好友/关注/标签列名系统性错配 → 静默假成功 |
| `base/logs` | IP 校验逻辑反向 + `X-Forwarded-For` 可伪造 + 路由全匿名 → 伪造审计日志、读取全量日志 |
| `base/cms` | 写接口仅校验"任意有效 JWT"无归属校验;`Site` 5 个接口空实现却返回成功 |
| `ec/address` | `Get/Modify/Delete``owner_identity` 校验 → 枚举读改删全站收货人 PII`Fetch` 裸 return 恒返回 nil |
| `base/feedback` | `Get/Delete/Remark` 无鉴权无归属校验;`Modify` 覆盖 `identity` → 工单失联、子表脱钩 |
| `base/fts` | 客户端可控 `bucket` 直接拼入落盘路径 → `..` 穿越任意目录写MinIO 分支可写任意桶 |
| `base/initial` | `Config` 把可控 `os` 拼进 `ORDER BY` → SQL 注入6 个字典接口无鉴权无限流 |
| `base/ads` | 独立入口网关整体 404唯一接口无鉴权无限流且 `pos_key` 无索引全表扫描 |
| `social/group` | 13 个 RPC 中 12 个确定性缺陷(表名/列名错、结果被丢弃、权限与 SQL 全错);`Disband` 是返回 OK 的模板 TODO |
| `ec/market` | 管理接口只验签名不验角色/归属 → 自审通过、删他人代理商、拉全量身份证;代理商登录签发的 token 不是 JWT |
## 汇总 TODO跨模块优先事项
按"先止血、再治本"排序。每条对应各模块报告中的具体 TODO 项,验收标准见对应文件。
### 阶段一P0 止血(上线前必须完成)
- [ ] **T1 统一密钥治理**|动作:`Authorization.Key`、SDK `JWT_SECRET_KEY`、各模块 `SecretKey` 全部改为强制注入且启动期校验(拒绝 `CHANGE_ME*``Cblocksmesh2022C``4ef05311358cd1c8f787281f08b38b1c` 等任何仓库内出现过的值;长度校验之外增加熵/黑白名单校验)|验收:以仓库内任意历史占位符启动必须失败|涉及:`pkgs/all/etc/default_dev.yaml:22``pkgs/ecmall/etc/default_dev.yaml:22`、SDK `crypto/token/jwt.go:34``module/base/mgt/internal/logic/pub/login.go:90``module/ec/mall/internal/config/config.go:41`
- [ ] **T2 账号接管链路封堵**|动作:`Forget.Reset` 接入真实验证码校验与身份校验;`Login.Code/Quick``Register.Code` 真正校验 `code`;删除 MD5 写入路径;为 `passport_account``account`/`phone` 唯一索引|验收:无 `code` 无法登录、无法改他人密码、库中不再产生 MD5 口令|涉及:`module/base/passport/internal/logic/forget/reset.go:31``login/code.go:17``login/quick.go:20``register/code.go:13``internal/logic/common/token.go`
- [ ] **T3 资金与交易完整性**|动作:支付回调真正入账并加签名验签与幂等;`Payment.Callback` 禁止客户端置状态;`AliPay.Body/ctx` 初始化;订单/优惠券/购物车全部补服务端归属校验;`confirm.go` panic 修复并加 recovery 拦截器|验收:伪造回调不能改状态、并发下单不超卖、越权订单操作返回 403涉及`module/finance/wallet/internal/logic/payment/callback.go:25``alipay/alipay.go:15,53``module/ec/order/internal/logic/**`
- [ ] **T4 越权与无鉴权接口收敛**|动作:按各模块报告清单补齐 `ParseMetaCtx` + 归属过滤 + 角色校验;独立入口禁止裸奔(补鉴权或直接不暴露)|验收:以 A 账号 token 访问 B 账号资源全部 403/404涉及`base/feedback``method/get.go:19`)、`base/logs``routers/register.go:23` 注释掉的 JwtAuth`ec/address``library/get.go:29`)、`ec/market``agency/approve.go:15`)、`ec/mall``base/cms``social/feed``base/sender``base/initial`
- [ ] **T5 远程 panic / DoS 修复**|动作:修所有 nil 解引用与无保护类型断言,并为全部 gRPC server 装配 recovery 拦截器|验收:`go test -run` 与手工用例下不再 panic进程不退出涉及`social/relation``friend/tag_fetch.go:22``friend/apply_get.go:32`)、`finance/wallet``alipay.go:53`)、`ec/order``confirm.go:62`)、`base/logs``log/fetch.go:29,45`)、`ec/address``library/fetch.go:16-40`
- [ ] **T6 上传路径穿越**|动作:对 `bucket`/文件名做白名单与 `filepath.Clean` + 前缀校验或改用服务端映射表MinIO 桶名同样校验|验收:`bucket=../../x``%2e%2e%2f` 均被拒|涉及:`module/base/fts/internal/logic/handler.go:27``provider.go:26,99`
- [ ] **T7 SQL 注入点修复**|动作:`Config``os` 改为参数化或白名单枚举排序|验收:注入 payload 不再进入 SQL涉及`module/base/initial/internal/logic/check/config_cache.go:21`
### 阶段二P1 治本
- [ ] **T8 修复 S-1 网关模板缺陷**|动作:`internal/server/new.go` 初始化 `gwRuntime.NewServeMux()` 并注册 handler`cmd/main` 改用 `service.Expose`;或明确关闭 `Gateway.Enable` 并同步文档|验收:独立二进制下文档中的 REST 路由可达|涉及:全部 18 个模块的 `internal/server/new.go``service/expose.go``cmd/main/main.go`
- [ ] **T9 修复 S-3 token 签发不一致**|动作:`ec/market``ec/mall` 的登录改用与聚合网关一致的 HS256 JWT 签发验收B 端登录 token 可通过 `pkgs/ecmall` 网关校验|涉及:`module/ec/market/internal/logic/agency/login.go:53``module/ec/mall/internal/logic/staff/login.go:88`
- [ ] **T10 数据完整性**|动作:补事务包围多表写入、去掉 `Select()+Updates(结构体)` 的零值覆盖、修正被改写的主键、补唯一索引与幂等|验收:并发与重复提交下无脏数据|涉及:`base/mgt``user/modify.go:64``permission/modify.go:140`)、`base/feedback``method/modify.go:37,89`)、`ec/order``base/cloud``album/delete_album.go:45-54`)、`ec/address`(默认地址)
- [ ] **T11 明文凭据与 PII 治理**|动作:轮换仓库/`etc`/`.builds` 中出现的所有口令与密钥PII 加密存储 + 日志脱敏 + 关闭生产 gorm Debug验收仓库与构建产物中扫描不到真实凭据日志无明文 PII涉及`module/base/feedback/etc/feedback_prod.yaml:4``.builds/etc/feedback_prod.yaml:4``scripts/build-all-linux.sh:100``module/base/cloud`(分享口令)、`module/finance/wallet`(银行卡/身份证)、`module/ec/market`(证件资料)
- [ ] **T12 未实现接口显式化**|动作:所有模板桩与空实现改为返回 `codes.Unimplemented` 或补齐实现,禁止"假成功"|验收:调用未实现接口返回明确错误而非 OK涉及`base/cms``site/*`)、`social/group``Disband`)、`ec/market``data/*`)、`base/sender``TencentSender`)、`finance/wallet`(转账/放款)
- [ ] **T13 限流与审计日志**|动作:登录/短信/上传/写接口加限流;管理端高危操作留审计痕迹;短信/邮件日限额计数器落写入侧|验收:超阈值请求被限流并有记录|涉及:`base/sender``base/mgt``base/logs`
- [ ] **T14 启动与迁移解耦**|动作:拆分连接/迁移/seed关闭生产 `AutoMigrate``Debug`,补真实迁移脚本(仓库当前无 `.sql`)|验收:只读实例与蓝绿发布可用|涉及:全模块 `internal/impl``internal/models`
### 阶段三P2/P3 收尾
- [ ] **T15 可观测性与优雅退出**|补 `/health`、结构化日志、`context.WithTimeout`、可用的大盘指标;修 `defer srv.Stop()` 因 SDK `select{}` 永不执行的问题|涉及:全模块 `cmd/main`
- [ ] **T16 测试补齐**|优先覆盖认证、权限、越权、订单状态机、并发扣款/核销、优惠券、崩溃路径;修复当前失败/失效的既有测试(如 `module/base/fts/internal/routers/register_test.go` 路由断言 FAIL、`.http` 服务名大小写错误)|涉及:各模块 `test/`
- [ ] **T17 清理死代码与失效文档**|移除零引用包(如 `base/fts/internal/{response,errors}`、各模块 `cmd/cli` Hello World、`grpcConns``MemorySerice` 等),修正 README/wiki 与实现不符的描述(缓存策略、`/health`、Makefile、Dockerfile 等)|涉及:全仓
- [ ] **T18 配置一致性**`prod``dev` 配置不得字节级相同;修正 `Service:` 名与模块不符(如 `ec/address` 的 etc 写 `Service: order`);统一命名与拼写(`MemorySerice`)|涉及:各模块 `etc/*.yaml`
- [ ] **T19 补充 scope 说明**`module/ec/supply`(仅 README`module/finance/bill``module/finance/loan`(空目录)、`social/relation/service`(空目录、缺 `expose.go`)需明确归属或补齐|验收:目录不再有"空壳"歧义
## 主流程复核记录
审计 agent 的结论并非全部可信,主流程对高危结论逐一回读源码复核,修正如下(未单独说明的 P0 均已被源码证据直接证实):
| 模块 | agent 结论 | 复核结果 |
| --- | --- | --- |
| `base/passport` | `Forget.Reset` 无鉴权改他人密码并写裸 MD5三个验证码登录接口不校验 `code` | **成立**`internal/logic/forget/reset.go:18-31`(仅校验非空后按 `identity` 直改 `password`,且写入 `utils.Md5``internal/logic/login/code.go:17-21`(只判空即签发 token |
| `base/initial` | `Config` 接口把调用方可控 `os` 拼进 `ORDER BY` | **成立**`internal/logic/check/config_cache.go:21` 字符串拼接进 `Order()`GORM 无法参数化该位置 |
| `base/mgt` | 初始化种子内置 root/123456 | **成立**`internal/models/init_db.go:15-16``:145` 仅打印"请尽快修改默认密码" |
| `social/relation` | `TagFetch` 命名返回指针未初始化导致必然 panic | **成立**`internal/logic/friend/tag_fetch.go:15,22` 对未初始化的 `*pb.FriendTagsReply` 直接 `Count(&reply.Total)`,且 `internal/server/new.go:23``grpc.NewServer()` 无 recover 拦截器 → 可远程打挂进程。同源问题见 `ec/address/library/fetch.go:16-40`(裸 `return` 恒返回 nil |
| `base/feedback` | Get/Delete/Remark 无鉴权无归属校验Modify 覆盖 identity | **成立**`internal/logic/method/get.go:19-29` 全文无 `ParseMetaCtx`,查询仅 `Where("identity = ?")``internal/logic/method/modify.go:37` 写入新 `utils.UUID()``Identity``:89``Updates(record)` → 业务主键被改写、子表 `item_identity` 脱钩 |
| `base/cms` | 写接口仅校验"任意有效 JWT",无归属校验 | **成立**`internal/logic/post/delete.go:29` 显式传空 author 过滤并留 `// Todo:``internal/logic/site/create.go:20-27` 为模板桩却返回 `vars.OK`(假成功) |
| `social/feed` | 动态写操作无属主校验 | **成立**`internal/logic/post/remove.go:17-25``ParseMetaCtx` 后即 `models.DeletePost(in.Identity)`,无属主过滤;`delete_comment.go:17-24` 同构 |
| `social/group` | `Disband` 为返回 OK 的模板 TODO`Modify` 会随机重写群 `identity` | **成立**`internal/logic/basic/disband.go:26``// TODO: add your logic code` 且函数返回成功;`internal/logic/basic/modify.go:33` 写入随机 `utils.UUID()``Identity``:35``UpdateColumns(record)` 全列写入(含 `identity` 与零值 `PassportID`/`MemberTotal` |
| `ec/mall` | 公开占位 JWT 密钥可直接签发 HS256 token | **成立**`pkgs/all/etc/default_dev.yaml:22``pkgs/ecmall/etc/default_dev.yaml:22``CHANGE_ME_32_BYTE_JWT_SECRET_KEY` 恰为 **32 字节**,通过 SDK `crypto/token/jwt.go:34-36` 的长度校验,并被 `pkgs/all/internal/server/authorization.go:39,78` 直接用作 HS256 签名密钥 → 可离线伪造管理员 token两个聚合入口均受影响 |
| `base/cloud` | P0-1/2/3照片无 `passport_id` 导致跨用户照片读写删与相册级联删除越权 | **高估,需下调**`internal/models/cloud_photo.go` 确实没有 `passport_id`,但照片级接口通过 `JOIN cloud_albums ... WHERE cloud_albums.passport_id = ?` 强制归属校验(`album/delete_photo.go:31-32``album/get_photo.go:31``album/update_photo.go:32``album/list_photos.go:36,44``album/set_cover_photo.go:32` 校验相册归属、`:39` 校验照片属于该相册;`album/delete_album.go:31``passport_id` 限定待删相册。**不构成越权**。其中两个子问题独立成立并保留:`delete_album.go:45-54` 两次删除无事务(非原子),以及 `CloudPhoto``passport_id` 属反规范化脆弱设计(可降级为 P1/P2 |
## 跨模块系统性缺陷(主流程独立确认)
以下问题不是单个模块的偶发缺陷,而是模板/生成器级别的共性,建议在汇总层单独立项修复,而不是在 18 个模块里各修一遍。
### S-1P0所有模块的独立部署入口没有可用的 HTTP 网关
证据链(主流程已逐个核对):
1. `internal/server/new.go` 声明了 `Mux *gwRuntime.ServeMux` 字段(如 `module/base/sender/internal/server/new.go:16`),但 `New()` 只注册 gRPC server`:33-34`**从不调用 `gwRuntime.NewServeMux()``Mux` 恒为 nil**;全仓库 `gwRuntime.NewServeMux()` 仅出现在 `pkgs/all/internal/server/server.go:38``pkgs/ecmall/internal/server/server.go:38`
2. 每个 `service/expose.go``options.Gateway` 直接传给生成的 `pb.Register*HandlerServer`(如 `module/base/sender/service/expose.go:30,33`)。
3. 生成的注册函数对 nil mux 无任何防御,第一行即 `mux.Handle(...)`(如 `module/base/sender/pb/sms.pb.gw.go:97-98`)→ 传入 nil 立即 panic。
4. 独立部署的 `cmd/main/main.go` 只调用 `server.New(nil)` 并自行 `net.Listen`**从不调用 `service.Expose`**,因此独立二进制下网关要么整体 404、要么在接入 `Expose` 时启动即 panic。
影响:`ads``initial``address``cms``cloud``ftS` 等多个模块的审计都独立报告了「独立部署 HTTP 网关 404 / Mux 为 nil」根因是同一处模板缺陷。README 宣称的独立部署 REST 入口在该形态下不可用。
### S-2P1`infra.Response` 为包级单例且被并发写
`bsm-sdk/core/infra/response.go``Response` 是包级变量、方法直接改字段,`fts``logs` 两个模块均独立报告了并发响应串包/数据竞争风险。属 SDK 级缺陷,需在 SDK 侧改为每次请求新建响应对象或加锁。
### S-3P0两套不兼容的 token 签发方式B 端登录 token 被聚合网关拒绝
聚合入口的鉴权中间件**只接受 HS256 JWT**`pkgs/all/internal/server/authorization.go:73-79``pkgs/ecmall/internal/server/authorization.go:73-79` 使用 `jwt.ParseWithClaims` 并强制 `WithValidMethods(["HS256"])`
而各模块签发 token 的路径分成两类:
| 模块 | 位置 | 实际产物 | 与聚合网关兼容 |
| --- | --- | --- | --- |
| passport登录/注册全部 4 处) | `internal/logic/common/token.go:10``token.New(...).GenerateJwt(...)` | 真正 JWT函数名有误导性 | ✅ |
| mgt | `internal/logic/pub/login.go:90``refresh.go:28``GenerateJwt` | 真正 JWT | ✅ |
| **ec/market** | `internal/logic/agency/login.go:53``encipher.GenerateTokenAes` | **AES-CBC + base64不是 JWT** | ❌ |
| **ec/mall** | `internal/logic/staff/login.go:88``encipher.GenerateTokenAes` | **AES-CBC + base64不是 JWT** | ❌ |
证据:`bsm-sdk/core/crypto/encipher/encipher.go``GenerateTokenAes` 最终调用 `AesEncryptCBC` + `base64.StdEncoding`,输出形状为密文 base64无法被 `jwt.ParseWithClaims` 解析。
影响market 代理商与 mall 员工登录成功后拿到的 token **无法通过聚合入口网关校验**B 端登录链路在该拓扑下不可用P0 可用性问题,非安全漏洞)。`market` 的审计已独立发现该点P0-2`mall` 的审计需一并核对。
## 汇总 TODO
(待回填)