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

33 KiB
Raw Permalink Blame History

cloud个人云空间与资料管理代码审计报告

内容
审计对象 module/base/cloud
服务域 基础与平台服务(个人云空间:网盘 / 相册 / 笔记 / 收藏 / 私人资料 / 分享 / 空间统计)
审计日期 2026-09-22
代码规模 Go 文件约 80 个全部手写7 个 server 注册、59 个 logic 实现、11 个 models、config/impl/cmd 各 1proto 9 个、etc yaml 3 个、README 1 个;生成代码 pb/*.go 仅看接口签名
入口 无独立 cmd/main(仅 cmd/cli/main.go 为 "Hello World" 桩);作为 gRPC 服务经聚合入口 pkgs/allpkgs/ecmall 接入
对外协议 gRPC7 个 service+ HTTP Gatewaygrpc-gateway 默认路由 /cloud.<Service>/<Method>proto 无 google.api.http 注解)
结论摘要 接口全集已实现、均以 service.ParseMetaCtx 做网关鉴权、并以 passport_id = auth.ID 做归属隔离(无跨用户越权读)。但存在私人资料"加密"未真正落地(明文存储、密钥由客户端传入、加解密为无状态工具不落库)空间默认 key_identifier="default" 与全局唯一索引冲突导致多用户仅首名可用GetDir/GetDirTree 对根目录 ParentID 空指针解引用 panic容量配额仅统计不限制、**分享链路断裂收件人无法取得他人资源、CreateShare 不校验资源归属)**等严重缺陷。

1. 服务定位与职责

个人云空间聚合服务,包含七个子域:云盘目录/文件disk、相册/照片album、笔记/附件note、书签收藏bookmark、私人加密资料private、分享share、空间统计space。每个子域通过 gRPC 暴露 CRUD 与列表/搜索接口,由 HTTP Gateway 透出 REST。多用户数据隔离通过 passport_id(网关注入的身份)实现,所有归属校验在 logic 层以 WHERE passport_id = ? 完成。

2. 代码结构与入口

路径 职责
cmd/cli/main.go CLI 桩,仅打印 "Hello World",无实质逻辑
internal/config/config.go 配置结构Base、Databases、MicroService、Rpc、Gateway、Apm、Etcd与校验
internal/impl/impl.go 初始化 Redis、Etcd、DB、Memory 等共享实例
internal/server/*.go 7 个 gRPC server 注册Disk/Album/Bookmark/Note/Private/Share/Space逐方法转发到 logic
internal/logic/disk/* 目录树、文件上传/移动/复制/搜索15 个实现)
internal/logic/album/* 相册与照片12 个实现)
internal/logic/note/* 笔记与附件10 个实现)
internal/logic/bookmark/* 书签与导入6 个实现)
internal/logic/private/* 私人资料与加解密9 个实现)
internal/logic/share/* 分享与密码校验5 个实现)
internal/logic/space/* 空间统计2 个实现)
internal/models/*.go 11 个 GORM 模型 + query.go(CloudBase 内嵌结构)
proto/*.proto 7 个 service 定义 + const.proto 公共消息
etc/cloud_dev.yaml 运行配置Gateway 开启、MicroService.Anonymous 含失效项 cloud.ping.hello

依赖注入与 logs 模块不同:本模块通过 service.ParseMetaCtx(ctx, nil) 从网关注入的 metadata 解析 auth.ID/Identity,每个 logic 函数首行即调用并校验,缺失即返回错误,因此所有接口均在网关层强制鉴权

3. 接口清单

鉴权列统一说明:全部接口经网关鉴权(ParseMetaCtx 缺失即报错)。除特别标注外,归属校验均为 WHERE passport_id = auth.ID。REST 路径为 grpc-gateway 默认格式 /cloud.<Service>/<Method>proto 未定义 http 注解)。

声明但未实现 / 占位:本次审计中,全部 59 个 proto 方法均有对应 logic 实现,未发现 TODO 占位。但"私人资料加密存储"与"分享向他人交付资源"两块在逻辑上未真正闭合(见 6.2/6.3)。

Disk15

方法 REST 路径 功能 鉴权/归属 实现位置
CreateDir /cloud.Disk/CreateDir 建目录(校验父目录归属) passport 归属 logic/disk/create_dir.go:20
GetDir /cloud.Disk/GetDir 目录详情(含子目录/文件) passport 归属 logic/disk/get_dir.go:16
UpdateDir /cloud.Disk/UpdateDir 改名/重算路径 passport 归属 logic/disk/update_dir.go:19
DeleteDir /cloud.Disk/DeleteDir 删目录(空目录才允许) passport 归属 logic/disk/delete_dir.go:17
ListDirs /cloud.Disk/ListDirs 分页列表 passport 归属 logic/disk/list_dirs.go:14
GetDirTree /cloud.Disk/GetDirTree 目录树 passport 归属 logic/disk/get_dir_tree.go:16
MoveDir /cloud.Disk/MoveDir 移动目录 passport 归属 logic/disk/move_dir.go:19
UploadFile /cloud.Disk/UploadFile 上传文件(校验目录归属) passport 归属 logic/disk/upload_file.go:19
GetFile /cloud.Disk/GetFile 文件详情JOIN dir passport 归属 logic/disk/get_file.go:16
UpdateFile /cloud.Disk/UpdateFile 改文件名等 passport 归属 logic/disk/update_file.go:18
DeleteFile /cloud.Disk/DeleteFile 删文件 passport 归属 logic/disk/delete_file.go:17
ListFiles /cloud.Disk/ListFiles 分页列表JOIN dir passport 归属 logic/disk/list_files.go:14
MoveFile /cloud.Disk/MoveFile 移动文件 passport 归属 logic/disk/move_file.go:17
CopyFile /cloud.Disk/CopyFile 复制文件 passport 归属 logic/disk/copy_file.go:19
SearchFiles /cloud.Disk/SearchFiles 关键字搜索 passport 归属 logic/disk/search_files.go:15

Album12

方法 REST 路径 功能 鉴权/归属 实现位置
CreateAlbum /cloud.Album/CreateAlbum 建相册 passport 归属 logic/album/create_album.go:19
GetAlbum /cloud.Album/GetAlbum 相册详情(含照片) passport 归属 logic/album/get_album.go:16
UpdateAlbum /cloud.Album/UpdateAlbum 改相册 passport 归属 logic/album/update_album.go:18
DeleteAlbum /cloud.Album/DeleteAlbum 删相册(先删照片) passport 归属 logic/album/delete_album.go:17
ListAlbums /cloud.Album/ListAlbums 分页列表(含照片) passport 归属 logic/album/list_albums.go:14
SetCoverPhoto /cloud.Album/SetCoverPhoto 设封面(校验照片归属相册) passport 归属 logic/album/set_cover_photo.go:17
UploadPhoto /cloud.Album/UploadPhoto 传照片(校验相册归属) passport 归属 logic/album/upload_photo.go:19
GetPhoto /cloud.Album/GetPhoto 照片详情JOIN album passport 归属 logic/album/get_photo.go:16
UpdatePhoto /cloud.Album/UpdatePhoto 改照片 passport 归属 logic/album/update_photo.go:17
DeletePhoto /cloud.Album/DeletePhoto 删照片 passport 归属 logic/album/delete_photo.go:17
ListPhotos /cloud.Album/ListPhotos 分页列表 passport 归属 logic/album/list_photos.go:14
MovePhoto /cloud.Album/MovePhoto 移动照片到其他相册 passport 归属 logic/album/move_photo.go:17

Bookmark6

方法 REST 路径 功能 鉴权/归属 实现位置
CreateBookmark /cloud.Bookmark/CreateBookmark 建书签 passport 归属 logic/bookmark/create_bookmark.go:19
GetBookmark /cloud.Bookmark/GetBookmark 书签详情(仅 id passport 归属(id+passport) logic/bookmark/get_bookmark.go:16
UpdateBookmark /cloud.Bookmark/UpdateBookmark 改书签 passport 归属 logic/bookmark/update_bookmark.go:18
DeleteBookmark /cloud.Bookmark/DeleteBookmark 删书签(仅 id passport 归属(id+passport) logic/bookmark/delete_bookmark.go:17
ListBookmarks /cloud.Bookmark/ListBookmarks 分页列表 passport 归属 logic/bookmark/list_bookmarks.go:14
ImportBookmarks /cloud.Bookmark/ImportBookmarks 批量导入 passport 归属 logic/bookmark/import_bookmarks.go:21

Note10

方法 REST 路径 功能 鉴权/归属 实现位置
CreateNote /cloud.Note/CreateNote 建笔记 passport 归属 logic/note/create_note.go:19
GetNote /cloud.Note/GetNote 笔记详情(含附件) passport 归属 logic/note/get_note.go:16
UpdateNote /cloud.Note/UpdateNote 改笔记 passport 归属 logic/note/update_note.go:18
DeleteNote /cloud.Note/DeleteNote 删笔记(先删附件) passport 归属 logic/note/delete_note.go:17
ListNotes /cloud.Note/ListNotes 分页列表 passport 归属 logic/note/list_notes.go:14
TogglePin /cloud.Note/TogglePin 置顶切换 passport 归属 logic/note/toggle_pin.go:17
IncrementViews /cloud.Note/IncrementViews 浏览+1 passport 归属(仅本人) logic/note/increment_views.go:17
SearchNotes /cloud.Note/SearchNotes 关键字搜索 passport 归属 logic/note/search_notes.go:15
InsertAttachment /cloud.Note/InsertAttachment 加附件 passport 归属 logic/note/insert_attachment.go:17
DeleteAttachment /cloud.Note/DeleteAttachment 删附件JOIN note passport 归属 logic/note/delete_attachment.go:17

Private9

方法 REST 路径 功能 鉴权/归属 实现位置
CreatePrivateData /cloud.Private/CreatePrivateData 建私人资料 passport 归属 logic/private/create_private_data.go:19
GetPrivateData /cloud.Private/GetPrivateData 详情 passport 归属 logic/private/get_private_data.go:16
UpdatePrivateData /cloud.Private/UpdatePrivateData 改资料 passport 归属 logic/private/update_private_data.go:18
DeletePrivateData /cloud.Private/DeletePrivateData 删资料 passport 归属 logic/private/delete_private_data.go:17
ListPrivateData /cloud.Private/ListPrivateData 分页列表 passport 归属 logic/private/list_private_data.go:14
GetPrivateDataByType /cloud.Private/GetPrivateDataByType 按类型查 passport 归属 logic/private/get_private_data_by_type.go:16
SearchPrivateData /cloud.Private/SearchPrivateData 搜索 passport 归属 logic/private/search_private_data.go:15
EncryptData /cloud.Private/EncryptData AES 加密(无状态工具) 仅网关鉴权,auth 被丢弃、不落库 logic/private/encrypt_data.go:19
DecryptData /cloud.Private/DecryptData AES 解密(无状态工具) 仅网关鉴权,auth 被丢弃、不落库 logic/private/decrypt_data.go:17

Share5

方法 REST 路径 功能 鉴权/归属 实现位置
CreateShare /cloud.Share/CreateShare 建分享 passport 归属(不校验 resource_id 归属) logic/share/create_share.go:19
GetShare /cloud.Share/GetShare 分享详情 passport 归属(仅创建者可见) logic/share/get_share.go:16
DeleteShare /cloud.Share/DeleteShare 删分享 passport 归属 logic/share/delete_share.go:17
ListShares /cloud.Share/ListShares 列表 passport 归属 logic/share/list_shares.go:14
ValidateSharePassword /cloud.Share/ValidateSharePassword 校验密码 仅网关鉴权;按 identity 查、无 passport 限制、不校验 is_public logic/share/validate_share_password.go:18

Space2

方法 REST 路径 功能 鉴权/归属 实现位置
Get /cloud.Space/Get 空间统计(缺则建默认) passport 归属 logic/space/get.go:16
GetByKeyIdentifier /cloud.Space/GetByKeyIdentifier 按 key 查空间 passport 归属 logic/space/get_by_key_identifier.go:16

4. 数据模型与表

公共内嵌:types.Std_IICUDS(id、created_at、updated_at、deleted_at)、types.Std_Passport(passport_id、passport_identity)、CloudBase(cloud_id、cloud_identity)。表名由 GORM 取 struct 蛇形复数。

cloud_disk_dir(云盘目录)

字段 类型 键/约束 说明
id uint PK 自增
passport_id / passport_identity uint / varchar 归属 来自 Std_Passport
cloud_id / cloud_identity uint / varchar(36) Index 来自 CloudBase
parent_id *uint index, 可空 父目录;根目录为 nil
name varchar(100) - 目录名
path varchar(500) - 完整路径
created_at/updated_at/deleted_at - - 时间戳

cloud_disk_file(云盘文件)

字段 类型 键/约束 说明
id uint PK 自增
passport_id 等 - 归属 Std_Passport
directory_id *uint index, 可空 所属目录
name / original_name varchar(255) - 文件名
size int64 - 字节
mime_type varchar(100) - 类型
storage_path varchar(500) - 实际存储路径
hash varchar(64) - 去重哈希

cloud_album(相册)

字段 类型 键/约束 说明
id / passport_id 等 - PK/归属 -
name varchar(100) - 名称
description varchar(500) - 描述
cover_photo varchar(255) - 封面 URL
is_private bool default false 是否私有

cloud_photo(照片)

字段 类型 键/约束 说明
id uint PK 自增
无 passport 列 - - 归属经 album 间接隔离
album_id uint index 所属相册
title/description varchar(100)/(500) - -
file_path varchar(500) - 路径
file_size int64 - 大小
mime_type varchar(100) - 类型
width/height int - 尺寸
taken_at time - 拍摄时间
location varchar(200) - 地点
tags varchar(500) - 标签

cloud_note(笔记)

字段 类型 键/约束 说明
id / passport_id 等 - PK/归属 -
title varchar(200) - 标题
content text - 内容
category varchar(50) - 分类
tags varchar(500) - 标签
is_markdown bool default true -
is_pinned bool default false 置顶
is_private bool default false 私有
views int default 0 浏览数

note_attachments(笔记附件)

字段 类型 键/约束 说明
id uint PK 自增
note_id uint index 笔记 ID
file_name varchar(255) - 文件名
file_path varchar(500) - 路径
file_size int64 - 大小
mime_type varchar(100) - 类型
created_at time - 创建时间

注:该表无 identity 列,故 DeleteAttachment 只能按 id 删除。

cloud_bookmark(书签)

字段 类型 键/约束 说明
id / passport_id 等 - PK/归属 -
title varchar(200) - 标题
url varchar(500) - 网址
description/category varchar(500)/(50) - -
tags/icon varchar(500) - -
is_private bool default false 私有

cloud_private(私人资料)

字段 类型 键/约束 说明
id / passport_id 等 - PK/归属 -
data_type varchar(50) - password/card/document…
title varchar(200) - 标题
description varchar(500) - 描述
data text - 明文存储的实际数据(未真正加密)
is_encrypted bool default true 由客户端请求标记,非服务端计算
tags varchar(500) - 标签

cloud_share(分享)

字段 类型 键/约束 说明
id / passport_id 等 - PK/归属 -
share_type varchar(20) - file/album/note…
resource_id uint - 资源 ID建分享时不校验归属
share_token varchar(32) uniqueIndex 分享令牌
password varchar(100) - 明文存储的分享密码
expires_at time - 过期时间
view_count / download_count int default 0 统计
is_public bool default false 读取链路未使用该字段

cloud_space(空间统计)

字段 类型 键/约束 说明
id / passport_id 等 - PK/归属 -
key_identifier varchar(32) uniqueIndex全局 默认写入字面量 "default"
total_storage / used_storage / max_storage int64 - 容量(写入从不校验上限
file_count/album_count/photo_count/note_count/bookmark_count/private_count int - 各类统计

5. 核心流程

flowchart TD
    A["客户端请求 GetFile(id)"] --> B["ParseMetaCtx 解析 passport"]
    B --> C{"有鉴权?"}
    C -- 否 --> E["返回错误"]
    C -- 是 --> D["JOIN cloud_disk_dirs WHERE passport_id=auth.ID AND file.id=?"]
    D --> F{"命中记录?"}
    F -- 否 --> G["返回 ErrInvalidArgument"]
    F -- 是 --> H["返回文件详情"]
flowchart TD
    A["分享者 CreateShare(resource_id)"] --> B["仅存分享记录, 不校验 resource_id 归属"]
    C["收件人 ValidateSharePassword(identity,password)"] --> D["WHERE identity=? (无 passport 限制, 不校验 is_public)"]
    D --> E{"密码/过期校验"}
    E -- 失败 --> F["返回错误"]
    E -- 通过 --> G["ViewCount++, 返回 OK"]
    G --> H["收件人尝试 GetFile/GetNote(resource_id)"]
    H --> I["WHERE passport_id=收件人(非分享者)"]
    I --> J["无权限, 返回 ErrInvalidArgument"]
    J --> K["资源实际无法被收件人获取"]

6. 审计发现

6.1 安全

级别 位置 问题
internal/logic/private/encrypt_data.go:19-75decrypt_data.go:17-81create_private_data.go:54 私人资料"加密"未真正落地。README 宣称"加密存储",但 EncryptData/DecryptData 是无状态 AES 工具:密钥由客户端 in.Key 传入(仅填充/截断到 32 字节),函数不读写任何数据库、不绑定任何 CloudPrivate 记录(auth_ 丢弃)。CreatePrivateData 直接把请求里的 Data 原文与客户端自报的 IsEncrypted 落库(data 为明文/客户端原文,非服务端密文)。即"加密"仅是客户端行为 + 一个标记位,服务端未对存储数据做任何加密保护。
internal/logic/share/create_share.go:19-87get_share.go:30 分享链路断裂且可构造越权分享GetShare/ListShares/DeleteShare 均以 passport_id = auth.ID 限定,仅有创建者能看到自己的分享;而底层资源(文件/笔记/相册)读取仍按 passport_id 隔离,收件人即便通过 ValidateSharePassword 也无法取得他人资源(见第 5 节流程图)。此外 CreateShare 仅校验 share_type/resource_id/cloud_identity不校验 resource_id 是否属于当前用户,可对任意资源 ID 创建分享记录。
internal/logic/share/create_share.go:71get_share.go:54list_shares.go:57models/cloud_share.go:18 分享密码明文存储并在响应中回传Passwordvarchar(100) 明文入库,未做哈希;GetShare/ListShares 直接把 Password 回传给调用方。一旦数据库泄露,分享密码即暴露。
internal/logic/share/validate_share_password.go:32,43 is_public 在读取链路完全未生效。该校验按 identity 查(无 passport 限制),密码判断为 if share.Password != "" && share.Password != in.Password。对于 is_public=falsepassword="" 的分享,任意持有 identityUUID者都能校验通过。该标志位仅被存储与回传未参与任何访问控制。
internal/config/config.go:35impl/impl.go:23 配置未校验 Databasesconf.NotNil(Spec.Service, Spec.Cache) 仅校验 Service/CacheDatabases 缺失时 with.Databases(config.Spec.Databases, nil) 可能返回 nilDBService 为 nil首个查询即空指针 panic。
internal/logic/*/create_*.goupload_*.go cloud_id/cloud_identity 由请求方自报,未与网关 auth 校验一致。因实际隔离靠 passport_id,该字段被当作装饰性元数据;但 ImportBookmarks 又硬编码为 1/"default"(见 6.2),多处来源不一致,容易被误用。

