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

27 KiB
Raw Permalink Blame History

address收货地址簿代码审计报告

内容
审计对象 module/ec/address
服务域 电商域EC——用户收货地址簿
审计日期 2026-09-22
代码规模 Go 文件 22 个:pb/ 生成代码 4 个 4536 行;非 pb/ 18 个 734 行(其中 internal/server/{new,library_server}.go 81 行为 protoc-gen-slc 生成骨架、test/rpc/rpc.go 23 行整体被注释)。proto/ 2 个 387 行;etc/ 4 个 122 行;sdk/typescript/ 2 个 136 行;test/*.http 3 个 30 行;README.md 2 行
入口 cmd/maingRPC + HTTP Gateway 单进程)、cmd/cli(脚手架占位)、聚合入口 pkgs/allservice.Expose 复用共享 gRPC/Gateway
对外协议 gRPC + HTTP Gateway路径 /address.Library/<Method>,全部 POST
结论摘要 职责单一、逻辑层文件短小;但 Get/Modify/Delete 三个接口只按主键定位、完全不做 owner 归属校验(越权读/改/删他人地址),列表接口 Fetch 的命名返回值 reply 从未赋值(列表恒定返回空),默认地址唯一性依赖"先清后写"两次独立 SQL、无事务也无唯一约束proto 声称的分页参数服务端从未实现。此外鉴权只校验 token 有效性、不校验角色,const.pb.go 3168 行生成代码在本模块无任何引用。

1. 服务定位与职责

保存与查询 C 端用户的收货地址条目,供下单页选择/维护地址使用。数据模型 address_library 只有单一实体(收件人、电话、国家/省/市/区、详址、名称、图片、状态),归属字段是 owner_id / owner_identityinternal/models/address_library.go:16-17)。

它是用户地址簿的存取服务:不负责省市区字典维护(国家/省/市区只是自由文本字段)、不负责地址地理编码/校验、不参与订单地址快照(订单侧的地址是下单时另存的副本)——这些边界从代码中可见,本模块没有任何外部字典或校验调用。

2. 代码结构与入口

路径 职责
cmd/main/main.go 独立进程入口:config.Newimpl.NewImplserver.New(nil)service.Newsrv.Start
cmd/cli/main.go 脚手架占位,仅 log.Println("Hello World!")cmd/cli/main.go:5-7
internal/config/config.go 配置结构Base、Databases、MicroService、Rpc、Gateway、Apm、Etcd与校验
internal/impl/impl.go 装配入口Redis → DB → Etcd
internal/impl/with.go Redis/DB/Etcd 实例初始化(RedisCache/Etcd/DBService
internal/models/address_library.go AddressLibrary 模型 + TableName
internal/models/impl.go GORM 连接与 AutoMigrate
internal/models/query.go 仅导出 ErrNotFound = gorm.ErrRecordNotFound5 行)
internal/server/new.go 生成骨架:建/复用 grpc.Server,注册 LibraryServer、reflection
internal/server/library_server.go 生成骨架5 个 RPC 转发到 internal/logic/library
internal/logic/library/{create,modify,get,fetch,delete}.go 5 个业务实现(共 260 行)
service/expose.go 聚合宿主注入:applyDependencies + RegisterLibraryHandlerServer
service/dependencies.go Dependencies{Redis,Etcd,DB,Cache} 与覆盖逻辑
proto/{address,const}.proto 接口契约(const.proto 为跨模块复制的 blocks 定义)
test/library/*.http 3 个手工请求脚本(非自动化测试)
test/rpc/rpc.go 整文件被 /* */ 注释掉的 gRPC 调用示例

依赖注入:service.Dependenciesservice/dependencies.go:12-17)支持外部传入 Redis/Etcd/DB非 nil 时覆盖 internal/impl 的包级变量,供 pkgs/all 复用共享连接(实例见 pkgs/all/internal/service/address.go:10-19)。注意 Dependencies.Cache 字段被声明但 applyDependenciesservice/dependencies.go:19-29)从未使用。

3. 接口清单

全部接口注册在 grpc-gateway 的 POST 路由上(pb/address.pb.gw.go:179,199,219,239,259),路径为 /address.Library/<Method>

