Skip to content

[Bug] CDN 从不清理远端孤儿文件,本地已删除的图片仍可通过 CDN URL 公开访问 #47

@Tespera

Description

@Tespera

Gridea Pro 版本

main 分支 HEAD (b84db26)

操作系统

macOS (Apple Silicon)

系统版本

Darwin 25.4.0(问题与平台无关)

优先级

P1(数据生命周期 / 轻度隐私问题)

Bug 描述

backend/internal/service/cdn_upload_service.go:299-388UploadMediaForDeploy 只做"上传本地媒体到远端",从不删除"本地已不存在、远端仍在"的文件

组合场景:

  1. 用户在文章里插了一张敏感图 post-images/leaked.png,部署后走了 CDN(例如 GitHub + jsdelivr)。
  2. 发现问题后删掉文章、删掉本地 post-images/leaked.png
  3. 再次部署。
  4. 本地 HTML 产物里已经不再引用 leaked.png,但:
    • 远端 GitHub 仓库 post-images/leaked.png 依旧存在;
    • jsdelivr CDN https://cdn.jsdelivr.net/gh/user/repo@branch/post-images/leaked.png 依旧可公开访问;
    • jsdelivr 的 CDN 缓存会保留这个 URL 相当长的时间,即便 GitHub 那边删了文件也不会立刻失效。

用户的心智模型是"删除 = 下线",但实际上:

  • 最好的情况——永远下不掉,需要用户手工登 GitHub 删文件 + 申请 jsdelivr purge。
  • 最坏的情况——用户根本不知道文件还在外网能被访问。

对于"分享过链接 → 想撤回"或"上传错图 → 立刻下线"的场景,这是一个真实可复现的信任问题

复现步骤

  1. 配置 GitHub + jsdelivr CDN。
  2. 写一篇文章包含 post-images/test-leak.png,发布。
  3. 复制 jsdelivr URL 在浏览器打开,确认可访问。
  4. 删除本地 post-images/test-leak.png(在应用或文件系统层)。
  5. 再次发布。
  6. 刷新刚才的 jsdelivr URL:图片依然显示。
  7. 打开 GitHub repo 查看对应文件:还在那儿,带着 Gridea Pro 最近一次提交时间。

期望行为

  • CDN 上传任务结束时,识别"远端有、本地没"的孤儿文件。
  • 默认策略:移动到 _trash/<date>/ 前缀下(软删除,保留 30 天),让用户后悔时还能恢复。
  • 可选策略(设置开关):硬删除,并(对支持的 CDN)尝试触发 purge。
  • UI 在部署摘要里显示"清理了 X 个不再使用的图片"。

实际行为

本地删图后远端永远留存,用户无感知,且可能被公开访问。

截图 / 日志

无。

补充信息 — 最佳解决方案

基于本地 CDN manifest 做 diff 清理

前置:先落 issue #45 的 local cdn-manifest.json

流程:

  1. 扫描本地 post-images/images/media/,生成 localSet = { path: sha }
  2. 读取上一次成功部署的 cdn-manifest.json,生成 knownSet
  3. orphans = knownSet - localSet(在上一次部署时存在、本次已不存在的文件)。
  4. 根据设置:
    • softDelete(默认):将 orphan 从原路径 move 到 _trash/<YYYY-MM-DD>/<path>(GitHub 走 Git Data API 一次性 tree 变更)。
    • hardDelete:直接删除。
  5. 硬删除 + 使用公共 CDN(如 jsdelivr)时:
    • 显示警告:"CDN 缓存可能仍可访问该文件长达 12 小时",并给出 purge 按钮(跳转 jsdelivr purge 页面)。
  6. 每次部署自动清理 _trash/ 中超过 30 天的内容。

重要安全提示

  • 此功能首次启用时给用户一个明确的 onboarding:"启用后本地删除的文件会在远端也被清理,确认开启?"
  • 在"测试上传"按钮旁增加"测试清理"按钮,让用户在真正发布前预览会动的 orphan 列表。

额外优化

  • 对其它存储类型(自定义域名 CDN 走 COS/OSS/S3 等),用相同的 manifest diff 逻辑,动作调用对应 API。
  • 考虑提供"孤儿文件"列表 view,让用户可手工决定哪些留哪些删。

影响文件:

检查清单

  • 我已搜索过现有 Issue,确认此 Bug 尚未被报告过。
  • 我正在使用最新版本的 Gridea Pro。

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1中优先级 / 强烈建议处理bugSomething isn't workingsecurity安全相关triage待分诊

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions