Files
full/docs/pkgs-ecmall.md
2026-09-22 21:15:34 +08:00

32 KiB
Raw Permalink Blame History

pkgs/ecmall电商聚合宿主代码审计报告

内容
审计对象 pkgs/ecmallmodule 路径 bsm/full/pkgs/ecmall,电商聚合宿主)
所属域 聚合运行入口(电商交易 4 + 资金支付 1 + 平台运营 6 + 基础支撑 3共 14 个服务)
审计日期 2026-09-22
代码规模 Go 文件 27 个 / 1395 行(其中 _test.go 5 个 / 317 行);etc/default_dev.yaml 151 行;go.mod 146 行;另有 AGENT.md 776 行
监听端口 gRPC 0.0.0.0:12300、HTTP 0.0.0.0:12301pkgs/ecmall/etc/default_dev.yaml:3-8
结论摘要 pkgs/all 是同一实现的另一份拷贝(internal/server 4 个文件逐字节相同),差异仅在注册表(少 cloud)与端口;多了一层注册表测试护栏service_test.go)、并用 AGENT.md §4.5 主动记录了匿名口径差异,是本次两个聚合入口里工程质量更好的一份。但共性问题同样存在:无 prod 配置与构建覆盖、两个密钥默认值可启动且不校验、gateway 404 与业务 NotFound 不可区分、reflection 无开关且匿名可枚举、无 TLS、进程级 XFF 可伪造、/files 下载地址无路由。此外聚合模式不建表不播种Mgt.InitRootUser: true 在聚合下不生效),注册表清单在 3 处各存一份需手工同步。

1. 定位与职责

pkgs/ecmall 是面向电商业务的聚合宿主:在单进程内注册 14 个模块的 gRPC/gateway/REST 入口共享一份数据库、Redis、etcd 与内存缓存,监听独立的 12300/12301,因此可与 pkgs/all12000/12001)同时运行。