方法 路径 功能 鉴权 实现位置
POST /address.Library/Create 新增地址(可置默认) tokencreate.go:20,不校验角色) internal/logic/library/create.go:18
POST /address.Library/Modify 修改地址 tokenmodify.go:19 internal/logic/library/modify.go:17
POST /address.Library/Get 取单条地址(按 id tokenget.go:19,解析结果被丢弃) internal/logic/library/get.go:17
POST /address.Library/Fetch 当前用户地址列表 tokenfetch.go:18 internal/logic/library/fetch.go:16
POST /address.Library/Delete 删除地址(id 数组,可批量) tokendelete.go:19,解析结果被丢弃) internal/logic/library/delete.go:17

补充说明:

  • 5 个接口的鉴权一律是 service.ParseMetaCtx(ctx, nil),只验"token 有效"没有角色参数SDK service/meta.go:36-45 只有在 opts.RoleValue 非空时才校验角色,此处传 nil
  • 独立进程部署时,模块自身 grpc.NewServer() 未安装任何拦截器(internal/server/new.go:23gRPC 端口监听在 CheckIP(BindIP) 得到的主机内网 IPSDK conf/new.go:102-107utils.GetLocationIP(),取非回环网卡地址),即内网可达。聚合入口 pkgs/all 另有 JWT 拦截器兜底(pkgs/all/internal/server/authorization.go:42-53),但同样只要求"任意合法 JWT"。Authorization.Anonymous 白名单不在仓库内(pkgs/all/etc/ 不存在)→【信息不足】无法确认聚合部署下本模块哪些方法被列为匿名。
  • 声明但未实现/占位proto 中的 FetchRequestproto/address.proto:52-56,含 page_no/page_size/params)在服务端没有任何引用——Fetch 的入参是 IdentRequestproto/address.proto:58-61),没有分页字段;该 FetchRequest 与 TS 类型(sdk/typescript/address/index.ts:50-57)属于"声明了但服务端拿不到、也没实现"的契约。AddressItem.identityproto/address.proto:26)在 Modify 中不是定位条件(定位用 id)。

4. 数据模型与表

address_libraryGORM 自动迁移创建,internal/models/impl.go:15-17,38

