Immich v3.4.0 候选版解读:安全边界收敛、通行密钥与备份链路变化

举报
yd_43947564 发表于 2026/11/03 14:23:00 2026/11/03
【摘要】 Immich v3.4.0-rc.1 于 2026-10-30 发布,安全类改动 16 项,共享链接的元数据裁剪被收敛到统一映射层;Web 端新增通行密钥登录,数据库备份支持 pigz 并行压缩,JPEG XL 首次成为服务端转码目标。本文按主线归纳变更,给出可验证的判定口径与升级前检查清单。

1. 概述

Immich 在 2026-10-30 发布 v3.4.0-rc.1,这是 v3.4 分支的首个候选版本。上一个稳定版是 2026-10-21 的 v3.3.5,本身即一次带安全公告的补丁发布:上游依赖的证书颁发机构被攻破,叠加 Node.js 当月安全补丁。v3.4 的起点因此已经是一个"先修安全问题"的版本,候选版把这个方向做得更彻底。

按变更主线归纳,v3.4 相对 v3.3.5 的差异集中于四个方向:

主线 v3.3.5 现状 v3.4.0-rc.1
安全 逐路由各自裁剪元数据 裁剪收敛到资产映射层,按共享链接身份统一派生
认证 密码 / OAuth 新增 Web 端通行密钥
备份 每日数据库导出串行压缩 内置备份模块 + pigz 并行压缩(可选)
图片格式 JPEG / WebP 新增 JPEG XL 作为转码目标

候选版不是稳定版。上游在发布说明中要求升级前备份数据库与图库,本文的检查清单按此前提组织。

2. 安全面:从分散校验收敛到统一映射

这是本次改动最密集的部分,共 16 项,几乎由同一名维护者提交。共性是:以前每个路由自行决定"对共享链接返回什么",现在只在一个位置决定。

2.1 共享链接的元数据可见性

POST /search/metadata 向共享链接开放后,响应没有经过共享链接的"显示元数据"开关。结果是一个关闭元数据展示的分享链接,仍能返回完整 EXIF(含 GPS)、原始路径、文件名与所有者 ID。

修复方式是把裁剪逻辑移入 mapAsset:showExif 为 false 时剥离元数据,任何共享链接都隐藏所有者与人物信息。原先分散的 stripMetadata 选项与 mapSharedLink 的 stripAssetMetadata 参数被删除——新增路由不会再因"忘记裁剪"而泄露。

同批的另一项修复针对相同路径:相册对象返回给共享链接访客时,把全部成员的邮箱、名称和头像一起带了出去,而分享页并不使用这些字段。现在对共享链接鉴权的请求返回空成员列表与零贡献者计数。

2.2 会话与授权路径

  • OAuth cookie 注入:POST /oauth/authorize 曾把调用方提供的 state 写入 immich_oauth_state cookie,并把空 code verifier 序列化成字面量 j:null。由于该路由公开,跨站表单提交可在受害者浏览器中植入攻击者选定的值,随后 /oauth/link 会信任它,把攻击者的身份提供方账号绑定到受害者账号。现在只写入服务端生成的值,且不序列化 null。
  • 重复项解析越权:POST /duplicates/resolve 加载重复组时没有按所有者过滤。由于 duplicateId 是可写字段,用户可以给自己的资产打上他人的组 ID,通过"组内有我的资产"检查,再让元数据合并把描述、评分、收藏、位置与标签写到别人的资产上。现在按调用者作用域加载组。
  • 合作伙伴时间线:合作伙伴的 /timeline 请求没有限定为时间线资产,可能返回超出范围的条目。
  • 符号链接:存储层的 crawl 与 watch 均不再跟随符号链接。

2.3 CSP 与模板注入

两项改动影响部署形态而非功能:

  1. 内容安全策略中的内联脚本改为内联哈希,因此可以去掉 unsafe-inline。这对 XSS 是实质性缓解;若在反向代理层做过 CSP 覆写或注入了自定义脚本,升级后需重新验证页面渲染。
  2. 自定义邮件模板与 EXIF 模板变量均做了转义,存储路径检查同时收紧。使用模板变量拼接存储路径的部署应回归一次文件名生成结果。