6.2 正确性与逻辑缺陷

级别 位置 问题
internal/logic/disk/get_dir.go:91get_dir_tree.go:97 根目录 ParentID 空指针解引用 → panic。模型 CloudDiskDir.ParentID*uint(根目录为 nilcreate_dir.go:71 仅当 ParentId>0 才赋值)。GetDir 返回体 ParentId: uint64(*dir.ParentID)GetDirTree 第 97 行均无条件解引用,对任意根目录调用即 panicgRPC 返回 internal 错误)list_dirs.go:53 已做 nil 判断,说明作者知晓该可空性,但此处遗漏。
internal/logic/space/get.go:36-38,97get_by_key_identifier.go:12models/cloud_space.go:12 空间默认 key_identifier="default" 与全局唯一索引冲突,多用户仅首名可用CloudSpace.KeyIdentifieruniqueIndex(全局唯一),而 Space.Get 在无记录时新建并写入字面量 "default"。用户 A 首次调用成功建空间;用户 B 查不到自身空间 → 也尝试建 key="default" → 唯一索引冲突 → Create 失败 → Space.Get 永久报错。即除首名用户外,所有用户空间统计接口不可用
全模块写入路径(upload_file.gocopy_file.gocreate_*.go 等) 容量配额从未校验Space 表虽有 total_storage/max_storage/used_storage,但 Space.Get 只统计不限制;UploadFile/CopyFile 等在写入前没有任何 used_storage >= max_storage 检查。配额是可被无限突破的"展示值"。
internal/logic/disk/move_dir.go:47-49 目录环检测不全MoveDir 仅在 dir.ID == parentDir.ID(直接自环)时拒绝,未检测"把目录移入其自身后代"这类深层环。一旦成环,GetDir/GetDirTree 通过 Preload("Subdirectories") 加载自关联子树时会无限递归(预加载不防环),导致请求挂起或栈溢出。
internal/logic/note/insert_attachment.go:59 返回 Details 为乱码而非数字 IDDetails: string(rune(attachment.ID)) 把整数 ID 当作 Unicode 码点转成单个字符(如 ID=65→"A"),调用方无法据此定位附件。应为 strconv.FormatUint(...)
internal/logic/bookmark/import_bookmarks.go:68-70 导入书签硬编码 CloudID:1, CloudIdentity:"default",与 create_bookmark.go:47 使用请求 in.CloudId/in.CloudIdentity 不一致;导入的书签永远归属到固定云空间,与创建接口语义割裂。
internal/logic/space/get.go:61-67get_by_key_identifier.go:52-58 used_storage 全量加载文件行到内存再累加:先 Find(&files) 取全部文件记录,再 for 循环加 Size。文件量大时内存与 DB 压力显著,应使用 SELECT COALESCE(SUM(size),0)
internal/logic/space/get.go:46-100get_by_key_identifier.go:88-107 读取接口内含写副作用Space.Get/GetByKeyIdentifier 在统计后执行 Save(&space),每次读取都会更新 updated_at 与统计字段(且保存失败仅打印不报错),违背只读语义、放大写竞争。
internal/logic/note/increment_views.go:31 IncrementViewspassport_id 限制,仅本人可自增浏览数;访客/他人打开笔记无法计数,浏览统计实际只能反映"本人打开次数"。
internal/logic/note/delete_attachment.go:34-39 identity 分支为死代码if/else 两个分支都写 note_attachments.id = ? 且都用 in.Id;且 NoteAttachment 无 identity 列,按 identity 删除本就不可能。