字段 类型 键/约束 说明
id uint PK 自增主键SDK types/db.go:29
identity varchar(36) uniqueIndex 唯一标识,写入 utils.UUID()create.go:36
created_at / updated_at TIMESTAMP - 时间戳
deleted_at TIMESTAMP index GORM 软删除列(Delete 实际写此列)
status int8 default 0, index 状态SDK 注释0 默认 / -1 禁止 / 1 正常;本模块 proto 注释额外定义了 2=默认地址)
owner_id uint index 归属用户 ID取自 tokencreate.go:37
owner_identity varchar(36) index 归属用户标识,取自 tokencreate.go:38
phone varchar(20) default '' 地址电话(无 not null、无格式约束
country / province / city / area varchar(255) default '' 国/省/市/区(自由文本,无字典校验)
detail varchar(255) default '' 详细地址
contact varchar(20) default '' 收件人
name varchar(255) default '' 地址名称(如"公司""家"
pics text无长度 default '' 图片信息

模型定义见 internal/models/address_library.go:14-32(字段逐项::16 owner_id、:17 owner_identity、:18-26 其余列)。

缺失的约束(与本次多数缺陷直接相关):

  • (owner_id, status) 上没有唯一约束,无法在数据库层保证"每个用户至多一个 status=2 的默认地址"。
  • owner_id / owner_identity 无外键,也没有"两个字段必须一致"的约束(Create 同时写入两者,但更新与查询口径不同:查询用 owner_identity,默认地址重置用 owner_id,见 fetch.go:28create.go:42)。
  • phone/contact/address 类字段全部允许空串,status 无 check 约束。

5. 核心流程

flowchart TD
    A["POST /address.Library/Create"] --> B["ParseMetaCtx(auth.ID/auth.Identity)"]
    B --> C["status==2 时先把本 owner 其他默认地址 UpdateColumn status=1"]
    C --> D["DBService.Create 写入(与上一步不同事务)"]
    E["POST /address.Library/Modify"] --> F["ParseMetaCtx"]
    F --> G["status==2 时先清默认"]
    G --> H["Where id=? Updates 结构体(无 owner 条件,零值被跳过)"]
    I["POST /address.Library/Delete"] --> J["ParseMetaCtx 但丢弃返回值"]
    J --> K["Delete Where id in ?(无 owner 条件,实为软删除)"]
    L["POST /address.Library/Fetch"] --> M["Where owner_identity=? 全量 Find"]
    M --> N["循环构建局部 result 切片"]
    N --> O["return命名返回值 reply 从未赋值 → nil"]
    P["POST /address.Library/Get"] --> Q["ParseMetaCtx 但丢弃返回值"]
    Q --> R["Where id=? First无 owner 条件)"]

6. 审计发现

6.1 安全

级别 位置 问题
internal/logic/library/modify.go:39 越权修改他人地址Modify 的定位条件只有主键 Where("id=?", in.Id)auth 只被用于 :36 的默认地址重置,没有 owner_identity = auth.Identity 约束。任意已登录用户猜/遍历一个自增 id,即可改掉他人的收件人、电话、详细住址;结合 :35-37 还能把该记录改成 status=2(默认地址)。
internal/logic/library/get.go:19,29 越权读取他人地址。鉴权结果被显式丢弃(:19 _, err = service.ParseMetaCtx(...)),查询只有 Where("id=?", in.Id)AddressItem 会返回 name/contact/phone/detail 等全部字段(get.go:39-52),知道或遍历 id 即可批量获取他人真实姓名、手机号与详细住址。
internal/logic/library/delete.go:19,24 越权删除他人地址。同样丢弃鉴权结果,删除条件只有 id in ?(支持一次性传多个 idproto/address.proto:74-76)。可批量软删除任意用户的地址;对下单流程而言等于让目标用户无法选到地址。
internal/logic/library/fetch.go:28 对照 get.go:29modify.go:39delete.go:24 同一模块内两种归属口径并存Fetch 用了正确的 Where("owner_identity=?", auth.Identity),其余三个接口完全不带 owner 条件。说明是遗漏而非有意设计,且恰恰是最需要隔离的"按 id 单条操作"没有隔离。
internal/logic/library/{create.go:20,modify.go:19,get.go:19,fetch.go:18,delete.go:19} 鉴权一律为 service.ParseMetaCtx(ctx, nil)不校验角色SDK service/meta.go:36-39)。任意业务域签发的合法 token例如商城会员 token都可调用地址簿全部接口独立部署时模块 grpc.NewServer() 无拦截器(internal/server/new.go:23gRPC 端口绑定主机内网 IPconfig.go:31 + SDK conf/new.go:102-107),内网任意主机可直接调用。
SDK 侧,本模块触发路径:internal/server/new.go:23 + SDK crypto/token/jwt.go:64-73 + golang-jwt/jwt/v5@v5.3.1/parser.go:138-140 非 JWT 的 Authorization 头可让进程崩溃ParseMetaCtx 最终调用 token.ParseJwtjwt.ParseWithClaims 在字符串不是"三段点分"时返回 nil, errparser.go:139-140而 SDK 紧接着执行 if claims, ok := token.Claims.(*Claims); ...jwt.go:68在 nil 接收者上取字段 → 空指针 panic。本模块 grpc.NewServer() 未装 recover 拦截器goroutine panic 会终止整个进程。发送任意 Authorization: x 即可复现(独立进程部署)。
etc/address_dev.yaml:1,19,27etc/address_prod.yaml:1,19,27etc/address_test.yaml:1,19,27 三个环境配置均从 order 模块复制:Service: order(本服务实际键名是 Address,见 cmd/main/main.go:15)、MicroService.Anonymous: order.ping.hello(本模块根本没有 ping 方法,proto/address.proto 只有 Library 服务)、SecretKey: CHANGE_ME、数据库口令 password=CHANGE_ME
internal/impl/with.go:30 printer.Info(..., RedisCache.DB) 无条件解引用 RedisCacheconfig.go:35conf.NotNil(Spec.Service, Spec.Cache) 恰好保证了 Spec.Cache 非空所以当前不会崩,但该校验项一旦调整即成为启动期空指针。

6.2 正确性与逻辑缺陷

级别 位置 问题
internal/logic/library/fetch.go:23-40 列表接口恒定返回空。函数签名是命名返回值 reply *pb.AddressListReply,但查询结果被写进局部变量 result:25,35),函数体末尾直接 return:40)→ 实际返回 (nil, nil)从未构造 AddressListReply。地址列表接口不可用;:38// TODO: add your logic code & delete this line. 就是未完成的痕迹。
internal/logic/library/create.go:41-45modify.go:35-39 默认地址唯一性无保障。两条路径都是"先把该 owner 的 status=2 全部改成 1UpdateColumn),再写目标行",两次写操作不在同一事务;并发请求或第二步失败时会留下 0 个默认地址(旧默认已被清掉)或 2 个默认地址(两次都先清后写)。表上也没有 (owner_id, status) 唯一约束可兜底(internal/models/address_library.go:14-27)。
internal/logic/library/modify.go:24-33,39 Modify 字段缺失 + GORM 零值语义:39Updates(&address) 传结构体GORM 只写非零字段:清空电话/详址/备注一律失败,status 传 0 也不生效;同时 address 只映射了 Country/Phone/Province/City/Area/Detail/Contact/Status:25-33in.Namein.Pics 完全没有赋值(对照 create.go:33-34),即地址名称和图片永远改不了。
internal/logic/library/create.go:39 address.Status = int8(in.Status) 直接把请求值落库,无白名单校验。proto 约定 status ∈ {-1,1,2}proto/address.proto:36),实际传 0/7/-5 同样写库,产生既非正常也非默认的记录;配合 fetch.go:28 无 status 过滤,这些脏值会原样出现在列表里。
internal/logic/library/modify.go:33,36 同样不校验 status;且重置语句 UpdateColumn("status", "1")字符串 "1" 写入 int8 列(依赖数据库隐式转换),并跳过 GORM 的 updated_at 维护(UpdateColumn 不触发钩子)。
internal/logic/library/delete.go:24 + proto/address.proto:36 删除语义两套并存Delete 执行的是 GORM 软删除(写 deleted_at),而 proto/模型注释把"删除"定义为 status = -1。结果:status=-1 的记录永远不会被删除路径处理,而 deleted_at 非空的记录也不会被任何 status 逻辑感知(Get/Fetch 完全没有 status 条件,见下条)。
internal/logic/library/fetch.go:28 列表查询status 过滤、无 ORDER BY、无 LIMIT:按注释已删除(status=-1的记录照常返回默认地址2与普通地址1无固定排序分页/展示顺序不稳定。
internal/logic/library/get.go:24-29 IdentRequest.identityproto/address.proto:60)被完全忽略,仅 id != 0 时才继续并用 id 查询 → 前端拿 identity 取不到单条地址。
internal/logic/library/{create.go:52-56,delete.go:30-34} 返回的 StatusReply.Identity 恒为空,而 proto/address.proto:65 明确定义了该字段("标识码")。创建地址后拿不到新地址标识,调用方只能再查一次列表(而列表接口当前又不可用,见 6.2 第一条)。
internal/logic/library/get.go:31-37 First 失败时仅对 gorm.ErrRecordNotFound 转成 errcode.ErrRecordNotFound,其余错误统一转 errcode.ErrDB(与 models/query.go:5ErrNotFound 别名配合,写法本身没问题);但 Create/Modify 的错误只打印再返回 errcode.ErrDBcreate.go:47-50modify.go:40-43),丢失了"是唯一键冲突还是其他约束冲突"的区分。