它同样不承载业务逻辑:internal/service/*.go 每个文件只有 14-22 行,只做 ExposeOptions 组装(pkgs/ecmall/internal/service/ads.go:9-20)。

pkgs/all 的差异(已用 diff 全量核对):

维度 pkgs/all pkgs/ecmall 证据
注册表 15 项(含 cloud 14 项(无 cloud pkgs/all/internal/service/service.go:14-33 vs pkgs/ecmall/internal/service/service.go:14-32
端口 12000 / 12001 12300 / 12301 两侧 etc/default_dev.yaml:3-8
Fts.Local.Site http://127.0.0.1:12001/files http://127.0.0.1:12301/files pkgs/all/etc/default_dev.yaml:73 vs pkgs/ecmall/etc/default_dev.yaml:79
internal/server/* —— 完全相同(仅 server.go:79-80 的日志字面量 all/ecmall 不同) diff 结果
internal/config/config.go —— 仅多一段顶部注释(config.go:20-25 diff 结果
internal/impl/impl.go —— 仅 import 路径不同 diff 结果
注册表测试 internal/service/service_test.go62 行) 实测 go test ./pkgs/all/... 输出 internal/service [no test files]
配置一致性断言 只校验非空 额外断言 Services == ecmallServices 常量 internal/config/config_test.go:15,43-52
Cloud 配置节 cloud 无专项配置) 两侧 SrvConfig 均无 Cloud 字段

internal/config/config.go:20-25 的注释明确了这个取舍「Modules that are not part of ecmall keep their own configuration in their own process」。

2. 代码结构与关键文件

路径 行数 职责
cmd/main/main.go 58 进程入口:--workspaceconfig.Newimpl.NewImplserver.Newservice.Expose → 信号 → srv.Start
internal/config/config.go 130 配置结构 + 监听地址规范化 + 密钥校验 + 共享配置回填 + Enabled
internal/config/config_test.go 53 dev 配置非空、端口不同、密钥长度、5 个专项节存在,且 Services 必须逐项等于 ecmallServices
internal/server/server.go 133 gRPC serverunary 拦截器 + reflection、gateway mux、gin engine、动态路由、404 兜底、*http.Server 参数、Stop
internal/server/authorization.go 126 HS256 JWT 校验、HTTP 中间件、gRPC unary 拦截器、匿名白名单、/rpc/... 路径规范化
internal/server/dynamic.go 194 动态 RPCreflection 取 descriptor + dynamicpb + protojson + 内网 Invoke
internal/server/response.go 78 统一错误体与 gRPC code → errcode 映射
internal/impl/impl.go 42 进程级共享资源Memory/Redis/DB/Etcd
internal/service/service.go 45 注册表14 项)+ session 中间件 + 逐个 Expose
internal/service/service_test.go 62 注册表护栏:断言 11 个模块前缀的 gRPC 服务 + 6 条 REST 路由确实注册成功
internal/service/{ads,cms,feedback,fts,initial,logs,mgt,passport,sender,address,mall,market,order,wallet}.go 14-22 各模块注入(Dependencies ± GRPC/Gateway/Engine/Config
etc/default_dev.yaml 151 唯一一份配置
AGENT.md 776 ecmall 开发上下文§3.5 记录聚合不建表不播种、§4.5 记录匿名口径差异、§5 服务注册表)
go.mod 146 14 个 module replace 到 ../../module/...bsm-sdk/core../../../../bsm-sdk/core

注册表(internal/service/service.go:14-32ads, cms, feedback, fts, initial, logs, mgt, passport, sender, address, mall, market, order, wallet,与 etc/default_dev.yaml:50-64Services 逐项一致(已核对,并被 config_test.go:43-52 强制)。

3. 启动流程与请求分流

启动顺序(cmd/main/main.go:22-57--workspace 校验 → config.New(读 ecmall/etc/default_<mode>.yaml、归一化地址、校验两地址不同与密钥、写入 env.JwtSecretKey/coreVars.JwtExpire、回填共享配置)→ impl.NewImpl()Memory/Redis/DB/Etcdecmallserver.NewgRPC server + reflection + gin engineservice.Exposesession 中间件 + 注册表逐个注册,失败即 panicsrv.StartSIGINT/SIGTERMsrv.Stop

分流实现与 pkgs/all 完全一致(两个文件逐字节相同),因此下文只标注 ecmall 侧行号:

flowchart TD
    C["客户端请求"] --> A["httpMiddlewarecanonicalHTTPPath + isAnonymous<br/>authorization.go:55-66"]
    A -->|"匿名 或 JWT 有效"| G["grpc-gateway mux ServeHTTP写入 bufferedResponse<br/>server.go:63-65"]
    A -->|"鉴权失败"| E1["HTTP 200 + {code,message,details,timeseq}<br/>authorization.go:122-126"]
    G -->|"status != 404"| F1["flush 缓冲响应<br/>server.go:66-68"]
    G -->|"status == 404路由未命中 或 业务 NotFound"| H["gin ServeHTTP 兜底<br/>server.go:70"]
    H --> R1["POST /rpc/:module/:service/:method<br/>server.go:61"]
    R1 --> D["resolveMethodgRPC reflection 取 descriptor带缓存<br/>dynamic.go:115-176"]
    D --> D1["streaming → Unimplemented<br/>dynamic.go:79-82"]
    D --> D2["protojson 解 JSON≤4 MiB→ 内网 gRPC Invokeinsecure<br/>dynamic.go:84-112"]
    H --> R2["/rest/fts/... /rest/logs/... /rest/mgt/...(模块自注册)<br/>service/{fts,logs,mgt}.go"]
    H -->|"无路由"| N["gin 默认 404纯文本"]
    D2 --> E2["内网 gRPC 再经 unary 拦截器校验 JWT<br/>authorization.go:42-53"]
入口 形态 ecmall 覆盖范围 鉴权节点
原生 gRPC /{pkg}.{Svc}/{M} 11 个 gRPC 模块fts/logs/mgt 无 gRPC 面) gRPC unary 拦截器(server.go:32
grpc-gateway POST /{pkg}.{Svc}/{M} 同 11 个模块 HTTP 中间件 + 内网 unary 拦截器
动态 RPC POST /rpc/{pkg}/{Svc}/{M} 同 11 个模块,仅 unary HTTP 中间件(按网关形态匹配白名单)+ 内网 unary 拦截器
原生 REST /rest/{fts,logs,mgt}/... 仅这 3 个模块 HTTP 中间件 + 模块内层 JwtAuth/rest/fts/uploader/rest/mgt/{user,app,role,pmn,dpt}/*

4. 配置与安全

ecmall 现状 证据
监听地址 gRPC 12300 / HTTP 12301BindIP 0.0.0.0;两地址相同则 panic etc/default_dev.yaml:3-8config.go:66-68
端口缺省 Port 为空时随机 1024-65535SDK 行为),两地址必然不同 SDK conf/new.go:87-97config.go:91-95
JWT HS256裸 JWT 放 Authorization 头(无 Bearer),校验 exp/iat/now-iat ≤ Expire authorization.go:68-94authorization_test.go:36-41
JWT 密钥 Authorization.Key 长度 ∈ {16,24,32},写入全局 env.JwtSecretKey 供模块签发共用 config.go:72-80
默认密钥 CHANGE_ME_32_BYTE_JWT_SECRET_KEY32 字节,直接通过校验) etc/default_dev.yaml:22config.go:72-75
session 密钥 config.Spec.SecretKeyconf.Base)拼 "-session",配置值 CHANGE_ME无校验 internal/service/service.go:35etc/default_dev.yaml:17config.go:69-78 未校验
匿名白名单 19 条11 条网关形态 + 8 条 REST 路径),/grpc.reflection./grpc.health. 前缀硬编码放行 etc/default_dev.yaml:24-43authorization.go:96-103
gRPC reflection 无条件注册、无开关、匿名可达 server.go:33authorization.go:98-100
TLS 无:net.Listen("tcp", ...) + insecure.NewCredentials()grep -i "tls|certificate|x509" pkgs 无命中 server.go:45,49dynamic.go:45
HTTP server 参数 ReadHeaderTimeout=10sIdleTimeout=120sMaxHeaderBytes=1MiB;无 ReadTimeout/WriteTimeout server.go:72-78
gRPC server 参数 无 MaxRecvMsgSize、无 keepalive、无 StreamInterceptor server.go:32
客户端 IP gin.New()SetTrustedProxiesgin v1.12.0 默认信任全网段) server.go:34gin gin.go:225,214
限流 grep -i "ratelimit|limiter|throttle" pkgs 无命中
运行模式 gin 未按 BSM_RuntimeMode 设置 server.go:34
CORS 未启用(独立 fts/mgt 启用SDK 实现为 AllowAllOrigins: true server.go:34module/base/fts/cmd/main/main.go:26module/base/mgt/cmd/main/main.go:39
建表/播种 聚合不执行:applyDependencies 只覆盖包变量(module/base/mgt/service/dependencies.go:19-32),不含 models.New/AutoMigrate/InitRootUserData(唯一调用链是 module/base/mgt/internal/impl/with.go:20 ← 模块自己的 impl.NewImpl() grep -rn "NewImpl()|AutoMigrate|models.New(" module/*/*/service/*.go 无命中

匿名白名单逐条核对11 条 gRPC 条目全部对应真实方法(module/base/passport/proto/{login,register,forget,verify}.protomodule/ec/mall/proto/staff.proto:9module/ec/market/proto/agency.proto:98 条 REST 条目全部对应已注册路由(module/base/fts/internal/routers/register.go:22-23module/base/logs/internal/routers/register.go:24module/base/mgt/internal/routers/register.go:31-36)——无死条目,问题在"该放行的没放行"(见 5.1)。

AGENT.md 与代码的一致性:AGENT.md:193-216 的《匿名白名单现状》表与 etc/default_dev.yaml:24-43 逐条相符,并主动列出"模块声明匿名但聚合不放行"的 4 组接口ads.Fetch/ByPos、sender 三个方法、feedback 四个方法、logs 三个 REST文档是诚实的,但没有任何代码或测试强制两者一致(见 5.3)。

5. 审计发现

5.1 安全

级别 位置 问题
etc/default_dev.yaml:22 + internal/config/config.go:72-75 JWT 签名密钥默认值是可直接使用的公开字面量CHANGE_ME_32_BYTE_JWT_SECRET_KEY 恰好 32 字节 → 通过长度校验 → 写入 env.NewEnv().JwtSecretKeyconfig.go:79)。authorization.go:74-79 只校验 HS256 签名与 iat/exp,因此知道该字面量即可自行签发任意身份的 token模块侧签发也用同一密钥module/base/mgt/internal/logic/pub/login.go:90module/base/passport/internal/logic/common/token.go:10),无法单侧规避。
etc/default_dev.yaml:17 + internal/service/service.go:35 session 密钥来自未校验的 SecretKeycookie.NewStore([]byte(config.Spec.SecretKey+"-session")),配置值 CHANGE_MEconfig.New 只校验 Authorization.Keyconfig.go:69-78),对 SecretKey 从不校验。默认配置下 mysession cookie 的 HMAC 密钥可猜(当前唯一使用点是 mgt 的 SessionDemo 演示路由)。
pkgs/ecmall/etc/(仅 1 个文件) 不存在生产配置BSM_RuntimeMode=prod 时读 default_prod.yaml,缺失即回退 workspace_default_prod.yamlSDK conf/new.go:35-43),两者都不存在 → log.Fatalfnew.go:49-52)。即聚合入口只能以 dev 模式启动;scripts/build-all-linux.sh 又只扫描 module/:7,30),聚合既无生产配置也不在构建范围内。
internal/server/server.go:33 + authorization.go:96-100 reflection 无开关且匿名可达:匿名可枚举 11 个模块的全部 gRPC 服务/方法与完整消息结构(动态 RPC 自身就依赖它,dynamic.go:124-143)。
internal/server/authorization.go:98 /grpc.health. 硬编码匿名,但没有任何模块注册 health 服务(grep -rn "health" module 只命中注释与字符串)→ 当前是死名单,将来补 health 服务会先天匿名。
internal/server/server.go:45,49 + dynamic.go:45 全链路明文,无 TLS 配置能力JWT 与业务数据在 0.0.0.0:12300/12301 明文传输(是否由前置代理终止 TLS 属部署前提,进程内无保障)。
internal/server/server.go:34dynamic.go:180-188 客户端 IP 可伪造:未 SetTrustedProxiesgin v1.12.0 默认 trustedProxies={"0.0.0.0/0","::/0"}ForwardedByClientIP=truegin gin.go:225,214)→ c.ClientIP() 取客户端自报 XFF动态 RPC 把请求中所有 x- 前缀头原样透传内网(dynamic.go:182-188grpc-gateway 也会把请求 XFF 拼入 metadatagateway runtime/context.go:185-193)。落点示例:module/base/mgt/internal/logic/pub/login.go:90 把 client IP 写进 token。
internal/server/authorization.go:55-66server.go:35 匿名登录入口无任何限流`passport.Login/Pwd
etc/default_dev.yaml:24-43 对比模块实现 匿名口径与模块声明不一致ecmall 侧比 all 多一项):① logs 的 `POST /rest/logs/create
internal/server/authorization.go:96-120 白名单是精确字符串匹配gRPC 形态与 REST 真实路径混列同一列表;/rpc/... 仅在恰好 4 段时改写为网关形态(canonicalHTTPPath)。新增方法/路由忘记登记只会静默变成"需要 JWT",无一致性校验或告警。
internal/server/dynamic.go:73,115-176 动态 RPC 描述符缓存键由请求路径拼接(无上限、无 TTL未命中时每次向内网反射服务发一次 RPC任意匿名调用者可用随机符号名放大内网调用protojson 解析错误原样回显(dynamic.go:95-98)。

5.2 正确性与逻辑缺陷

级别 位置 问题
internal/server/server.go:63-71 gateway 404 与业务 NotFound 不可区分,真实响应被丢弃recorder.status != http.StatusNotFound 被用作"网关未命中路由"的判据,但 grpc-gateway 对业务 NotFound 同样渲染 HTTP 404gateway runtime/errors.go:48-49),与路由未命中的 404runtime/mux.go:464,552)状态码相同。任何 gateway 方法返回 codes.NotFound"记录不存在"等高频分支,如 mall.Product/order.Summary/cms.Post 的查询)时,已生成的 {code,message,...} 响应被丢弃,客户端收到 gin 的纯文本 404 page not found
internal/server/server.go:82-89 启动期错误只处理一半errCh 只读一个值即返回,先失败的一方不会关闭另一侧的 listener/gRPC server也没有 Shutdown 兜底。
cmd/main/main.go:48-53 + internal/server/server.go:92-110 优雅关闭超时被复用、错误被丢弃GracefulStop 超时后用同一个已过期的 ctx 执行 s.http.Shutdown(ctx),立即返回 deadline exceeded在途 REST 请求被中断;外层 _ = srv.Stop(ctx) 丢弃返回错误。
internal/server/server.go:32 流式 RPC 不受鉴权覆盖:只有 grpc.UnaryInterceptor,无 StreamInterceptor。当前 66 个 proto、35 个服务无 stream 声明(grep -n "stream " --include=*.proto module 无命中),故无实际暴露;动态 RPC 侧已显式拒绝流式(dynamic.go:79-82),风险在原生 gRPC 面(12300)。
internal/server/server.go:112-133 全量缓冲响应:所有 gateway 响应先写入 bytes.Buffer 再整体 flushserver.go:64,125-132cms.Post.Listmall.Productorder.Summary.List 这类列表响应在内存中同时存在两份;flushAdd 复制响应头(server.go:128WriteHeader 无重复调用保护(server.go:123)。
etc/default_dev.yaml:79 + module/base/fts/internal/logic/provider.go:71 配置的下载地址没有对应路由Fts.Local.Site: http://127.0.0.1:12301/files,本地存储返回 Site + "/" + bucket + "/" + subdirpath + "/" + fileName;而 fts 只注册 `/rest/fts/ping
internal/config/config.go:92-95 端口漏配会随机绑定conf.CheckPortPort 为空时随机生成 1024-65535SDK conf/new.go:90-97),两个 listener 各自随机且"必须不同"的校验必然通过 → 在随机端口"成功"启动;ecmall 的随机端口还可能撞上 pkgs/all12000/12001 或模块 dev 端口。
internal/config/config_test.go:15,43-52 注册表清单存在 3 处拷贝internal/service/service.go:14-32(注册表本体)、internal/config/config_test.go:15ecmallServices 常量)、etc/default_dev.yaml:50-64Services)。测试只能保证后两者一致;新增服务若漏改常量与 yaml两项测试都不会失败service_test.go:31 只断言 11 个固定前缀存在,不断言"注册表 == yaml")。AGENT.md:247 已把"改 3 处"写成约定,属人工约束。
internal/config/config.go:115-130 + internal/service/service.go:34-45 服务名写错会静默启动空宿主Enabled 不校验名字是否存在,Expose 对 0 个注册不报错;BSM_SERVICES=mailmall 的笔误)会得到一个只在 12300/12301 上回答 404 + reflection 的进程。
cmd/main/main.go:23-29 workspace 名会被 utils.MustString 转成小写SDK utils/ext.go:40-41),因此 --workspace=Tenant01 会去找 tenant01_dev.yamlREADME 描述一致,但配置文件命名必须全小写,否则报错信息(workspace must match ...)与实际文件名不匹配,易误判。

5.3 未完成/不一致

级别 位置 问题
pkgs/ecmall/etc/ + scripts/build-all-linux.sh:7,30 无 prod/test 配置,且不在构建脚本范围内MODULE_ROOT="${WORKSPACE_ROOT}/module"pkgs/ 完全不被扫描,pkgs/ecmall/etc 只有 default_dev.yaml。上线路径目前只有"开发模式 + 手工 go build"。
etc/default_dev.yaml:86-88 + module/base/mgt/internal/models/impl.go:61-74 聚合下 Mgt.InitRootUser: true 不生效。建表与 root 播种唯一调用链是 module/base/mgt/internal/impl/with.go:20 ← 模块自己的 impl.NewImpl();聚合只调 applyDependenciesmodule/base/mgt/service/dependencies.go:19-32grep -rn "NewImpl()|AutoMigrate|models.New(" module/*/*/service/*.go 无命中。yaml 已用注释说明(default_dev.yaml:68-70 指向 AGENT.md §3.5),但配置项本身仍在,容易被误读为"已开启初始化"。
module/base/passport/internal/logic/verify/jumio_callback.go:13-34 被匿名放开的回调是空壳/passport.Verify/JumioCallback 在白名单(etc/default_dev.yaml:33),实现只 printer.Info 后返回成功,代码内自带 TODO「In production, implement proper callback handling logic」不校验签名。当前无实际危害补实现前必须先加签名校验。
internal/config/config.go:84 + SDK with/databases.go:13-15 Databases 未纳入配置校验conf.NotNil(Spec.Service, Spec.Cache) 只两项;Databases 缺失时 impl.NewImpl()impl.go:38)内部 panic("No Database Source Found !")impl.NewImpl() 早于 server.New/Exposecmd/main/main.go:31-44),此时 Redis/Etcd 已建连且无失败回滚。
internal/server/* vs pkgs/all/internal/server/* 两份拷贝需手工同步server.go/authorization.go/dynamic.go/response.go 逐字节相同(唯一差异是 server.go:79-80 的日志字面量),没有测试或 CI 阻止单侧漂移;改一处漏一处会导致"同一接口在两个入口鉴权/分流行为不同"。
无进程级健康检查端点:独立入口均注册 app.HEAD("/", infra.Health)module/base/fts/cmd/main/main.go:36module/base/logs/cmd/main/main.go:39module/base/mgt/cmd/main/main.go:41ecmall 只有三个模块自带的 /rest/*/pingHEAD / 落到 gin 404。
AGENT.md:110 文档引用行号漂移:AGENT.mdSecretKeyinternal/service/service.go:29 使用,实际在 :35。文档正文正确、行号过期,属长期维护噪音(同类引用见 §4.2/§4.4 等处的行号)。

