Releases: KJoner/litesync
Release list
0.17.0
正式版本。 自 0.15.1 以来的两个大阶段(多租户基座与隐私增强)在六轮
预发布中完成了三平台实测验收(Windows + iOS + Web,另有 macOS/Linux/Android
的 CI 实机矩阵),本版为其稳定形态。配套服务端 v0.17.0。
新能力
隐私增强(可选,默认关闭)
- 大小混淆:文件按填充桶上传(LSE4 信封),服务器只能看到桶大小——
精确字节数不再泄露「这是哪份已知文档」「谁在什么时候写了多少」。
成本在设置页如实标注(最坏 +12.5%,小文件按 4KB 计)。 - 时间混淆:上传对齐到时间窗口发车(默认 5 分钟,平均一个窗口、
最多约两个窗口),定时/前台/启动轮次只拉不推——服务器只能判断编辑
发生在哪个窗口里;mtime 按小时取整后上报。「立即同步」不受影响。
分享
- 内嵌图片随分享一起加密打包(服务器仍只见单个密文 blob),查看页本地
解密渲染;离线查看器补齐解帧并加了页面级 CSP(笔记里的外链图片
不再能把查看者 IP 报给第三方)。 - 有效期支持「自定义…」(分钟/小时/天);运维页可撤销分享、
设置新的到期时间。
运维
- 运维页设备列表显示设备名称、平台、客户端版本与最近来源 IP
(转发头只在可信代理时采信);设备被撤销后,被撤销端会收到常驻提示
并停止无意义重试。 - 首次配置三选项:远端未初始化时「测试连接」直接给出
关闭 / 立即同步 / 添加 E2EE 并立即同步(空仓启用无迁移,
首次上传即密文,明文从头到尾不经过服务器)。 - 每请求上报客户端版本与平台:不可逆迁移前「所有设备都升级了吗」
是一个可查的事实。
实测验收修掉的问题(RC 系列)
- 改名的身份连续性:改名后立刻编辑、纯大小写改名、改名到「曾存在后删除」
的名字、两设备并发改名——名字、内容、版本历史三样都对,对象身份
永不重置(INV-05)。 - E2EE 冲突解决死循环与删除重建产生冲突副本(内容世代以认证后的
远端值为下限)。 - 明文模式的改名检测(v6 改名不发删除,拉取端按 fileId 对账,
旧文件不再残留成孤儿)。 - Web 只读端补齐 LSE4 解密。
- 元数据迁移中止后的「伪名回声」不再被误当成改名。
升级
- 先升服务端:
docker compose pull && docker compose up -d - 再升所有设备的插件(协议 v6)
- 隐私增强按需在设置页开启;已有文件在下次修改时自然转入新信封
测试
插件 289 条 / 服务端 210+ 条自动化全绿(含崩溃点注入、对抗性协议、
跨平台文件系统语义、跨租户越权矩阵);验收手册第 1–5 章人工实测通过。
已知限制与剩余威胁见 README「Known limitations & remaining threats」。
0.17.0-rc.4
预发布版本。请勿在唯一的真实 Vault 上使用。
rc.3 的第二轮实测(Windows + iOS + Web)又揪出一批问题,这一版全部修复,
并补上两组运维增强。需要配套服务端 v0.17.0-rc.5(Web 端 LSE4 解密、
设备平台/IP 记录、运维页分享撤销都在服务端仓)。
提醒:上一轮实测的「分享页 LSN2 乱码」是部署版本问题——线上仍在跑
rc.3 的 Web 资产。请确认 compose 没有固定旧镜像标签,升级到 rc.5。
修了什么
改名后版本历史从 1 重新开始(实测 T3.2 / T3.5)
「改名后立刻编辑」时,编辑事件会把队列里的改名操作覆盖成普通上传——
新路径于是被当成全新对象从零建档,身份被重置、历史断裂(正打开在编辑器里的
笔记被改名时,Obsidian 也会顺手触发一次编辑事件,纯改名同样中招)。
现在改名操作不会被后续编辑覆盖:内容变更由推送时的「先改名、再把新内容传到
同一个对象上」处理,名字、内容、身份、历史四样都对。
时间混淆形同虚设(实测 T4.5)
窗口约束此前只加在「修改防抖」一个触发源上,而每轮同步都是拉推一体——
默认 30 秒的定时同步每次都会把队列顺手推走,混淆等于没开。
现在定时/前台/启动/重试的轮次只拉不推,上传只发生在窗口发车点或用户
显式动作。「立即同步」照旧立刻生效;拉取完全不受影响。披露文案同步修正:
延迟平均约一个窗口、最多约两个窗口。
设备被撤销后静默失败(实测 T5.2)
被撤销的设备此前把 401 当成普通网络错误无限退避,移动端连状态栏都没有,
用户毫无感知。现在凭据被拒会弹出常驻通知(不点不消失)说明「设备凭据
已失效或被撤销,请重新接入」,停止无意义的自动重试;重新接入后首次同步
成功自动恢复。
Web 只读端不认 LSE4(实测 T4.4,服务端仓)
开启大小混淆后的新文件在网页端显示乱码——Web 端没有实现 LSE4 解密。
已补齐(含 flags 认证与去填充),密钥世代与防回退检查同步覆盖 LSE4。
并发改名的收敛提示(实测 T3.4)
两台设备把同一文件改成不同名字时,收敛是对的(后改名的一方生效、同一身份、
不产生假删除),但过程是静默的。现在会有一条提示说明另一台设备的改名
已被采纳为基础。
新增
- 运维页设备列表:显示设备名称、平台(windows/ios/…)、客户端版本与
最近来源 IP。IP 只在直连方是可信代理(OBSYNC_TRUSTED_PROXIES)时
采信转发头,客户端伪造不进库。 - 分享:有效期支持「自定义…」(分钟/小时/天);运维页 Shares 卡片
新增「撤销」,「延长」改为「设置新的到期时间」(预设 + 自定义)。
已撤销的分享依旧不能通过设置到期时间复活。
测试
插件 289 条全绿(较 rc.3 新增 5 条回归:move 不被编辑覆盖、时间混淆只拉
不推、凭据被拒停机与恢复等);服务端全量通过并新增 4 条(平台/IP 记录、
伪造 XFF 不采信、可信代理链解析、运维撤销分享的完整语义)。
0.17.0-rc.3
预发布版本。请勿在唯一的真实 Vault 上使用。
rc.2 的五平台实测(Windows + iOS + Web)暴露了一批真实缺陷,这一版全部修复,
并新增首次配置的三选项接入。需要配套服务端 v0.17.0-rc.4(分享附件的查看端
与 restore 响应字段在服务端仓)。
修了什么
冲突解决死循环(实测 T2.3)与删除重建出冲突副本(实测 T2.2)
同一个根因:E2EE 上传的内容世代一律从本地缓存推导。对端在本机离线期间推高过
世代的话,合并保存必然被服务器按「世代不增」拒绝——而重试并不会改变本地缓存,
每一次都撞同一堵墙;三次自动合并失败后落到保底路径,凭空多出一个冲突副本。
现在「基于刚下载的远端内容上传」的场景(合并保存、自动合并、删除后重建的恢复
重传)都以刚下载并经认证的远端世代为下限。删除后重建不再产生冲突副本——
它是一次干净的恢复:同一对象身份、revision 连续、删除前的历史仍然可见。
改名被当成「新建」,旧文件成为孤儿(实测 T3.2)
协议 v6 的改名只发一条「新路径 + 同 fileId」的变更,不发删除——但只有元数据
加密模式的拉取端会按 fileId 认出改名。明文模式把它当新文件下载,旧文件永远
没人清理,另一台设备上出现新旧名各一份。
现在明文模式同样按 fileId 对账,顺带修复了三个相邻缺陷:
- 纯大小写改名(
note.md → Note.md)在大小写不敏感的设备上不再被误判
「目标被占用」而卡死——用两步探测消歧,中途崩溃可自动恢复; - 两台设备并发改成不同名字:本地的改名意图会在对端改名落地后自动换基,
作为同一对象的正常改名推送——收敛到一个名字,不产生假删除、不复制内容; - 快照对账中的改名不再退化为「回收站 + 全量重新下载」(2GB 的视频改个名
不该重新传 2GB)。
分享的图片终于显示了(实测 T2.4)
以前分享包里只有那一篇笔记的密文,内嵌图片的字节根本没进包——查看页只能显示
文件名。现在创建分享时把笔记内嵌的图片一起加密打包(总预算 64MB,超出的单张
跳过并提示),查看页在本地解密渲染。服务器看到的仍然只是一个密文 blob:
附件的数量、名字、内容都在密文里。
旧分享没有附件,图片位置仍是文字——重建一次分享即可。
顺带修了离线查看器的两个问题:0.13.3 之后创建的分享会在正文开头渲染出帧头
垃圾(漏了解帧);页面没有 CSP,笔记里的外链图片会把查看者 IP 报给第三方
(现在一律不加载外部资源)。
首次配置的三选项(新功能)
第一台设备填好地址与 Token 后点「测试连接」,如果远端仓库还是空的,不再只弹
一条成功消息,而是当场给出下一步:
- 关闭 —— 什么都不做;
- 立即同步 —— 初始化并开始首轮同步;
- 添加 E2EE 并立即同步 —— 先设加密密码再初始化。远端是空的,没有迁移
过程:首次上传就是密文,明文从头到尾不经过服务器。
远端已有内容或已启用加密时不弹(那是第二台设备的接入,走原有向导)。
元数据迁移 abort 后的回声(代码核查发现)
修复 T3.2 引入的新改名检测有一个盲区:路径加密迁移中止后,迁移期间产生的
伪名化变更会被当成真实改名,把本地文件改名成 32 位十六进制。已修复并有
回归测试锁住——这类变更按对象的真实路径应用、按伪名寻址服务器。
测试
插件 286 条(新增 16 条回归,覆盖上述全部修复路径)、服务端全量通过。
验收手册的 T1.1 / T2.2 / T2.4 描述已同步更新。
0.17.0-rc.2
预发布版本。请勿在唯一的真实 Vault 上使用。
rc.1 的 iOS 与 Android 实机自检已完成,两份报告都是 0 条不安全。
这一版修掉的是那次实测暴露出来的自检判定逻辑本身的缺陷。
修了什么
自检的判定逻辑写错了
rc.1 的报告顶部会挂着「
没有一条是危险的——错的是判据。
原实现用「实测 == 预言」的严格相等。这个判据是错的:
规则比现实更严完全安全,只是多拦一次。
真正危险的只有一个方向:规则比现实宽松——我们以为两个名字不同、
文件系统认为相同,于是后写的静默覆盖先写的。
iOS 的文件系统是大小写敏感的,我们的规则仍判碰撞,也就是比现实更严——
最安全的方向,却被标成了红色。而一份大部分是红色的报告等于没有报告:
真出问题的那一行会被淹掉。
现在是三档判定:
| 判定 | 含义 |
|---|---|
| ✓ 安全 | 规则与现实一致,或比现实更严 |
| △ 受限 | 设备接受某个名字而规则拒绝它 → 该文件在部分设备上不同步,不丢数据 |
| ✗ 不安全 | 规则比现实宽松 → 可能静默覆盖。只有这一类需要立刻处理 |
碰撞类与可创建性类分开判定:后者的危险方向是反的(规则接受而设备建不出来 →
写入会失败),原来两类混用同一个判据也是错的。
报告里新增「判定的含义」一节——读报告的人得知道该不该慌。
rc.1 实测带来的其他更新
README 用实测数据替换了推测
iOS 与 Android 在两项关键行为上完全相反:
| iOS | Android | |
|---|---|---|
| 大小写 | 敏感 | 不敏感 |
| Unicode 归一 | 会归一 | 不归一 |
这正好是「碰撞规则为什么不按平台分支」最有力的论据——按本地平台判断,
这两台设备会对「两个名字是不是同一个文件」给出相反答案,然后互相覆盖。
我们的规则取所有平台里最严的一套(小写化 + NFC 归一 + 去尾随点空格),
因此在两台行为相反的设备上都是「一致或更严」。
原子替换那条也改成实测结论:已测的五个平台(Windows / Linux / macOS /
iOS / Android)全部支持原子安装,因此退化路径是安全网而不是日常行为。
跨平台 CI
新增 ubuntu / macos / windows 三平台矩阵,每次推送都在真实文件系统上验证
路径规则。这一路上抓到一个只在 macOS 的 bash 3.2 上出现的解析 bug——
$VAR 后紧跟全角字符时,变量名会把那个字符的首字节吃进去。靠推理是想不到的。
rc.1 已包含、此版沿用
- 大小混淆(LSE4 信封 + 填充桶,默认关闭)
- 时间混淆(上传对齐窗口 + 抖动,mtime 量化,默认关闭)
- 不可原子替换平台的退化路径修复(远端更新此前根本收不到)
- 每个请求上报
X-Client-Version,服务端obsync migration preflight据此
确认「所有设备都升级了吗」
已知限制
- 大小混淆不覆盖元数据信封(LSM1)与分享名信封(LSN1)
- 时间混淆不隐藏「你在某个窗口里到底有没有编辑」——那需要持续的掩护流量,
本插件不做,也不暗示自己做了 - LSE4 需要 v0.17+ 客户端才能读。开启大小混淆前请确认所有设备都已升级
- 网络盘 / 云同步盘仍不支持作为 Vault 位置
安装
用 BRAT 添加 KJoner/litesync
并勾选包含预发布版本。服务端对应 v0.17.0-rc.1。
0.17.0-rc.1
预发布版本,用于 iOS 实机验证。请勿在唯一的真实 Vault 上使用。
这个 RC 的唯一目的,是跑掉 v0.14 stable 仅剩的两条发布门槛中属于 iOS 的那部分。
装好之后要做的一件事
打开命令面板,执行 「LiteSync: 平台兼容性自检」。
它会在 Vault 里生成一篇 LiteSync 平台自检 <时间戳>.md。把那篇笔记的内容贴回来即可。
自检做什么:在 iOS 的真实文件系统上跑一遍计划书 §8.4 的路径用例矩阵——大小写、
Unicode NFC/NFD、尾随点与空格、CON/AUX/NUL、超长文件名,以及能否原子替换。
报告的每一行同时列出本机实测与规则预言并标出是否一致。只输出「规则怎么说」
等于什么也没验证——要的恰恰是发现某条跨平台假设在 iOS 上是错的。
自检全程只在插件自己的 .probe/ 目录里操作,不碰你的任何笔记,跑完自动清理。
为什么需要真机
tests/realfs.test.ts 会在真实文件系统上验证这些行为,但它依赖 Node——
iOS 上没有任何办法执行它。于是移动端那一格长期只能靠推理填。
而推理错的方向恰好是最危险的那种:我们以为两个名字不同,文件系统认为相同,
后写的静默覆盖先写的。
本版包含的内容(v0.15.1 → v0.17.0-rc.1)
隐私增强(计划书 §11,全部默认关闭)
- 大小混淆:新增 LSE4 信封,把密文填充到桶边界。精确文件大小本身就是内容:
已知文档的字节数命中即确认,同一文件的大小序列画出的是编辑节奏。
最坏开销 12.5%,小于 4KB 的对象按 4KB 计。可按路径前缀只对高敏目录开启。 - 时间混淆:上传对齐到时间窗口网格并加抖动,
mtime按粒度量化。
请求节奏本身就是一份打字记录——作息、时区、工作日与假期都能从中推断。
代价是同步延迟最多一个窗口,跨设备可见延迟与冲突窗口都会变长。
「立即同步」不受影响。
修复
- 不可原子替换的平台上,远端更新根本收不到。 覆盖被拒后走的是
「本地变了 → 合并 → keep-both」,而 keep-both 的最后一步仍是覆盖,同样被拒。
净结果:远端版本哪儿都没写,本地原地不动,却多出一个存着本地内容的副本。
现在改为:远端版本另存为新文件,本地保持原样,两份都在盘上由用户核对。 - 没有 rename 的平台连新文件都收不到。 新建的目标路径上什么都没有,
直接写毁不掉任何东西,但代码一律走 staging→rename。已修。 - 原子性探测改为校验改名后的内容而非仅「目标存在」,且不再跳过桌面端
(网络盘、同步盘挂载的 Vault 就在桌面上)。
其他
- 每个请求上报
X-Client-Version,服务端obsync migration preflight据此
确认「所有设备都升级了吗」——这是不可逆迁移的前置条件,此前只能靠人挨个问。 - 新增「平台兼容性自检」命令(见上)。
已知限制
- macOS 与 Android 未经实机验证。 本 RC 只用于跑 iOS 那一格。
- 大小混淆不覆盖元数据信封(LSM1)与分享名信封(LSN1)。
- 时间混淆不隐藏「你在某个窗口里到底有没有编辑」——那需要持续的掩护流量,
本插件不做,也不暗示自己做了。 - LSE4 需要 v0.17+ 的客户端才能读。开启大小混淆前请确认所有设备都已升级。
安装
用 BRAT 添加 KJoner/litesync
并勾选包含预发布版本。服务端需要同步升级到对应版本。
0.12.0
0.11.0
0.11.0:v9 三阶段一/二期——fileId 稳定身份 + 原子 MOVE + LSE3 信封(协议 v4) 一期(fileId 奠基 + 原子改名): - 队列升级为结构化 op(upsert/delete/move,持久化兼容旧格式) - rename 事件(含文件夹子文件)入队原子 MOVE;内容变更/409/404/422/ 旧服务器一律安全回退 delete+upsert;扫描不覆盖排队中的 move - FileState 记录服务器分配的稳定 fileId 二期(LSE3 + E2EE 原子 MOVE): - LSE3 信封:magic|keyEpoch|contentGeneration|iv|ct,AAD 绑定 vaultId+keyEpoch+fileId+generation——路径退出 AAD(E2EE 改名走 原子 MOVE 无需重加密);generation 单调递增,HEAD 下载回退即硬失败 (恶意服务器无法把旧版本密文当最新重放),历史版本下载豁免 - 新文件 fileId 客户端预生成(加密前确定身份),随上传头交服务器 - fileId/generation 贯通全部 store.set 站点;E2EE 下 tracked 为 LSE3 的文件放行 move;迁移与信封升级命令直接产出 LSE3 - 新增 LSE3 密码学矩阵与 decode 层回退防护注入测试 协议 v4(配套 litesync-server 0.11.0);测试 104/104,lint 0 errors。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.10.0
0.10.0:v9 二阶段——设备级凭据 + LSE2 信封(协议 v3) - 设备凭据(审查 14):根 Token 首轮同步自动换发本设备专属凭据 (SecretStorage),配对包 v2 只携带一次性注册凭据、根 Token 绝不下发; 导入端 /enroll 换取设备 token(v1 旧包兼容);新增「设备列表」命令 - LSE2 信封(审查 7):AAD 绑定 vaultId+keyEpoch+path,拒绝跨 vault / 跨密钥世代密文重放;写入一律 LSE2、读取兼容 LSE1;E2EE 迁移直接产出 LSE2;新增「升级加密信封」命令批量重加密(密文→密文,断点续传) - bootstrap 记录 keyEpoch;协议检查采纳 keyEpoch 并首见固定 vaultId - 设置页非本机 http:// 即时警告 - 协议 v3(配套 litesync-server 0.10.0);测试 95/95,lint 0 errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.9.0
0.9.0:v9 一阶段架构加固(P0 全量修复,协议 v2) 对照《一阶段架构审查》完成客户端全部 P0 与可落地 P1: - P0-3 Remote-Wins:trashLocal 返回值硬校验,回收站失败绝不覆盖本地 - P0-4 本地 CAS:pull 下载 / auto-merge 写回 / keepBoth / Resolver 保存 统一走 writeIfLocalUnchanged,下载期间的新编辑绝不被远端覆盖 - P0-5 防复活:base-0 撞墓碑先比对 priorHash(E2EE 下载历史版本比对明文), 陈旧副本不上传并登记 pendingDelete;同名新内容基于墓碑 revision 显式重建 - P0-6 状态双副本:state-a/b(generation+checksum)写非活动副本→读回校验, 单份损坏自动回退,两份全坏停机保护,绝不 starting fresh - P0-7 E2EE:启用后收到明文硬失败停止同步;迁移走服务器状态机 (begin 冻结明文写 → 快照清单 → complete 全量验证);vault-key 替换带 CAS 指纹 - P0-8 repoEpoch:变化即停自动同步、重置接入引导「安全合并」; 另加同 epoch 内 head 落后于游标的防御性 snapshot 对账 - P1-10 队列 generation 防 lost wake-up + pendingOps 持久化(重启不丢) - P1-11 blocked changes 持久化重试(文件夹同名不再静默 ACK) - P1-15 非 loopback http:// 在 API client 与配对导入两处硬拒绝 - P1-18 冲突副本名加随机后缀 + 存在性循环 协议升级到 v2(配套 litesync-server 0.9.0,minProtocolVersion=2)。 测试 89/89,lint 0 errors。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0.8.1
智能合并引擎(frontmatter 字段级、列表 union、token 级合并)和新版 Resolver 建议 UI