# feedback(意见反馈管理)代码审计报告 | 项 | 内容 | | --- | --- | | 审计对象 | `module/base/feedback` | | 服务域 | 基础与平台服务 | | 审计日期 | 2026-09-22 | | 代码规模 | 手写 Go 文件 19 个、821 行(含 `test/rpc/rpc.go` 23 行空壳);`proto` 2 个(`feedback.proto`、`const.proto`);`pb/` 生成文件 4 个 | | 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程)、聚合入口 `pkgs/all`(`pkgs/all/internal/service/feedback.go`) | | 对外协议 | gRPC + REST Gateway,前缀 `/feedback.Method` | | 结论摘要 | 越权缺陷成堆:`Get`/`Delete`/`Remark` 连身份都不解析,`Modify` 解析了身份却仍按调用方传入的 `identity` 更新,四个接口**均无归属校验**(IDOR),且 `Modify` 会把记录 `identity` 改写成随机 UUID 并把归属改成攻击者;`Modify` 处理附件时未生成 `identity`,子表 `identity` 是唯一索引,附件 ≥2 条必然唯一键冲突;`List`/`Get` 只 `Preload("Images")`,附件永远为空;此外 `etc/` 下 4 份 yaml 全部不符合 SDK 配置约定(`Name`/`ListenOn`/`Dsn` + 顶层 `Anonymous`),独立进程启动即 `log.Fatalln`。 | ## 1. 服务定位与职责 用户意见/反馈的提交与后台管理:提交反馈(含图片、附件)、按条件分页查询、查看详情、修改、删除、填写备注并流转状态。数据模型为"反馈主表 + 图片子表 + 附件子表"。它不负责附件文件本身的上传(那是 `module/base/fts` 的职责),只保存 URL/路径。 ## 2. 代码结构与入口 | 路径 | 职责 | | --- | --- | | `cmd/main/main.go` | 独立进程入口:`config.New("Feedback")` → `impl.NewImpl` → `server.New(nil)` → `service.New(...).Start()` | | `cmd/cli/main.go` | 命令行入口,仅 `log.Println("Hello World!")`(7 行,占位) | | `internal/config/config.go` | 配置结构(`Base`/`Databases`/`MicroService`/`Rpc`/`Gateway`/`APM`/`Etcd`)与 `conf.NotNil` 校验 | | `internal/impl/impl.go` | 初始化 DB / Redis / Etcd / 内存缓存 | | `internal/logic/method/list.go` | `List`:分页 + 按 identity/agency/username/status/category 过滤 | | `internal/logic/method/get.go` | `Get`:按 `identity` 查详情 | | `internal/logic/method/add.go` | `Add`:创建主表 + 图片 + 附件 | | `internal/logic/method/modify.go` | `Modify`:删子表 → 更新主表 → 重建子表 | | `internal/logic/method/delete.go` | `Delete`:按 `identity` 删主表 | | `internal/logic/method/remark.go` | `Remark`:更新 `remark` + `status` | | `internal/logic/method/ref.go` | `convert`:模型 → pb 转换(含图片、附件) | | `internal/models/feedback_item.go` | `feedback_item` 主表模型 + 迁移注册 | | `internal/models/feedback_images.go` | `feedback_images` 图片子表 | | `internal/models/feedback_accessory.go` | `feedback_accessory` 附件子表 | | `internal/server/{new,method_server}.go` | 注册 `Method` 到 gRPC,handler 转发 logic | | `internal/routers` | **不存在**(走 gRPC + HTTP Gateway) | | `service/{expose,dependencies}.go` | 聚合宿主注入接口 | | `etc/{feedback.yaml,feedback_dev,feedback_prod,feedback_test}.yaml` | 4 份配置,字段命名与 SDK 约定不符(见 6.3) | | `test/add.http`、`test/rpc/rpc.go` | 手工用例(JSON 非法)+ 注释空壳 | ## 3. 接口清单 | 方法 | 路径 | 功能 | 鉴权 | 实现位置 | | --- | --- | --- | --- | --- | | POST | `/feedback.Method/List` | 分页查询反馈(条件过滤) | 网关 JWT + `service.ParseMetaCtx`(`list.go:19`) | `internal/logic/method/list.go` | | POST | `/feedback.Method/Get` | 按 `identity` 查详情(返回 email/phone) | 网关 JWT,**进程内不解析身份**(`get.go` 无 ParseMetaCtx) | `internal/logic/method/get.go` | | POST | `/feedback.Method/Add` | 新增反馈 | 网关 JWT + `ParseMetaCtx`(`add.go:21`) | `internal/logic/method/add.go` | | POST | `/feedback.Method/Modify` | 按 `identity` 修改 | 网关 JWT + `ParseMetaCtx`(`modify.go:24`) | `internal/logic/method/modify.go` | | POST | `/feedback.Method/Delete` | 按 `identity` 删除 | 网关 JWT,**进程内不解析身份**(`delete.go`) | `internal/logic/method/delete.go` | | POST | `/feedback.Method/Remark` | 按 `identity` 更新备注 + 状态 | 网关 JWT,**进程内不解析身份**(`remark.go`) | `internal/logic/method/remark.go` | **声明但未实现的接口**:无。`proto/feedback.proto:6-13` 的 `Method` 服务 6 个 rpc 与 `pb/feedback_grpc.pb.go` 的 `MethodServer`、`internal/server/method_server.go:18-40` 的转发方法一一对应。 > 鉴权补充: > 1. 模块 yaml 里列出的 `feedback.Check.Hello/Updates`、`feedback.Data.Configure/Areas/Tags`(`etc/feedback_dev.yaml:19-23`)在本模块 proto 中**根本不存在**,属死配置,详见 6.3。 > 2. 聚合入口 `pkgs/all/etc/default_dev.yaml:24-43` 的 `Authorization.Anonymous` 也未包含 `/feedback.Method/*`,故上线形态下 6 个接口都要求携带有效 JWT——但"有效 JWT"仅代表"是某个登录用户",接口内部不做任何归属判断,见 6.1。 ## 4. 数据模型与表 ### 表 `feedback_item`(`internal/models/feedback_item.go`) | 字段 | 类型 | 键/约束 | 说明 | | --- | --- | --- | --- | | `id` | uint | PK | 来自 `types.Std_IICUDS` | | `identity` | varchar(36) | uniqueIndex | 记录唯一标识(`D:\work\bsm-sdk\core\types\db.go:30`) | | `created_at`/`updated_at`/`deleted_at` | timestamp | - | 带软删 | | `passport_id` | uint | Index | 提交人 ID(`types.Std_Passport`) | | `passport_identity` | varchar(36) | Index | 提交人标识,`List` 的归属过滤字段 | | `status` | int32 | default 1 | 1 未处理 / 2 已处理(**与内嵌 `Std_IICUDS.Status` int8 同列冲突**,见 6.2) | | `category` / `user_name` / `email` / `phone` | varchar | - | 分类 / 用户名 / 邮箱 / 手机(**含 PII**) | | `title`(255)/ `content`(500)/ `remark`(500) | varchar | - | 标题 / 内容 / 备注 | | `agency` | varchar(255) | - | 机构 | | `images` / `accessories` | - | `foreignKey:ItemIdentity` | GORM 关联字段(非列) | ### 表 `feedback_images` / `feedback_accessory` | 字段 | 类型 | 键/约束 | 说明 | | --- | --- | --- | --- | | `identity` | varchar(36) | uniqueIndex | 子表唯一标识(来自 `Std_IICUDS`) | | `item_identity` | varchar(36) | default '' | 关联主表 `identity`,无 DB 级外键 | | `url`(图片) | varchar(255) | default '' | 图片地址 | | `title` / `file_path`(附件) | varchar(255) / varchar(500) | not null | 附件标题 / 附件路径 | ## 5. 核心流程 ```mermaid flowchart TD A["POST /feedback.Method/Modify"] --> B["service.ParseMetaCtx 解析 auth"] B --> C["校验 in.Identity 非空"] C --> D["构造 record:Identity = utils.UUID(),归属改写为 auth"] D --> E["按 item_identity 删除 feedback_images / feedback_accessory"] E --> F["Where(identity = in.Identity).Updates(record)"] F --> G["GORM 顺带 upsert 非空 HasMany 关联"] G --> H["显式 Create(&record.Images) / Create(&record.Accessories)"] I["POST /feedback.Method/Get"] --> J["Where(identity = in.Identity).First:无身份、无归属校验"] J --> K["返回含 email / phone 的完整记录"] ``` ## 6. 审计发现 ### 6.1 安全 | 级别 | 位置 | 问题 | | --- | --- | --- | | **高** | `internal/logic/method/get.go:19-41`(关键:`:21` 只校验 `identity` 非空、`:29` 按 `identity` 查询) | `Get` **完全不解析调用方身份**,仅按请求体里的 `identity` 取记录,且返回体包含 `email`/`phone`(`ref.go:17-18`)。任何持有效 JWT 的用户遍历 `identity` 即可读取**全部用户**的反馈与联系方式(IDOR + PII 泄露)。 | | **高** | `internal/logic/method/delete.go:19-29`(关键:`:26`) | `Delete` **不解析身份、不做归属校验**,`Where("identity = ?", in.GetIdentity()).Delete(...)` 直接删除。任何登录用户可删除他人反馈(软删,`deleted_at`)。 | | **高** | `internal/logic/method/remark.go:19-35`(关键:`:21`,`:28`,`:32`) | `Remark` **不解析身份、不做归属校验**,且 `status` 取自请求体(`remark.go:28`)→ 任何登录用户可改写任意反馈的备注与处理状态(可把"已处理"回退或伪造)。 | | **高** | `internal/logic/method/modify.go:22-27,30,89` | `Modify` 虽然解析了 `auth`,但**只用它覆写归属字段**(`modify.go:39-42`),更新条件仍是调用方传入的 `in.Identity`(`modify.go:89`)→ 可篡改他人反馈;更严重的是会把该记录的 `passport_identity` 改成攻击者自己,**完成"夺权"**:原主人因 `List` 按 `passport_identity` 过滤(`list.go:43`)而再也看不到自己的反馈。 | | 中 | `internal/logic/method/list.go:38-45` | `List` 的过滤是"二选一":传入 `agency` 时**只**按 `agency` 过滤,`passport_identity` 条件被跳过 → 任意登录用户只要知道机构名,就能枚举该机构下**全部**反馈,且响应含 `email`/`phone`(`ref.go:17-18`)。 | | 中 | `internal/logic/method/list.go:41-44` | 当 `auth.Identity` 为空串时,`passport_identity` 过滤被跳过(`if userIdentity != ""`),查询退化为无归属条件的分页查询。`service.ParseMetaCtx` 只校验 JWT 签名与有效期、不校验 `identity` 非空(`D:\work\bsm-sdk\core\service\meta.go:19-47`),因此该分支在 token 未携带 `identity` 时可达。 | | 低 | `internal/logic/method/ref.go:13-26` | 列表/详情统一输出 `email`、`phone`,PII 在所有读接口上无脱敏、无权限分级。 | ### 6.2 正确性与逻辑缺陷 | 级别 | 位置 | 问题 | | --- | --- | --- | | **高** | `internal/logic/method/modify.go:35-38,89` | `Updates(record)` 的 `record` 里 `Std_IICUDS.Identity = utils.UUID()`(新 UUID)。GORM 结构体 `Updates` 只忽略零值,`Identity` 非零 → **主表 `identity` 列被改写成新 UUID**。由于 `Where` 用的是旧 `identity`,更新后原 `identity` 立即失效,`Get`/`Modify`/`Delete`/`Remark` 全部找不到该记录,任何外部引用(如工单、通知)随之断裂。 | | **高** | `internal/logic/method/modify.go:70-76` | 构造附件时**未设置 `Identity`**(对比 `add.go:57-66` 明确赋 `utils.UUID()`),`FeedbackAccessory` 的 `identity` 取零值 `""`;而子表 `identity` 是 `uniqueIndex`(`D:\work\bsm-sdk\core\types\db.go:30`)→ **附件 ≥2 条时必然唯一键冲突**;即使只传 1 条,也会写入一行 `identity=""` 的脏数据,后续再改就再也插不进去。 | | **高** | `internal/logic/method/modify.go:50-52,89,95-106` | 关联被写入两遍。GORM v2 的 Update 处理器注册了 `gorm:save_after_associations`(`gorm@v1.31.2/callbacks/callbacks.go:72`),`SaveAfterAssociations(false)` 对 ReflectValue 为 Struct 的 `HasMany` 字段会调用 `saveAssociations`(`callbacks/associations.go:190-236`),最终执行 `tx.Create(values)`(`callbacks/associations.go:431`,非 `FullSaveAssociations` 时带 `ON CONFLICT DO NOTHING`,见 `:371-372`)。也就是说 `modify.go:89` 的 `Updates` 已把 `Images`/`Accessories` 插入库中,随后 `modify.go:96`/`:102` 又对同一批(同 `identity`)显式 `Create` → 第二次插入撞唯一索引,接口返回错误,而主表与子表**其实已经改了一半**(无事务包裹)。 | | 中 | `internal/logic/method/delete.go:25-26` | 注释写"GORM会自动处理关联的图片和附件删除",但模型只在关联字段上声明了 `foreignKey:ItemIdentity`(`feedback_item.go:23-24`),**没有 `OnDelete:CASCADE`**,子表也无 DB 级外键 → `Delete` 只删主表,`feedback_images`/`feedback_accessory` 成为孤儿数据(`Modify` 是手动先删子表的,`Delete` 没做)。 | | 中 | `internal/models/feedback_item.go:12` 与 `:18` | `status` 列被定义两次:内嵌 `types.Std_IICUDS.Status`(int8,`default:0;index`)与自定义 `Status int32`(`default:1`)。GORM 按"最短绑定路径优先"解析(`gorm@v1.31.2/schema/schema.go:238`),外层字段胜出、内嵌字段被遮蔽 → 实际建表丢失 `index` 标签,默认值语义也从 SDK 约定的 0 变成 1。 | | 中 | `internal/logic/method/list.go:35` 与 `internal/logic/method/get.go:29` | 两处只 `Preload("Images")`,**没有 `Preload("Accessories")`**,而 `ref.go:38-45` 会转换并输出附件 → `List`/`Get` 返回的 `accessories` 恒为空数组,与 `proto/feedback.proto:46`(`repeated FeedbackAccessory accessories = 12`)的契约不符。 | | 低 | `internal/logic/method/list.go:25-32,71` | `page` 无上限钳制(只保证 `>= 1`),`offset = (page-1)*size` 直接下推为 `OFFSET`,传超大 `page` 会造成大偏移慢查询。 | | 低 | `internal/logic/method/remark.go:37-40`、`internal/logic/method/delete.go:31-34` | 返回 `pb.StatusReply` 只填 `Message`/`Timeseq`,`code` 恒为 0(`proto/feedback.proto:126`),调用方无法从 `code` 判断成功/失败。 | | — | `internal/logic/method/list.go:71` | `.Find(&list).Count(&count)` 复用同一 `Statement` 看似可疑,但 GORM 在 `Execute` 结束时会重置 SQL(`gorm@v1.31.2/callbacks.go:149-152`),`Count` 重建为 `SELECT count(*) ... LIMIT n OFFSET m`,聚合结果单行不受 `LIMIT` 影响,`count` 等于命中总数。**非缺陷**(特此说明)。 | | — | `internal/logic/method/add.go:36` | `Add` 接受调用方传入的 `status`,与 `proto/feedback.proto:74` 注释"也可以调用方自行设置"一致,属设计约定;但结合 6.1 的 `Remark` 问题,状态流转整体缺少权限约束。 | ### 6.3 未完成实现 - **独立进程无法启动:4 份 etc 配置全不符合 SDK 约定**。`etc/feedback_dev.yaml:1-4` 使用 `Name:` / `ListenOn:` / `Dsn:`,而 SDK 的 `conf.Base` yaml 标签是 `Service`/`Port`/`Cache`/`SecretKey`/`BindIP`(`D:\work\bsm-sdk\core\conf\types.go:7-16`);更关键的是 `conf.New` 在解析前会校验文件文本必须包含 `Service:`,否则 `log.Fatalln("ERROR: Service Not Nil")`(`D:\work\bsm-sdk\core\conf\new.go:58-60`)。`feedback.yaml`、`feedback_dev.yaml`、`feedback_prod.yaml`、`feedback_test.yaml` 都只有 `ServiceName:`(不匹配 `Service:` 子串),默认 `BSM_RuntimeMode=dev`(`D:\work\bsm-sdk\core\env\env.go:20`)会加载 `etc/feedback_dev.yaml` → **进程直接 Fatal 退出**。即便绕过该校验,因无 `Databases:` 段,`with.Databases` 也会 `panic("No Database Source Found !")`(`D:\work\bsm-sdk\core\with\databases.go:13-15`)。`etc/feedback_prod.yaml:4` 是 `Dsn: postgres://prod:***@192.168.0.224:5432/scf...`,即"生产配置"同样不可用。 - **匿名清单整段失效且内容不存在**:`etc/feedback_dev.yaml:16-23` 用的是顶层 `Anonymous:` + `Key:`/`Urls:` 结构,而 SDK 期望 `MicroService: {Enable: bool, Anonymous: []string}`(`conf\types.go:23-26`);`internal/config/config.go:17-25` 的 `SrvConfig` 也没有对应字段 → 该段被 yaml **静默忽略**(`conf.New` 未启用 strict 模式)。同时 `MicroService:` 整段缺失 → `Spec.MicroService == nil` → `service.Start` 既不注册服务也不发布匿名清单(`D:\work\bsm-sdk\core\service\service.go:66-90`)。所列 `feedback.Check.Hello/Updates`、`feedback.Data.Configure/Areas/Tags` 在本模块 proto 中不存在(`proto/feedback.proto:6-13` 只有 `Method`)→ 死配置。 - `proto/feedback.proto:119-122` 的 `IdentRequest` 消息未被任何 rpc 引用 → 死消息。 - 未发现显式 TODO/占位。 ### 6.4 健壮性与可维护性 | 级别 | 位置 | 问题 | | --- | --- | --- | | 中 | `internal/config/config.go:39` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`(与 ads 等模块同模式),配合 `with.Databases` 的 panic,缺配置时表现为启动崩溃且报错不含配置项名。 | | 中 | `internal/logic/method/modify.go:79-106` | 删子表 → 更新主表 → 建子表 三步**没有事务包裹**(对比 `add.go:69` 的单次 `Create` 由 GORM 自动开事务),任一步失败都会留下半成品数据。 | | 低 | `internal/impl/impl.go:13-16` | `RedisService`/`EtcdService`/`MemorySerice` 初始化后本模块无任何使用点(死代码,同 ads)。 | | 低 | `test/add.http:9,16` | 用例 JSON 含 `"type"` 字段(proto 无此字段)且末尾多一个逗号(非法 JSON),无法直接执行;`status` 未传。 | | 低 | `test/rpc/rpc.go:3-23` | 整个 `main` 被注释,且引用不存在的 `pb.NewMethodClient`/`pb.Crc` → 死代码。 | | 低 | 全模块 | **无任何 `*_test.go`**(`test/rpc/rpc.go` 在 `package rpc` 下且函数体全注释),上述 IDOR 与附件唯一键冲突均无测试覆盖。 | | 低 | `internal/logic/method/modify.go:56-59` | 图片 `identity` 缺省时生成新 UUID,意味着"不传 identity 就重建",与"按 identity 幂等更新"的语义不一致;结合 6.2 的重复写入问题,同一图片会被覆盖或冲突。 | ## 7. 风险汇总 | 编号 | 级别 | 问题 | 影响面 | | --- | --- | --- | --- | | F1 | 高 | `Get`/`Delete`/`Remark` 不解析身份 + 四接口均无归属校验(IDOR) | 越权读取/删除/篡改他人反馈 | | F2 | 高 | `Get`/`List` 返回 `email`/`phone`(PII) | 用户隐私泄露 | | F3 | 高 | `Modify` 把主表 `identity` 改写为随机 UUID | 记录"消失"、外部引用断裂 | | F4 | 高 | `Modify` 附件未赋 `identity` → 子表唯一键冲突 | 修改功能不可用 | | F5 | 高 | 关联被 `Updates` 与显式 `Create` 写两遍 + 无事务 | 修改报错且数据半更新 | | F6 | 中 | `List` 的 `agency` 分支绕过归属过滤;`auth.Identity` 为空时返回全量 | 机构级数据枚举 | | F7 | 中 | `List`/`Get` 未 Preload 附件 | 接口返回与 proto 契约不符 | | F8 | 中 | `Delete` 不清理子表 | 孤儿数据堆积 | | F9 | 中 | 4 份 etc 配置不符合 SDK 约定,独立进程 Fatal;匿名清单整段失效且引用不存在的服务 | 独立部署不可用、鉴权策略失真 | | F10 | 中 | `status` 列重复定义(`Std_IICUDS.Status` 与自定义 `Status`) | 建表丢索引、默认值语义漂移 | | F11 | 低 | 死代码(Redis/Etcd/Memory、`IdentRequest`、`test/rpc`)、非法 http 用例、无测试、无事务 | 可维护性 | ## 8. 修复建议(务实项) 1. **补齐归属校验(最高优先)**:`Get`/`Delete`/`Remark` 先 `service.ParseMetaCtx(ctx, nil)` 取 `auth`,再按 `in.Identity` 取出记录,比对 `record.PassportIdentity == auth.Identity`;`Modify` 在现有 `auth` 基础上加同一比对。管理侧(跨用户操作)另设显式判断,不要用"任意有效 JWT"代表权限。 2. **`Modify` 不要重设 `identity`**:从更新结构体里移除 `Std_IICUDS.Identity`(参考 `Map` 更新或 `Select(...)` 只选允许字段),保留原 `identity`;同时不要把 `Std_Passport` 用调用方身份覆写(会造成归属转移)。 3. **消除重复关联写入**:`modify.go:89` 的 `Updates` 前加 `.Omit(clause.Associations)`(或 `Select` 限定主表字段),只保留 `:95-106` 的显式 `Create`;二者只能留一个。 4. **补附件 `identity`**:`modify.go:70-76` 与 `add.go:57-66` 对齐,为 `FeedbackAccessory` 赋 `Identity: utils.UUID()`。 5. **`Delete` 清理子表**:按 `item_identity = in.Identity` 先删 `feedback_images`/`feedback_accessory`,再删主表(与 `Modify` 的现有动作一致)。 6. **事务**:把 `modify.go:79-106` 的删/更新/建三步放进 `impl.DBService.Transaction(func(tx *gorm.DB) error {...})`。 7. **`Preload` 补齐**:`list.go:35`、`get.go:29` 增加 `Preload("Accessories")`。 8. **`List` 权限收敛**:`agency` 过滤仅对管理角色开放;普通用户强制附加 `passport_identity = auth.Identity`;`auth.Identity` 为空时直接返回拒绝而不是放宽条件。 9. **`Remark` 限制状态**:仅允许管理角色传 `status`,或把 `status` 从请求体移除、由服务端按流程计算。 10. **`Get`/`Delete`/`Remark` 返回值补 `code`**:`StatusReply.Code` 填明确成功/失败码,避免调用方只能看 `message`。 11. **配置修正**:把 `etc/feedback*.yaml` 改成 SDK 约定(`Service:`/`Port:`/`BindIP:`/`Cache:`/`Databases:` + `MicroService: {Enable, Anonymous}`),删除 `feedback.Check.*`/`feedback.Data.*` 等不存在的匿名项;`conf.NotNil` 补 `Databases`。 12. **模型去重**:`feedback_item.go` 保留一处 `status` 定义(建议保留自定义 `int32` 并显式写 `index`,或移除自定义字段改用 `Std_IICUDS.Status` 并统一类型)。 13. **清理与补测**:删 `impl.go` 中未使用的 Redis/Etcd/Memory 初始化、`proto/feedback.proto:119-122` 的 `IdentRequest`、`test/rpc/rpc.go`;修正 `test/add.http` 的非法 JSON;为 `Get`/`Modify`/`Delete`/`Remark` 各写 1 条越权用例与 1 条正常用例。 > 本报告只列出与现有实现直接相关的修复项,不引入新的分层、抽象封装或 DTO/VO 改造。 ## 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,属本机既有问题)。 | 编号 | 级别 | 问题 | 处理结果 | | --- | --- | --- | --- | | F1 | 高 | `Get`/`Delete`/`Remark` 不解析身份、四接口均无归属校验(IDOR) | 已修复:全部补 `service.ParseMetaCtx` 并按 `passport_id`/`passport_identity` 限定读写,越权返回 `ErrPermissionDenied` | | F2 | 高 | `Get`/`List` 直接返回 `email`/`phone`(PII) | 已修复:非本人/非管理端访问时对 PII 字段做掩码,本人可全量 | | F3 | 高 | `Modify` 把主表 `identity` 改写为随机 UUID | 已修复:改用 `Updates` 正常更新,不再改写 `identity` | | F4 | 高 | `Modify` 附件未赋 `identity` 导致子表唯一键冲突 | 已修复:为每个附件生成 `identity` | | F5 | 高 | 关联被写两遍且无事务,修改报错且数据半更新 | 已修复:合并为一次写入,并用 `impl.DBService.Transaction` 保证主表与附件同成功/同失败 | ### 未纳入本轮范围 报告中「中」「低」级别的项(分页上限、死代码、README 与实现不符、单测缺失、可维护性等)**本轮未处理**;如需继续,按各报告第 8 节「修复建议」的顺序推进即可。 > 本轮整改未修改任何 `proto/*.proto` 与 `pb/*.go`,因此少数需要新增接口字段才能完整实现的项目(已在处理结果中标注)做了安全降级。