6.3 未完成实现

  • 私人资料加密存储未落地:仅有客户端可控的 AES 工具与 is_encrypted 标记,服务端未对落库 data 做加解密(同 6.1。README 宣称"数据加密存储""安全的加密算法"与实际不符。
  • 分享向他人交付资源未闭环:无"凭 share_token/identity 取他人资源"的读取路径,收件人无法通过分享取得内容(同 6.1/6.2)。is_public 形同虚设。
  • 无独立进程入口cmd/cli/main.go 仅为 "Hello World" 桩,模块须作为 gRPC 库经 pkgs/all/pkgs/ecmall 聚合运行README 描述的 cmd/mainmake runswaggerdocker-composetests 等在本仓库均不存在。
  • README 与实现不符README 声称 EXIF 提取、MIME 自动识别、全文搜索、Redis 缓存策略、swagger 文档、健康检查 /health/metrics 等;实际代码中均无对应实现,且 README 内嵌 SQL DDLparent_id BIGINT DEFAULT 0CREATE TABLE-cloud_disk_file 等)与真实 GORM 模型(*uint 可空、表名不同)不一致。

6.4 健壮性与可维护性

级别 位置 问题
list_dirs.go:25-27list_files.go:25-27list_albums.go:25-27list_photos.go:25-27list_notes.go:25-27search_notes.go:25-27list_bookmarks.go:25-27get_private_data*.go:24-29search_private_data.go:23-28 分页 page_size 无上限。各列表均以 if in.GetPageSize() < 10 { in.PageSize = 50 } 兜底,但可传入极大值(如 1000000无上限保护存在一次拉取全量导致的内存/响应膨胀风险。
internal/config/config.go:35 conf.NotNil 未覆盖 Databases,配置遗漏时无清晰报错即进入运行期(同 6.1)。
全模块 无任何单元测试*_test.go 缺失)。本次发现的空指针 panic、唯一索引冲突、乱码返回等问题若有测试极易暴露。
etc/cloud_dev.yaml:14-16 MicroService.Anonymouscloud.ping.hello,但本模块不存在该 service/method(无 ping/hello 实现),属失效配置;当前因无其它匿名项,副作用是所有接口强制鉴权(结果安全,但配置语义错误)。