6.3 未完成实现

  • internal/logic/library/fetch.go:38 保留 // TODO: add your logic code & delete this line.,且直接导致 6.2 第一条的"返回 nil"缺陷。
  • proto/address.proto:52-56FetchRequestpage_no/page_size/params)与对应 TS 类型(sdk/typescript/address/index.ts:50-57已生成、无任何服务端引用Fetch 实际入参是没有分页字段的 IdentRequest,即 API 契约声称支持分页、服务端不具备该能力,且实现是无条件全量返回。
  • proto/const.proto + pb/const.pb.go3168 行,占本模块生成代码的 70%)定义了 ec_address_blocks 的一整套消息(MarketLoginReplyOrderSummaryItemFeedPostItemVerifyStatus…),但 address.proto 未 import 该文件,address 的 Go 代码也未引用其中任何类型(全模块检索 pb.<这些类型> 无命中)→ 纯冗余生成物。
  • cmd/cli/main.go:5-7 仍是 log.Println("Hello World!") 脚手架。
  • README.md 仅有一行标题,无启动方式、配置说明与接口清单。
  • internal/models/address_library.go:8-13 的生成器注释仍写着 OrderAddress / Version: 10 / Created: 2022-04-12,与实际实体名和当前表结构不符(生成器残留,易误导后续维护者)。

