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.
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 | 0 | 3 | 11 | 7 | 21 |
base/cloud |
module-base-cloud.md | 5 | 15 | 14 | 5 | 39 |
base/cms |
module-base-cms.md | 1 | 11 | 16 | 5 | 33 |
base/feedback |
module-base-feedback.md | 3 | 8 | 15 | 2 | 28 |
base/fts |
module-base-fts.md | 2 | 5 | 10 | 7 | 24 |
base/initial |
module-base-initial.md | 0 | 8 | 6 | 8 | 22 |
base/logs |
module-base-logs.md | 2 | 13 | 6 | 5 | 26 |
base/mgt |
module-base-mgt.md | 2 | 9 | 15 | 4 | 30 |
base/passport |
module-base-passport.md | 8 | 7 | 6 | 4 | 25 |
base/sender |
module-base-sender.md | 4 | 12 | 12 | 6 | 34 |
ec/address |
module-ec-address.md | 2 | 7 | 6 | 4 | 19 |
ec/mall |
module-ec-mall.md | 5 | 13 | 19 | 8 | 45 |
ec/market |
module-ec-market.md | 2 | 8 | 9 | 5 | 24 |
ec/order |
module-ec-order.md | 7 | 12 | 10 | 5 | 34 |
finance/wallet |
module-finance-wallet.md | 5 | 11 | 8 | 6 | 30 |
social/feed |
module-social-feed.md | 3 | 13 | 14 | 7 | 37 |
social/group |
module-social-group.md | 0 | 13 | 12 | 6 | 31 |
social/relation |
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);报告正文编号 1–31,其中 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、SDKJWT_SECRET_KEY、各模块SecretKey全部改为强制注入且启动期校验(拒绝CHANGE_ME*、Cblocksmesh2022C、4ef05311358cd1c8f787281f08b38b1c等任何仓库内出现过的值;长度校验之外增加熵/黑白名单校验)|验收:以仓库内任意历史占位符启动必须失败|涉及:pkgs/all/etc/default_dev.yaml:22、pkgs/ecmall/etc/default_dev.yaml:22、SDKcrypto/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.gopanic 修复并加 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()因 SDKselect{}永不执行的问题|涉及:全模块cmd/main - T16 测试补齐|优先覆盖认证、权限、越权、订单状态机、并发扣款/核销、优惠券、崩溃路径;修复当前失败/失效的既有测试(如
module/base/fts/internal/routers/register_test.go路由断言 FAIL、.http服务名大小写错误)|涉及:各模块test/ - T17 清理死代码与失效文档|移除零引用包(如
base/fts/internal/{response,errors}、各模块cmd/cliHello 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-1(P0):所有模块的独立部署入口没有可用的 HTTP 网关
证据链(主流程已逐个核对):
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。- 每个
service/expose.go把options.Gateway直接传给生成的pb.Register*HandlerServer(如module/base/sender/service/expose.go:30,33)。 - 生成的注册函数对 nil mux 无任何防御,第一行即
mux.Handle(...)(如module/base/sender/pb/sms.pb.gw.go:97-98)→ 传入 nil 立即 panic。 - 独立部署的
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-2(P1):infra.Response 为包级单例且被并发写
bsm-sdk/core/infra/response.go 的 Response 是包级变量、方法直接改字段,fts 与 logs 两个模块均独立报告了并发响应串包/数据竞争风险。属 SDK 级缺陷,需在 SDK 侧改为每次请求新建响应对象或加锁。
S-3(P0):两套不兼容的 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
(待回填)