判定口径很直接:升级后用未登录浏览器打开一条关闭元数据展示的共享链接,抓取 /api/search/metadata 响应,检查 exifInfo、originalPath、ownerId、people 四个字段是否为空或缺失。任一非空即说明裁剪未生效。

3. 认证:Web 端通行密钥

Web 端新增通行密钥登录,入口在用户设置的 Passkeys 页,与密码登录、OAuth 登录一样可在管理员设置中整体开关(config.passkey.enabled,默认开启)。

配置层面有三个要点:

rpId 默认取请求的 Origin 头
config.passkey.domain             显式指定 rpId(反向代理改写 Host 时需要)
config.passkey.additionalDomains  经 /.well-known/webauthn 支持 Related Origin Requests,最多 5 个附加域

通行密钥与域名绑定,改动访问域名等于让既有凭据失效。部署在反向代理后、内外网使用不同域名的环境,应在启用前确定唯一 rpId,再用 additionalDomains 承接其余域名,而不是事后调整。登录与注册均生成一次性 challenge,用于防重放。

移动端尚未实现。发布说明给出的原因是自托管场景下 rpId 难以统一,移动端计划使用固定域名 my.immich.app。

4. 备份链路:内置模块与压缩

两个独立改动,影响不同层面。

4.1 内置备份模块

v3.4 引入备份子系统(backup.beta 开关),以模块形式挂进服务端,前端有配套界面,所有 /api/yucca 请求要求管理员鉴权。底层使用 restic,镜像随附 restic 与并行代理,并提供回滚动作:重启进入维护模式后做快照恢复。

对既有部署的实际意义:容器内需存在 restic 才能启用该能力。实测一套 v3.3.5 部署的镜像内 restic 不存在,因此启用前需自行准备,或维持宿主机层面的备份任务,不要默认它开箱可用。

4.2 pigz 并行压缩数据库备份

数据库备份检测到 pigz 时使用 pigz --rsyncable 占满所有可用核心,否则回退 gzip --rsyncable。输出仍是标准 gzip,恢复流程不变。上游基准为:72 线程 Xeon 上 28 GB dump 从约 35 分钟缩短到约 2 分钟。

该加速对小库几乎没有体感。实测一套约 1.2 万张照片、500 余段视频的部署(图库总量 77 GB,数据库所在根分区占用 45%),每日备份文件约 87 MiB,串行压缩在秒级完成,容器内同样没有 pigz。真正受益的是数据库达数十 GB 的实例;这类实例应先确认 cgroup CPU 配额是否够用,避免备份窗口与缩略图生成争抢核心。

5. JPEG XL:从解码到转码目标

移动端加入 jxl-rs 解码器,使 JPEG XL 在没有系统解码器时也能显示;服务端把 JXL 加为转码目标格式,与 JPEG、WebP 并列。

上游同时标明当前限制,升级前必须知道:

限制 影响
HDR 与渐进式编码待 libvips 8.19 开启 JXL 后,HDR 内容与渐进加载不会按预期工作
FFmpeg 未编译 libjxl 视频缩略图仍输出 WebP

即"支持 JXL"目前是静态图片转码选项,不是全链路替换。存量实例不建议为省空间立刻切换目标格式:切换只影响新生成的转码产物,旧产物不会自动重算,混格式会让后续存储统计与删除策略变复杂。若确实要评估,先在非生产实例上修改 image 配置,抽样比较同画质下的体积与解码耗时。

6. 工作流:缺失属性与 OCR

工作流引擎有两处扩展。

  1. EXIF 过滤新增 missing / present 匹配类型。原先只能按正则匹配值,判断"字段是否存在"要靠凑正则;现在直接选匹配类型。典型场景是按相机 model 缺失来筛选非手机拍摄的导入图片。
  2. 新增 OCR 完成触发器与 OCR 文本过滤器。两者组合可实现"识别到收据关键词即自动加入相册"的自动化。

需注意执行顺序:OCR 是独立队列任务,触发器在 OCR 结束后才触发,长图与多语言识别会拉长端到端延迟。批量导入场景应观察 OCR 队列积压,而不是认为工作流未生效。

7. 性能与索引