正向确认:全模块所有 DB 查询均使用 GORM 参数化 Where("... = ?", ...),未发现 SQL 字符串拼接或 db.Exec/db.Raw(fmt.Sprintf(...))(已 grep 验证),无 SQL 注入风险。所有写操作均按 passport_id = auth.ID 隔离,未发现跨用户越权读取他人目录/相册/笔记/书签/私人资料。

7. 风险汇总

编号 级别 问题 影响面
C1 私人资料加密未落地(明文存储、密钥客户端传入、加解密不落库) 隐私数据保密性
C2 分享链路断裂 + CreateShare 不校验资源归属,收件人无法取得资源、可对任意资源建分享 功能不可用 / 潜在越权构造
C3 根目录 ParentID 空指针解引用 → GetDir/GetDirTree panic 接口可用性500
C4 key_identifier 全局唯一索引 + 默认 "default" → 仅首名用户空间可用 多用户空间功能
C5 容量配额仅统计不限制,写入无上限校验 资源滥用 / 存储失控
C6 分享密码明文存储并回传 凭证泄露
C7 is_public 读取链路未生效 访问控制语义错误
C8 配置未校验 DatabasesDB 可能为 nil → 运行期 panic 启动后稳定性
C9 MoveDir 目录环检测不全 → 预加载递归死循环 接口可用性 / 资源耗尽
C10 insert_attachment 返回乱码 ID 调用方无法定位资源
C11 ImportBookmarks 硬编码 cloud_id=1/"default" 数据归属不一致
C12 Space 统计全量加载文件到内存 + 读取接口内含写副作用 性能 / 语义
C13 所有列表 page_size 无上限 大响应 / 资源膨胀
C14 cloud_id/cloud_identity 自报且多来源不一致 数据一致性
C15 IncrementViews 仅本人可自增 统计失真
C16 delete_attachment 死代码分支 可维护性
C17 无单测 可维护性
C18 MicroService.Anonymous 失效配置项 配置正确性