5.4 健壮性与可维护性

级别 位置 问题
internal/server/server.go:72-78 WriteTimeout/ReadTimeout,只有 ReadHeaderTimeout/IdleTimeout/MaxHeaderBytes;同时 gin 侧无请求体大小限制(/rest/fts/uploader 的上限只在 fts 逻辑层),与动态 RPC 的 4 MiB 上限(dynamic.go:30,84-92)不对称。
internal/server/server.go:34 未按 BSM_RuntimeMode 设置 gin 模式(独立入口会调 middleware.ModeSDK middleware/mode.go:9-16)→ 生产环境仍为 gin Debug 模式(除非外部设置 GIN_MODE)。
internal/server/server.go:34 未启用 CORS而独立 fts/mgt 启用SDK 实现 AllowAllOrigins: true)→ 同一接口"独立可被浏览器调用、聚合不可用"的行为差。
internal/server/server.go:79-80 fmt.Printf 直接打印监听地址,绕过 SDK printergin.Logger() 无条件记录全部请求(server.go:35)。
internal/server/server.go:112-133 bufferedResponse 只实现 Header/WriteHeader/Write,缺 http.Flusher/Hijacker/CloseNotifier;当前 unary JSON 可用,任何流式/大文件网关响应都会静默退化。
internal/server/dynamic.go:34-36 描述符缓存无条件增长(map + RWMutex,无上限、无 TTL键来自外部输入的路径拼接。
internal/config/config.go:115-130 BSM_SERVICES 一旦非空即完全覆盖 Services,且无去重、无存在性校验;Enabled 同时对 "all" 特判,语义("全开"与"名单"混用)只能从 README/AGENT 得知。
测试覆盖 有护栏但不深:service_test.go:15-53 断言 11 个前缀与 6 条 REST 路由存在,config_test.go 断言 yaml 与常量一致;但没有httpMiddleware 白名单与注册表一致性的断言、没有对 404 分流语义的断言(本次 E4 的 404 覆盖问题因此未被测试发现)。

6. 风险汇总

编号 级别 问题 影响面
E1 JWT 密钥默认值是可直接使用的公开字面量 全系统身份伪造
E2 session 密钥取未校验的 SecretKey(默认 CHANGE_ME cookie 完整性与伪造
E3 无 prod 配置,且构建脚本不覆盖 pkgs/* 上线路径、部署可靠性
E4 gateway 404 与业务 NotFound 不可区分,真实响应被覆盖 所有 gateway 方法的错误语义
E5 reflection 无开关且匿名可达(该入口依赖它) 内部 API 全貌泄露
E6 无 TLS 传输机密性
E7 客户端 IP 可经 XFF 伪造并透传内网 metadata 审计与风控数据可信性
E8 匿名登录入口无任何限流 账号安全
E9 匿名口径与模块声明不一致logs 三条 REST、ads/sender/feedback 多组接口) 独立/聚合行为不一致、上线 401
E10 流式 RPC 不受鉴权拦截器覆盖(当前无流式方法) 未来扩接口时的越权风险
E11 Fts.Local.Site 指向的 /files 无路由 文件下载不可用
E12 优雅关闭超时后 HTTP 立即中断且错误被丢弃 关闭期请求可靠性
E13 gateway 响应全量缓冲 大响应下的内存放大
E14 聚合不建表不播种,Mgt.InitRootUser: true 不生效 首次部署缺表/缺管理员
E15 Databases 未校验 + 资源初始化无失败回滚 启动期崩溃与连接泄漏
E16 Jumio 回调空壳(无签名校验)却已匿名放开 补实现后的数据伪造风险
E17 注册表 3 处拷贝、服务名写错静默空宿主、两份 server 拷贝需手工同步 变更回归与一致性
E18 端口漏配随机绑定、无 WriteTimeout、gin 模式/CORS 与独立入口不一致、无健康端点、文档行号漂移 运维与可维护性

7. 修复建议(务实项)

  1. 密钥不能是占位值E1、E2internal/config/config.go 现有 Authorization.Key 校验之后,加"Authorization.Key 不得等于样例字面量"与"SecretKey 非空且不得等于 CHANGE_ME"两条判断(各 3 行);顺带把 Databases 纳入 conf.NotNilE15。不引入配置中心或加密方案。
  2. 补生产配置与构建覆盖E3新增 pkgs/ecmall/etc/default_prod.yaml(替换全部 CHANGE_ME、显式 BindIP),并让 scripts/build-all-linux.sh 的模块收集范围覆盖 pkgs/E3 与工作区脚本报告同源)。
  3. 修 404 分流E4自定义 gateway 的 routing error handler或让 bufferedResponse 记录"是否由 routing error handler 写出"),只在"路由未命中"时回退 gin业务 NotFound 必须原样 flush。这是 server.go:63-71 一处判断的修正,不需要改接口契约。
  4. reflection 开关化E5Server.New 增加一个布尔形参/配置项(默认关闭),仅在开启时 reflection.Register;同时从 isAnonymous 删除 /grpc.health. 前缀。注意动态 RPC 依赖反射——若要保留 /rpc/...,应改为只允许来自 loopback 的调用(remoteAddr 判定即可)。
  5. 客户端 IP 与头部清理E7server.Newengine.SetTrustedProxies(<真实代理网段>)(无代理传 niloutgoingMetadata 不再透传客户端自带的 x-forwarded-for/x-real-ip,改为服务端重算。
  6. 匿名白名单口径对齐E9逐条决策后同步两处——logs 的 create/fetch/total、ads 的 Fetch/ByPos、sender 的三个方法、feedback 的四个方法,要么加入 Authorization.Anonymous,要么在模块侧去掉"匿名"声明。改配置即可,零代码。
  7. 文件下载路由E11在 fts 路由注册补静态目录(本地存储场景),或把 Fts.Local.Site 改成真实存在的前缀。
  8. 启动/关闭收尾E12、E15Start 中任一侧 Serve 返回错误时关闭两个 listener 并 ShutdownStop 中给 http.Shutdown 单独的短超时 ctx 并输出错误;Port 为空改为报错退出不要随机绑定E18
  9. 注册表与测试护栏E17config_test.go 增加"yaml 的 Services 与注册表逐项一致"的断言(现只能比对常量);在 service_test.go 补一条"白名单中的 REST 路径确实已注册"的断言,把 AGENT.md §4.5 的记录变成可执行检查。
  10. 两份拷贝的同步E17pkgs/all 补与 pkgs/ecmall/internal/service/service_test.go 等价的注册表测试;两份 internal/server 的漂移用"同一组测试断言 + 文件头注明需同步"来兜,不新建公共包。
  11. 首次部署前置E14README.md/AGENT.md 之外,把"聚合不建表、不播种 root"写成启动期显式检查(例如启动时探测 mgt 关键表是否存在,缺失则拒绝启动并打印指引),避免运行到首个请求才暴露。
  12. 入口限流与可观测E8、E18httpMiddleware 内对匿名路径加一个基于内存的简单计数限流;http.ServerWriteTimeout;按 BSM_RuntimeMode 设置 gin 模式;注册 HEAD / 健康端点。

本报告只列与现有实现直接相关的修复项,不引入新的分层或抽象封装。审计中"合并两个聚合入口""为聚合引入配置中心/服务注册中心""把动态 RPC 改造成网关插件"一类改造不在此列。

8. 整改记录2026-09-22

本节记录按本报告结论执行的代码整改。整改遵循最小修正原则未引入新框架、抽象层、DTO/VO、事件总线未拆分服务边界未修改任何 proto/*.proto 与生成的 pb/*.go;新增/修改注释均为中文;口令类摘要统一使用 bcrypt验证码等短时效一次性凭证仍按原有 Redis 明文比对链路存储)。校验方式:GOWORK=off go build ./... + GOWORK=off go vet ./... + gofmt -l(仓库根 workspace 模式存在 genproto 拆包的 ambiguous import属本机既有问题

编号 级别 问题 处理结果
E1 JWT 密钥默认值是可直接使用的公开字面量 已修复:同 pkgs/all A3显式拒绝公开默认密钥缺失时启动失败未改 SDK
E2 session 密钥取未校验的 SecretKey(默认 CHANGE_ME 已修复:同 pkgs/all A2增加非空与非公开默认值校验
E3 无 prod 配置,且构建脚本不覆盖 pkgs/* 已修复:新增 etc/default_prod.yamletc/default_test.yaml;构建脚本覆盖聚合入口
E4 gateway 404 与业务 NotFound 不可区分 已修复:同 pkgs/all A4改用 WithRoutingErrorHandler 标记「路由未命中」,业务 NotFound 原样透出
E14 聚合不建表不播种,Mgt.InitRootUser: true 不生效 部分处理:聚合已打开自动迁移,启动时可为可达的模块建表;播种(InitData/InitRootUser)位于各模块 internal/ 下,聚合进程无法导入,未能执行——需产品侧决策播种入口,未擅自扩大改动范围

未纳入本轮范围

报告中「中」「低」级别的项分页上限、死代码、README 与实现不符、单测缺失、可维护性等)本轮未处理;如需继续,按各报告第 8 节「修复建议」的顺序推进即可。

本轮整改未修改任何 proto/*.protopb/*.go,因此少数需要新增接口字段才能完整实现的项目(已在处理结果中标注)做了安全降级。