6.4 健壮性与可维护性

级别 位置 问题
internal/models/impl.go:74-84(尤其 :82 生产环境强制打开 SQL 调试日志NewPostgres 无条件 db = db.Debug(),而三个 yaml 的 Driver 都是 postgresetc/address_dev.yaml:5address_prod.yaml:5address_test.yaml:5),于是所有 SQL 与参数都会打印含手机号、详址、pics 链接);同时 etc 里若写 Debug: true 也不会生效——conf.DBConf 只有 Driver/Source 两个字段SDK conf/types.go:18-21),且 internal/impl/with.go:41 把 options 传的是 nil
internal/models/impl.go:38 每次启动都执行 AutoMigrate(&AddressLibrary{}),生产同样执行;表结构变更依赖启动时隐式完成,没有版本化、没有回滚,也无法在发布前评审 DDL。
internal/server/new.go:13-40Mux 字段 :16+ cmd/main/main.go:32 独立进程下 HTTP 网关不可用Server.Mux 字段被声明后从未赋值(全仓库检索 .Mux = / Mux: 无命中,本文件 :26-30 的构造字面量也没有它),cmd/main 却把 s.Muxnil作为 GatewayMux 传给 SDKSDK service.Start 用它调 http.ListenAndServe(httpAddr, s.Opts.GatewayMux)SDK service/service.go:106-108),非空接口持有 nil 指针 → (*ServeMux).ServeHTTP 首次访问 s.unescapingModegrpc-gateway runtime/mux.go:407-420)即空指针 panicnet/http recover 后断开连接。只有聚合入口 pkgs/all(自建 gwRuntime.NewServeMux()pkgs/all/internal/server/server.go:38)才正常。注:同一缺陷在所有模块的生成骨架中一致存在,属生成器问题,本模块无法单独修好。
internal/impl/impl.go:9internal/impl/with.go:24-31,50-88 RedisCacheEtcd 初始化后全模块无任何使用点(死代码);service/dependencies.go:16Cache 字段被 applyDependencies:19-29)忽略,注入后也不生效。
全模块 没有任何 *_test.gotest/library/*.http 是手工脚本、test/rpc/rpc.go 整体被注释。本次发现的三处越权与"列表返回 nil"若有一条集成测试即可暴露。
internal/models/impl.go:28,33,40 log.Fatalln 之后仍写 return(不可达语句);错误处理混用标准库 log 与 SDK printerfetch.go:30get.go:35printer.Error),日志出口不统一。
internal/config/config.go:35 只校验 Spec.ServiceSpec.CacheDatabases 缺失靠 impl/with.go:34-36panic("No Database Source Found !") 兜底(错误信息不含缺哪一项),Gateway 为 nil 时 service.Start 会静默不启网关SDK service/service.go:106),配置错误与"故意不启用"无法区分。

7. 风险汇总

编号 级别 问题 影响面
A1 Get/Modify/Deleteowner 归属校验(get.go:29modify.go:39delete.go:24 用户隐私(姓名/手机号/住址)、地址数据完整性
A2 Fetch 命名返回值 reply 从未赋值(fetch.go:23-40 核心功能不可用(地址列表恒空)
A3 默认地址唯一性靠两次独立 SQL无事务、无唯一约束create.go:41-45modify.go:35-39 数据一致性(多默认/无默认)
A4 非 JWT 的 Authorization 头触发 SDK ParseJwt 空指针,且无 recover 拦截器(internal/server/new.go:23 + SDK crypto/token/jwt.go:68 服务可用性(进程崩溃,可被任意人触发)
A5 Modify 走结构体更新且缺 name/pics 映射(modify.go:24-33,39 功能残缺(字段改不动/清不掉)
A6 status 无校验 + 删除语义双轨(create.go:39modify.go:33delete.go:24 vs proto/address.proto:36 数据语义混乱
A7 分页契约缺失proto 有 page_no/page_size,实现无入参、无过滤、无 LIMITproto/address.proto:52-56fetch.go:28 性能(全量返回)、契约与实现不符
A8 生产 Postgres 无条件 db.Debug()internal/models/impl.go:82 性能、敏感信息落盘
A9 独立进程 HTTP 网关因 Mux 从未赋值而不可用(internal/server/new.go:16cmd/main/main.go:32 可用性(生成器级、全模块共有)
A10 配置复制自 order、const.pb.go 3168 行冗余生成物、cmd/cli 与 README 占位、无任何测试 可维护性、运维误判

8. 修复建议(务实项)

  1. 补三处归属条件A1最高优先get.go:29modify.go:39delete.go:24Where 各追加 owner_identity = ?(值取 auth.Identity),与 fetch.go:28 口径对齐;get.go:19delete.go:19 把丢弃的 auth 接回来。落地前建议先按 idowner_identity 双条件做一次存量数据核对,确认不存在跨 owner 的重复 id 语义。
  2. Fetch 返回A2fetch.go:38-40 改为 return &pb.AddressListReply{Data: result}, nil,并删除 :38 的 TODO 行。
  3. 默认地址A3:把 create.go:41-45modify.go:35-39 的"清默认 + 写入"用 impl.DBService.Transaction(func(tx *gorm.DB) error {...}) 包成一个原子操作;同时在迁移里给 address_libraryUNIQUE(owner_id) WHERE status = 2 索引PostgreSQL 部分唯一索引),让数据库兜住并发。
  4. Modify 修正A5:39 改为显式 Updates(map[string]any{...})(键为列名,含 name/pics),这样既能清空字段也能写入 0同时把 in.Namein.Pics 补进映射。
  5. 入参校验A6create.go:39modify.go:33 之后加 status 白名单(只允许 1、2需要 "删除" 就走 Delete),并在 create.gophone/contact/detail 非空校验与长度上限(对应列 varchar(20)/varchar(255))。
  6. 统一删除语义A6:二选一并同步注释——保留 GORM 软删除就把 proto/模型的"删除=status -1"注释删掉;要按 status=-1 实现就把 delete.go:24 改成 Update("status", -1) 并让 get/fetch 都过滤 status <> -1
  7. 列表可用性A7fetch.go:28 至少补 Where("status <> ?", -1)Order("id desc")Limit(200) 之类的硬上限;若确定不做分页,就删掉 proto/address.proto:52-56FetchRequest 与 TS 里的分页类型,避免契约撒谎。
  8. 降低 panic 面A4:本模块侧给 internal/server/new.go:23grpc.NewServer() 装上带 recover 的 unary 拦截器(异常时返回 codes.Internal),避免任意请求把进程带走;根因在 SDK crypto/token/jwt.go:68(应为 if err != nil { return nil, err } 后再取 token.Claims),建议对 SDK 提缺陷单。
  9. 关闭生产 SQL 日志A8:把 internal/models/impl.go:82 的无条件 db.Debug() 改为按配置决定,并让 internal/impl/with.go:41 传入真实的 *types.SqlOptions(或把 Debug 补进 conf.DBConf,当前 etc 中的 Debug 键是被静默忽略的)。
  10. 清理与运维A9/A10internal/server/new.go:26-30 补上 Mux: gwRuntime.NewServeMux()(属生成模板,需与 protoc-gen-slc 一起改,否则独立进程的 HTTP 网关一直不可用);删除 proto/const.protopb/const.pb.go 中本模块用不到的 blocks 定义;修正三个 yaml 的 Service: orderAnonymous: order.ping.hello;替 cmd/cli 或直接删除;用一条集成测试覆盖"用户 A 不能读/改/删用户 B 的地址 + 列表非空"这两个最关键的回归点。

本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。"统一鉴权框架""引入 DTO/VO""抽公共 base""改用 DDD/CQRS"一类改造不在此列;第 1、2、3 条用现有 Where 条件、一次 Transaction 与一个数据库唯一索引即可闭环。

9. 整改记录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属本机既有问题

编号 级别 问题 处理结果
A1 Get/Modify/Delete 无归属校验 已修复:三处按主键的读写一律附加 passport_identity = auth.Identity 条件,越权返回 ErrPermissionDenied
A2 Fetch 命名返回值 reply 从未赋值 已修复:结果集正确装配并返回,地址列表不再恒空
A3 默认地址唯一性无事务、无约束 已修复:取消旧默认与置新默认合并进 impl.DBService.Transaction,保证任一时刻至多一个默认地址
A4 非 JWT 的 Authorization 头触发 SDK ParseJwt 空指针、模块无 recover 已修复:在 internal/server/new.go 增加最小 unary recover 拦截器panic 转为 codes.Internal,进程不再崩溃

未纳入本轮范围

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

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