8. 修复建议(务实项)

  1. 私人资料加密C1:若需服务端加密,应在 CreatePrivateData/UpdatePrivateData 落库前用服务端持有的密钥对 Data 字段做 AES-GCM 加密、置 IsEncrypted=true忽略客户端传入的 IsEncryptedEncryptData/DecryptData 应改为对指定 CloudPrivate 记录做加/解密并回写,而非无状态工具(或明确改文档说明其为客户端辅助工具,不在服务端落密)。
  2. 空间唯一索引冲突C4:将 KeyIdentifier 的唯一索引改为 (passport_id, key_identifier) 复合唯一,或在 Get 创建默认空间时使用 utils.UUID() 而非字面量 "default",避免全局冲突。
  3. 空指针 panicC3get_dir.go:91get_dir_tree.go:97 改为与 list_dirs.go:53 一致的 nil 判断(if dir.ParentID != nil { parentId = uint64(*dir.ParentID) },否则置 0
  4. 容量配额C5:在 UploadFile/CopyFile 写入前,按 passport_idCloudSpace,校验 used_storage + size <= max_storage,超限返回明确错误。
  5. 分享闭环C2:为收件人提供"凭 share_token/identity + 密码取资源"的读取路径(资源读取接口支持分享者 passport 或经分享校验放行),并在 CreateShare 校验 resource_id 归属当前用户。
  6. 分享密码C6/C7Password 入库前做哈希(如 bcryptValidateSharePassword 用哈希比对;并在该校验中真正使用 is_public(非公开且要求密码时才校验密码,公开分享直接放行)。响应中不再回传 Password
  7. 目录环C9MoveDir 在移动前递归检查目标目录是否为当前目录的后代,是则拒绝;或限制 GetDirTree/GetDir 的预加载深度。
  8. 小缺陷insert_attachment.go:59 改用 strconv.FormatUint(attachment.ID,10)import_bookmarks.gocreate_* 统一 cloud_id/cloud_identity 来源;列表 page_size 增加上限(如 min(in.PageSize, 200)Space.Get 统计改为 SELECT COALESCE(SUM(size),0) 且不带写副作用(或拆出独立统计刷新接口);config.NotNilDatabases;删除 delete_attachment.go 死代码分支;清理失效的 cloud.ping.hello 匿名配置。
  9. 补测试:针对根目录 GetDir、多用户 Space.Get、大 page_size、目录环、附件 ID 返回等补最小正例/边界用例。

本报告只列与现有实现直接相关的修复项不引入统一框架、抽象层、DTO/VO 或 DDD/CQRS 改造。

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属本机既有问题

编号 级别 问题 处理结果
C1 私人资料加密未落地(明文存储、密钥由客户端传入) 已修复:新增 internal/logic/private/crypto.go,改用服务端密钥对内容做 AES-GCM 加解密后落库、读取时解密;客户端传入的 key 不再充当服务端密钥。密钥只从环境变量 BSM_CloudPrivateKey 读取,未配置或长度不足 16 字节时直接报错——禁止回退到公开默认值(回退等同于假加密)。历史明文记录按原值返回,避免旧数据不可读
C2 分享链路断裂 + CreateShare 不校验资源归属 已修复:建分享前校验目标资源属于 auth.Identity;读取路径打通,收件人可真正取到资源
C3 根目录 ParentID 空指针解引用导致 panic 已修复:GetDir/GetDirTree 先判空再解引用
C4 key_identifier 全局唯一 + 写入字面量 "default" 已修复:取值改为按用户身份唯一,uniqueIndex 改回普通 index,多用户空间可用
C5 容量配额只统计不限制 已修复:新增 internal/logic/disk/quota.go,写入路径校验「已用 + 本次 ≤ 配额」,超限返回明确错误

未纳入本轮范围

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

部署前置(本次新增)

环境变量 用途 未配置后果
BSM_CloudPrivateKey 私人资料内容 AES-GCM 加解密密钥≥16 字节,建议 32 字节) 私人资料写入/读取接口返回内部错误,不再以公开默认密钥「假加密」

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