v3.3.x 的三次补丁基本都在修人物查询:v3.3.3 改进了 people 查询,v3.3.4 又做了一版更高效的实现,说明第一次未修彻底。v3.4 继续补索引与序列化:

  • asset_face、stack、workflow 增加 updateId 索引,asset 增加所有者与更新序号的复合索引,服务同步与增量拉取。
  • 相册响应不再加载全部相册资产来构造结果,共享链接也不再对全量相册资产做聚合。
  • "最近添加"时间桶改用创建时间范围查询。
  • 搜索只对实际返回的行序列化 EXIF。

对网络存储上的部署,这几项收益最明显:相册接口耗时从"随相册大小线性增长"变成按页取数。

8. 升级前检查清单

候选版升级次序与稳定版一致,但验证要更严。以下命令在服务端容器所在主机执行。

  1. 记录当前状态,作为回滚基线:
docker compose ps --format '{{.Name}} {{.Image}} {{.Status}}'
for c in immich_server immich_machine_learning immich_postgres immich_redis; do
  docker inspect "$c" --format '{{.Name}} status={{.State.Status}} restarts={{.RestartCount}} oom={{.State.OOMKilled}}'
done
  1. 生成并校验一份新的数据库备份,不要复用昨天的文件:
latest=$(find ./backups -name 'immich-db-backup-*.sql.gz' -printf '%T@ %p\n' | sort -nr | head -1 | cut -d' ' -f2-)
gzip -t "$latest" && echo "backup ok: $latest"
  1. 冻结写入(移动端后台备份、批量导入)后再切版本,避免迁移期间产生新资产:
docker compose pull && docker compose up -d
  1. 升级后按"不只看 ping"的口径验证——/api/server/ping 返回 pong 只说明监听器在,不说明媒体可读:
curl -sS "http://<host>:<port>/api/server/statistics"   # 照片与视频计数应与升级前一致
curl -sS -o /dev/null "http://<host>:<port>/api/assets/<id>/thumbnail?size=thumbnail"  # 抽 3 条
curl -sS -o /dev/null "http://<host>:<port>/api/assets/<id>/original"                  # 抽 3 条
  1. 验证安全改动是否生效:用未登录浏览器打开关闭了元数据展示的共享链接,确认 exifInfo、originalPath、ownerId、people 均为空。

  2. 若在反向代理层做过 CSP 覆写或注入自定义脚本,升级后回归页面渲染;unsafe-inline 已被移除。

  3. 回滚条件提前写清:统计计数下降、缩略图或原图任一取不到、容器出现非零重启计数,立即停止升级并回滚镜像标签,不带着残缺状态继续排查。

9. 运维注意

  • 任务并发要跟着硬件走。 3 vCPU 加网络存储的形态下,缩略图 4、元数据 3、视频转码 1、人脸检测 2、智能搜索 2、后台任务 4 是经过验证的配置;升级后若任务排队,先降并发再看硬件,不要先怀疑版本。
  • 不要让应用面对已消失的媒体挂载启动。 存储不可达时服务会退出,日志出现 EHOSTDOWN: host is down 之后常跟一条日志组件的 TypeError——后者是次生错误,真因在前一行。此时重启容器会让它扫描空目录并把资产标记为离线,必须先恢复挂载再启动。
  • 数据库与媒体放在不同存储上。 数据库落在本地卷、媒体走网络存储时,应用崩溃不能反推为升级失败;先确认挂载,再谈版本。
  • 候选版不要跑在主实例上。 v3.4.0-rc.1 是给留足回滚余量的场景用的版本,v3.3.5 仍在稳定线维护。

10. 结论

v3.4 的重心不在新功能,而在把安全边界从"每个路由自觉"改造成"统一映射层强制"。共享链接的元数据泄露、OAuth cookie 注入、重复项跨用户写入这三类问题,是同一结构性缺陷的不同表现;修复方式也统一——把判断搬到唯一的公共出口。

对使用者的收益排序是:安全补丁(应尽快)→ 慢存储上相册接口的响应(明显)→ 通行密钥(取决于域名规划)→ JPEG XL(观察,不急于切换)。备份相关的两项改动要按实际规模判断:几十 GB 以下的数据库看不到 pigz 的差别,内置备份则需先确认 restic 在位再谈启用。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。