Repository navigation
这一版只做一件事:把上一版埋的雷排掉
v5.0.0 把界面做出来了,但审的时候发现七个问题,其中两个会直接毁数据。v5.1.0 全部修完,并在本机实测通过。
严重
1. 回档接口是 GET,且不校验来源(越权写)
GET /beforewrite/api/restore?id=N 会直接改磁盘,而这条前缀路由不带 GUI 的授权。也就是说,任何网页只要写一句
<img src="http://127.0.0.1:3080/beforewrite/api/restore?id=3">浏览器就会替你发出请求,你的文件被静默回滚,页面上什么都不用发生。
现在:回档只认 POST(其余动词 405),并且所有接口都做同源校验(sec-fetch-site 必须是 same-origin/none;带 origin 时要求 host 相等),跨站一律 403。
2. 二进制文件被回档 → 文件报废
快照对任何 ≤2 MiB 的文件都存字节,但回档是「把字节当 UTF-8 文本写回去」。对 PNG / JPG / zip 回档一次,得到的是一堆 U+FFFD,原图永久损坏。
现在:存的时候按内容判定二进制(出现 NUL,或严格 UTF-8 解码失败),打 binary 标记;diff 和 restore 直接拒绝并给出内容块路径,让人工取走;界面上这些行也不再画「回档」按钮。
中等
3. 回档不带版本号 —— fs.writeText(target, text) 是无条件覆盖。回档那一秒如果别的程序刚改过文件,那笔改动无声消失。现在先 stat 取 version 再带 expected 写入,冲突就明确报错「文件刚被别处改过,再点一次」。
4. 诊断在 256KB 处静默断线 —— debug.log 写满 256KB 后直接 return,之后一条都不写,排查时线索凭空消失。现在滚动成 debug.log.1 继续写。
5. 时间轴一次返回整条会话 —— 几百上千条事件全丢给浏览器渲染。现在默认返回最近 400 条,附 total 与 truncated,界面明确写出「更早的 N 条没画出来」。
轻
6. 对不存在的路径打点仍报成功 —— 现在明确说明「这个路径现在不存在,已记下这件事,但没有内容可回滚」。
7. 快照同步写盘阻塞事件循环 —— 最多 2 MiB 的 writeFileSync 改成 fs/promises 异步写。
实测
node --check两个文件通过,离线冒烟测试全绿(含「rm掉文件后从快照救回」这条,返回operation=create)。- 本机重启后实测:
ping返回version:"v5";GET /restore→ 405;带Origin: https://evil.example的请求 → 403;timeline带total/truncated。
接口变更(如果你写过调用)
| 之前 | 现在 |
|---|---|
GET /beforewrite/api/restore?id=N |
POST /beforewrite/api/restore?id=N |
timeline 返回全部 |
timeline?sid=…&limit=…,默认 400,附 total/truncated |
English
v5.1.0 is a hardening release. Two of the seven fixes are data-safety critical:
- The restore endpoint was a plain
GETwith no origin check, so any web page could roll local files back with an<img>tag. It is now POST-only and same-origin-checked (403 otherwise, 405 for other verbs). - Restoring a binary snapshot wrote raw bytes back as UTF-8 text, destroying images. Binary content is now detected at snapshot time, flagged, and refused by both
diffandrestorewith the blob path handed back instead.
Also: restores carry an expected version (concurrent external edits are reported, not overwritten), debug.log rotates instead of going silent at 256 KB, the timeline API is capped at the 400 most recent rows and reports total/truncated, manual snapshots of non-existent paths say so, and blob writes are asynchronous.