dsh-rewind-plugin 0.20.0-alpha.4 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +16 -20
- package/README.md +17 -20
- package/SECURITY.md +80 -206
- package/lib/client.js +417 -387
- package/lib/types/client/action.d.ts +40 -0
- package/lib/types/client/hidden.d.ts +2 -2
- package/lib/types/client/index.d.ts +14 -11
- package/lib/types/client/locales.d.ts +17 -21
- package/lib/types/client/log.d.ts +1 -1
- package/lib/types/client/mount.d.ts +79 -0
- package/lib/types/client/pending-mount.d.ts +36 -0
- package/lib/types/client/pending.d.ts +43 -57
- package/lib/types/client/popover.d.ts +19 -19
- package/lib/types/client/retract.d.ts +32 -0
- package/lib/types/client/rewind-flow.d.ts +86 -0
- package/lib/types/client/styles.d.ts +5 -14
- package/lib/types/snapshot.d.ts +1 -1
- package/package.json +59 -59
- package/lib/types/client/portals.d.ts +0 -301
package/README.en.md
CHANGED
|
@@ -15,7 +15,7 @@ A deliberately focused plugin with one job: **rewind to any user message, no mat
|
|
|
15
15
|
|
|
16
16
|
- **In-place rewind, no new session** — the target message and everything after it (replies, tool calls) are withdrawn from the model context *and* the rendered transcript, and the target's text is filled back into the composer for editing and re-sending. It all happens inside the original session: no new branch, no leftover copy.
|
|
17
17
|
- **Conversation and files rewind together** — Claude Code-aligned behavior: files edited by the write tools are tracked and restored by the same rewind, and external changes to already-tracked files are restorable too. **Partial tracking, before-write backup, store only on change**, with no git dependency.
|
|
18
|
-
- **Safety guarantees** — the session log only ever appends rewind markers and is never deleted or rewritten; file backup and restore are hardened; tests are comprehensive, and maintenance continues across DSH upgrades. Security model: [
|
|
18
|
+
- **Safety guarantees** — the session log only ever appends rewind markers and is never deleted or rewritten; file backup and restore are hardened; tests are comprehensive, and maintenance continues across DSH upgrades. Security model: [docs/security-model.md](docs/security-model.md).
|
|
19
19
|
|
|
20
20
|
## Preview
|
|
21
21
|
|
|
@@ -34,19 +34,17 @@ Every user message carries a **↶ rewind** button in its action row. Clicking i
|
|
|
34
34
|
|
|
35
35
|
## Install
|
|
36
36
|
|
|
37
|
-
Check your local DSH version, then find the matching plugin version in
|
|
38
|
-
[Releases](https://github.com/SiriLee/dsh-rewind/releases).
|
|
39
|
-
|
|
40
37
|
**Command line**:
|
|
41
38
|
|
|
42
39
|
```sh
|
|
43
|
-
dsh plugin --profile <name> add dsh-rewind-plugin
|
|
40
|
+
dsh plugin --profile <name> add dsh-rewind-plugin
|
|
44
41
|
```
|
|
45
42
|
|
|
46
|
-
**Graphical interface**: the **Plugins** page in the sidebar → **Add plugin** → enter `dsh-rewind-plugin
|
|
43
|
+
**Graphical interface**: the **Plugins** page in the sidebar → **Add plugin** → enter `dsh-rewind-plugin` → install → restart DSH.
|
|
44
|
+
|
|
45
|
+
<img src="assets/screenshots/add-plugin.png" width="600" alt="Add plugin dialog: enter dsh-rewind-plugin in the field"><br>
|
|
47
46
|
|
|
48
|
-
|
|
49
|
-
<sub>The screenshot is an example only — use the version that matches your DSH version.</sub>
|
|
47
|
+
The default install is `latest`, which targets the latest stable DSH release. If you are on an older or alpha DSH version, find the matching plugin version on the [Releases](https://github.com/SiriLee/dsh-rewind/releases) page and install that version explicitly.
|
|
50
48
|
|
|
51
49
|
> [!WARNING]
|
|
52
50
|
> The npm name `dsh-rewind` belongs to another author's package — install with `dsh-rewind-plugin`.
|
|
@@ -130,21 +128,21 @@ The whole design rests on two principles, simple but deliberate: **the conversat
|
|
|
130
128
|
|
|
131
129
|
### 2. File restore: lightweight checkpointing, "before-write backup"
|
|
132
130
|
|
|
133
|
-
The file half follows Claude Code's checkpoint semantics — **partial tracking + before-write backup, plus a re-scan of tracked files at each message**, not a whole-tree snapshot. This trade-off saves space
|
|
131
|
+
The file half follows Claude Code's checkpoint semantics — **partial tracking + before-write backup, plus a re-scan of tracked files at each message**, not a whole-tree snapshot. This trade-off saves space while still covering external changes to tracked files:
|
|
134
132
|
|
|
135
133
|
- **Before-write backup**: tracks only the write-class tools (`write`, `edit`) — backs up the original content before a write and records/tracks the files it touches; it never backs up the whole workspace, so it's lightweight; **a single file that is too large is not backed up**.
|
|
136
134
|
- **External changes count too**: at every user-message boundary the plugin re-checks all tracked files — external changes such as a command run or a manual edit are recorded as well and restored by a later rewind. "Lightweight" but not "incomplete".
|
|
137
135
|
- **Unchanged-not-recorded**: an entry is written only when something changed — at the message-boundary re-check, an unchanged file is never backed up (no record); at before-write time, when the new content matches the path's prior record, only a **link to it** (`ref`) is stored instead of a copy.
|
|
138
|
-
- **Accurate restore**: backups are the sole standard, checked against the real disk — **only files that actually differ are touched**. Backups are stored byte for byte, so the restored result matches the backups exactly, with no "ghost impact".
|
|
136
|
+
- **Accurate restore**: backups are the sole standard, checked against the real disk — **only files that actually differ are touched**. Backups are stored byte for byte, so the restored result matches the backups exactly, with no "ghost impact". Coverage is bounded by design: [File-rewind tracking boundary](docs/compat/tracking-boundary.md).
|
|
139
137
|
- **Safety and integrity**: paths are sanitized so nothing ever escapes the backup root; symlinks / hard links are skipped so one restore can't clobber another name of the same file; a per-file failure never aborts the pass; backups and the restore journal are written atomically and kept across restarts, so a half-applied restore after a crash can be continued or rolled back.
|
|
140
138
|
|
|
141
139
|
> **Design highlight**: this checkpoint's light footprint comes from **recording only what was actually touched and really changed** — before-write backup makes it restorable, unchanged-not-recorded and content-as-link drop the repetition; only the files that differ are touched at restore time.
|
|
142
140
|
|
|
143
|
-
##
|
|
141
|
+
## Scope and division of labour
|
|
144
142
|
|
|
145
|
-
This plugin deliberately stays lightweight and focused on one thing — "conversation rewind". The following are **out of its scope
|
|
143
|
+
This plugin deliberately stays lightweight and focused on one thing — "conversation rewind". The following are **out of its scope**, and each has a better owner (see [design boundaries](docs/boundaries.md)):
|
|
146
144
|
|
|
147
|
-
- **Whole-tree / Git-level snapshots** — only write-class tool edits plus external changes to already-tracked files are backed up; files never touched by a tool are not restored. For a worktree-level full snapshot rollback, use
|
|
145
|
+
- **Whole-tree / Git-level snapshots** — only write-class tool edits plus external changes to already-tracked files are backed up; files never touched by a tool are not restored. Workspace history — any file, any moment — belongs to Git; the agent already uses it, and the plugin does not rebuild that layer. For a worktree-level full snapshot rollback, use Git or the snapshot tool the project already uses.
|
|
148
146
|
- **Subagent edits** — not tracked, and no rewind inside a subagent session (same as Claude Code): a subagent runs its own session, so its backups could never be restored by a rewind of the parent session, and none are kept for one.
|
|
149
147
|
- **Fork / branch rewind** — DSH already provides this ("branch in new chat"); no need to reinvent the wheel.
|
|
150
148
|
|
|
@@ -158,19 +156,17 @@ This plugin deliberately stays lightweight and focused on one thing — "convers
|
|
|
158
156
|
> This project and DeepSeek Harness are both in developer preview. Pin exact
|
|
159
157
|
> versions in reproducible environments and review the behavior notes above.
|
|
160
158
|
|
|
161
|
-
## Known
|
|
159
|
+
## Known limitations
|
|
162
160
|
|
|
163
|
-
1. **
|
|
164
|
-
2. **
|
|
165
|
-
3. **Turn navigation keeps rewound turns** — the right-side turn navigation keeps an "unloaded" tick for each rewound turn, and hovering previews the withdrawn content. Only a display difference; no functional impact.
|
|
166
|
-
4. **A far rewind may show a blank transcript** — when the target predates the loaded history, the conversation area goes blank; click **Load earlier** to page back until the history appears.
|
|
161
|
+
1. **Turn navigation keeps rewound turns** — the right-side turn navigation keeps an "unloaded" tick for each rewound turn, and hovering previews the withdrawn content. Only a display difference; no functional impact.
|
|
162
|
+
2. **A far rewind may show a blank transcript** — when the target predates the loaded history, the conversation area goes blank; click **Load earlier** to page back until the history appears.
|
|
167
163
|
|
|
168
164
|
> [!NOTE]
|
|
169
165
|
> Browser diagnostics are available; see [Browser diagnostics](docs/compat/diagnostics.md).
|
|
170
166
|
|
|
171
167
|
## Security
|
|
172
168
|
|
|
173
|
-
This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and files"; backups are stored under `<dsh home>/rewind-snapshots/`; restores draw only from those backups. It never touches your git repository, makes no network requests, and accesses no credentials. For sessions you've left inactive for a long time, a snapshot auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [SECURITY.md](SECURITY.md).
|
|
169
|
+
This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. Workspace files are written only when you choose "conversation and files"; backups are stored under `<dsh home>/rewind-snapshots/`; restores draw only from those backups. It never touches your git repository, makes no network requests, and accesses no credentials. For sessions you've left inactive for a long time, a snapshot auto-cleanup (off by default) can remove their snapshot directory in whole, leaving the active session and the chat log untouched. Full security model: [docs/security-model.md](docs/security-model.md); to report a vulnerability, see [SECURITY.md](SECURITY.md).
|
|
174
170
|
|
|
175
171
|
## Development
|
|
176
172
|
|
|
@@ -185,7 +181,7 @@ node scripts/verify-host.mjs # end-to-end verification of the built artifact
|
|
|
185
181
|
|
|
186
182
|
`prepack` runs the full build, so `npm pack` / `npm publish` always ship a complete `lib/`. Install from npm or a packed tarball — a git checkout has no built `lib/`.
|
|
187
183
|
|
|
188
|
-
Maintainers: the
|
|
184
|
+
Maintainers: the source layout and architecture are in [docs/architecture.md](docs/architecture.md); the harness interface reference is in [docs/harness-reference.md](docs/harness-reference.md).
|
|
189
185
|
|
|
190
186
|
Contributing guide: [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
191
187
|
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
15
15
|
|
|
16
16
|
- **就地消息回退**——目标消息及其之后的内容同时从**模型上下文**和**渲染对话**中撤回,目标消息文本自动回填输入框。始终在原会话内完成,不新建分支、不残留副本。
|
|
17
17
|
- **轻量备份还原**——对齐 Claude Code:追踪写类工具编辑过的文件,回退时一并还原,已跟踪文件的外部变更也能还原。**局部追踪、写前备份、不变不存**,不依赖 git。
|
|
18
|
-
- **安全保障**——会话日志仅追加回退标记,从不删改;文件备份与还原经安全加固;**完备测试**,随 DSH 升级长期维护。安全模型:[
|
|
18
|
+
- **安全保障**——会话日志仅追加回退标记,从不删改;文件备份与还原经安全加固;**完备测试**,随 DSH 升级长期维护。安全模型:[docs/security-model.md](docs/security-model.md)。
|
|
19
19
|
|
|
20
20
|
## 效果预览
|
|
21
21
|
|
|
@@ -34,18 +34,17 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
|
|
|
34
34
|
|
|
35
35
|
## 安装
|
|
36
36
|
|
|
37
|
-
确认本机 DSH 版本后,在 [Release](https://github.com/SiriLee/dsh-rewind/releases) 找到适配的插件版本。
|
|
38
|
-
|
|
39
37
|
**命令行**:
|
|
40
38
|
|
|
41
39
|
```sh
|
|
42
|
-
dsh plugin --profile <name> add dsh-rewind-plugin
|
|
40
|
+
dsh plugin --profile <name> add dsh-rewind-plugin
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
**图形界面**:侧栏「插件」页 →「添加插件」→ 填 `dsh-rewind-plugin
|
|
43
|
+
**图形界面**:侧栏「插件」页 →「添加插件」→ 填 `dsh-rewind-plugin` → 安装 → 重启 DSH。
|
|
44
|
+
|
|
45
|
+
<img src="assets/screenshots/add-plugin.png" width="600" alt="「添加插件」弹窗:在输入框填入 dsh-rewind-plugin"><br>
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
<sub>图片仅为示例,请以实际适配版本号为准。</sub>
|
|
47
|
+
默认安装 `latest`,它适配 DSH 最新稳定版本。若使用的是旧版或内测版 DSH,请在 [Releases](https://github.com/SiriLee/dsh-rewind/releases) 页面找到适配的插件版本,并指定版本安装。
|
|
49
48
|
|
|
50
49
|
> [!WARNING]
|
|
51
50
|
> npm 上的 `dsh-rewind` 属于其他作者,请用 `dsh-rewind-plugin` 安装。
|
|
@@ -116,23 +115,23 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
116
115
|
|
|
117
116
|
### 2. 文件还原:轻量检查点,「写前备份」
|
|
118
117
|
|
|
119
|
-
文件部分对齐 Claude Code 的检查点语义——**局部追踪、写前备份 +
|
|
118
|
+
文件部分对齐 Claude Code 的检查点语义——**局部追踪、写前备份 + 每条消息重扫已跟踪文件**,而不是整树快照。这项取舍省空间,也让已跟踪文件的外部改动同样可还原:
|
|
120
119
|
|
|
121
120
|
- **写前备份**:只追踪写类工具(`write`、`edit`),写前**备份原内容**,并**记录、追踪**被处理的文件——从不备份整个工作区,因此轻量;**单个文件过大时不备份**。
|
|
122
121
|
- **外部变更也追**:每条用户消息边界,插件重新检查所有已跟踪文件——命令执行、手动修改等外部变更同样被记录,回退时一并还原。这让「轻量」却不「残缺」。
|
|
123
122
|
- **不变不存**:记录只在有变化时发生——消息边界重扫时无变更的**不备份**(不留记录);写前备份时若与前一条记录一致,只存指向它的链接(`ref`)而非复制内容。
|
|
124
|
-
-
|
|
123
|
+
- **还原准确**:以备份为唯一标准,对照真实磁盘,只动真正不一致的文件;备份逐字节存储。还原结果与备份一致,无“幽灵影响”。覆盖范围按设计限定:[文件回退的追踪边界](docs/compat/tracking-boundary.zh.md)。
|
|
125
124
|
- **安全与完整性**:路径经安全化处理,绝不越出备份根目录;符号/硬链接跳过,避免透过一次还原误伤同名的另一份文件;单个文件失败绝不中止整轮还原;备份与还原日志均原子写落盘(跨重启仍在),断电或崩溃后的半还原可续做或回滚。
|
|
126
125
|
|
|
127
126
|
> **设计点睛**:这套检查点的「轻」,来自**只记录被工具动过、且确实变化的文件**——写前备份保证可还原,不变不存与存链接压掉重复;还原时再对照真实磁盘,只动不一致的文件。
|
|
128
127
|
|
|
129
|
-
##
|
|
128
|
+
## 范围与分工
|
|
130
129
|
|
|
131
|
-
|
|
130
|
+
本插件刻意保持轻量、聚焦「对话回退」这一件事,以下场景**不属于它的职责**,各自有更合适的归属(见[设计边界](docs/boundaries.zh.md)):
|
|
132
131
|
|
|
133
|
-
- **整树 / Git 级快照**——只跟踪写类工具编辑 +
|
|
132
|
+
- **整树 / Git 级快照**——只跟踪写类工具编辑 + 已跟踪文件的外部改动,从未被工具碰过的文件不还原。工作区历史(任意文件、任意时刻)归 Git;Agent 本来就在用 Git,插件不重复造这层。需要工作树级的完整快照回退时,请交给 Git 或项目自选的快照工具。
|
|
134
133
|
- **子代理的编辑**——不追踪,子代理会话内也不提供回退(同 Claude Code):子代理运行在自己的会话里,其备份无法由父会话的回退还原,因此也不会为子会话保留备份。
|
|
135
|
-
- **fork / 分支回退**——DSH
|
|
134
|
+
- **fork / 分支回退**——DSH 已内置「在新对话中分支」功能,不重复造轮子。
|
|
136
135
|
|
|
137
136
|
## 兼容性
|
|
138
137
|
|
|
@@ -144,19 +143,17 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
|
|
|
144
143
|
> 本项目与 DeepSeek Harness 均处于开发者预览阶段。可复现环境请 pin 精确版本,
|
|
145
144
|
> 并阅读上述行为说明。
|
|
146
145
|
|
|
147
|
-
##
|
|
146
|
+
## 已知限制
|
|
148
147
|
|
|
149
|
-
1.
|
|
150
|
-
2.
|
|
151
|
-
3. **轮次导航保留已回退轮次**——右侧轮次导航为已回退轮次保留「未加载」刻度,悬浮可预览已撤回内容。仅显示差异,无功能影响。
|
|
152
|
-
4. **较远回退可能显示空白**——回退目标早于已加载的历史时,对话区会显示空白;需点击「加载更早」往前翻,才能看到历史对话。
|
|
148
|
+
1. **轮次导航保留已回退轮次**——右侧轮次导航为已回退轮次保留「未加载」刻度,悬浮可预览已撤回内容。仅显示差异,无功能影响。
|
|
149
|
+
2. **较远回退可能显示空白**——回退目标早于已加载的历史时,对话区会显示空白;需点击「加载更早」往前翻,才能看到历史对话。
|
|
153
150
|
|
|
154
151
|
> [!NOTE]
|
|
155
152
|
> 本插件提供浏览器端诊断输出;详见 [浏览器诊断](docs/compat/diagnostics.zh.md)。
|
|
156
153
|
|
|
157
154
|
## 安全
|
|
158
155
|
|
|
159
|
-
本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和文件」时被改写,备份存储于 `<dsh home>/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的快照自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[SECURITY.md](SECURITY.md)。
|
|
156
|
+
本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和文件」时被改写,备份存储于 `<dsh home>/rewind-snapshots/`;还原以备份为唯一来源。不触碰你的 git 仓库,无网络请求,不访问任何凭据。对**长期不活跃**的会话,另有默认关闭的快照自动清理可整目录移除其快照,不影响活动会话与对话日志。完整安全模型:[docs/security-model.md](docs/security-model.md);漏洞报告政策见 [SECURITY.md](SECURITY.md)。
|
|
160
157
|
|
|
161
158
|
## 开发
|
|
162
159
|
|
|
@@ -171,7 +168,7 @@ node scripts/verify-host.mjs # 端到端验证构建产物
|
|
|
171
168
|
|
|
172
169
|
`prepack` 在打包前执行完整构建,所以 `npm pack` / `npm publish` 总会产出完整的 `lib/`。请从 npm 或打好的 tarball 安装——git 检出的源码里没有构建好的 `lib/`。
|
|
173
170
|
|
|
174
|
-
|
|
171
|
+
维护者:源码布局与架构见 [docs/architecture.md](docs/architecture.md),harness 接口参考见 [docs/harness-reference.md](docs/harness-reference.md)。
|
|
175
172
|
|
|
176
173
|
贡献指南:[CONTRIBUTING.md](CONTRIBUTING.md)
|
|
177
174
|
|
package/SECURITY.md
CHANGED
|
@@ -1,206 +1,80 @@
|
|
|
1
|
-
# Security
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
the
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
## Conversation integrity
|
|
82
|
-
|
|
83
|
-
The session log is **append-only** — the plugin never deletes or rewrites
|
|
84
|
-
recorded history. A rewind appends a single marker event (a `user/message` whose
|
|
85
|
-
content is the constant `(empty message)` placeholder) whose `surfaceOp`
|
|
86
|
-
replaces every surface node after the target. The raw log (audit trail, search,
|
|
87
|
-
`/export`) is untouched — only the model-visible surface is cut, so the next
|
|
88
|
-
request derives its context from the target onward. The marker's content is a
|
|
89
|
-
constant, so it carries no untrusted text into the model context; the client
|
|
90
|
-
hides the `[target, marker]` span from the rendered transcript.
|
|
91
|
-
|
|
92
|
-
## Filesystem containment
|
|
93
|
-
|
|
94
|
-
- **Store root**: `<dsh home>/rewind-snapshots/` by default (resolved via
|
|
95
|
-
`resolveDshHome`, so `$DSH_HOME` feeds the base and `~/.dsh` is the fallback);
|
|
96
|
-
the `snapshotDir` config overrides the whole path. The plugin reads no
|
|
97
|
-
environment variable of its own. Deleting the root only removes file backups
|
|
98
|
-
and the store rebuilds from scratch.
|
|
99
|
-
- **Path sanitization**: session ids and call ids are scrubbed to
|
|
100
|
-
`[a-zA-Z0-9._-]` (`safeSessionId` / `safeFileId`) before joining the store
|
|
101
|
-
root; `.` and `..` bare values are replaced — hostile ids cannot traverse
|
|
102
|
-
out of the root.
|
|
103
|
-
- **Links and moved directories are refused**: two checks guard a tracked path.
|
|
104
|
-
(1) Planning skips a path whose last component is a symlink or a hard link
|
|
105
|
-
(`lstat().nlink > 1`) and reports it, never restoring it — a symlink would
|
|
106
|
-
redirect the write outside the checkpoint, and a hard link would clobber every
|
|
107
|
-
other name of the same inode (e.g. pnpm-installed files); a link swapped in
|
|
108
|
-
after planning is not re-checked. (2) At apply time, every record this build
|
|
109
|
-
writes — entry, link and
|
|
110
|
-
journal action — carries the `realpath` of the file's directory at commit time
|
|
111
|
-
(the location pin; best-effort, and absent on released-v1 data or a commit
|
|
112
|
-
whose parent could not be resolved), and a restore re-checks it before
|
|
113
|
-
touching the path, including a post-restart continue or rollback, which reads
|
|
114
|
-
the pin from the journal: a directory that no longer resolves there (a
|
|
115
|
-
repointed or moved ancestor) is refused and reported instead of writing
|
|
116
|
-
outside the recorded location, while data with no pin falls back to check (1)
|
|
117
|
-
alone. A parent chain that
|
|
118
|
-
is gone is still recreated (files whose directory was deleted are restorable),
|
|
119
|
-
but only while its nearest surviving ancestor resolves inside the pin. A
|
|
120
|
-
stable symlinked ancestor resolves identically on both sides, so a symlinked
|
|
121
|
-
workspace or temp root is never a false skip. This matches Claude Code's
|
|
122
|
-
checkpoint behavior since v2.1.216.
|
|
123
|
-
- **Restores name only recorded paths**: the store contains resolved display
|
|
124
|
-
paths of the session's own write-class tool calls (plus boundary re-checks
|
|
125
|
-
over that same tracked set) — a restore can never write an arbitrary path.
|
|
126
|
-
- **Path resolution rule**: relative paths resolve against the session's current
|
|
127
|
-
working directory, read from the same service the fs tools resolve through.
|
|
128
|
-
- **Bounded backups**: `prune` keeps the newest 100 anchor groups per session
|
|
129
|
-
(`MAX_ANCHOR_GROUPS`), except groups a non-terminal restore journal still
|
|
130
|
-
references (pinned so the op can be finished), so backup accumulation stays
|
|
131
|
-
bounded by the cap plus the number of unresolved journals (the exact cap is
|
|
132
|
-
pinned in `docs/format.md`). Across sessions, the opt-in `pruneStale` sweep
|
|
133
|
-
removes whole **long-inactive** session directories (measured by the newest
|
|
134
|
-
member being idle past the configured `autoCleanupMaxAgeDays`); it uses
|
|
135
|
-
`lstat` (no symlink following), skips dot-prefixed temp files, and never
|
|
136
|
-
targets the active session (`keepActiveId`).
|
|
137
|
-
|
|
138
|
-
## Crash safety
|
|
139
|
-
|
|
140
|
-
- **Ordered commits**: a checkpoint commit places the before-sidecar (a raw
|
|
141
|
-
byte copy) before publishing the entry JSON, so a crash can leave an
|
|
142
|
-
unreferenced sidecar but never an entry whose bytes are missing. Every JSON
|
|
143
|
-
write (checkpoint entries, restore journals) goes to a sibling temp file and
|
|
144
|
-
is renamed over the target: a host crash mid-write can leave only an inert
|
|
145
|
-
`<target>.tmp` — never a readable half-written file — and readers never pick
|
|
146
|
-
up temp files.
|
|
147
|
-
- **Journaled restores**: before mutating anything, the restore captures each
|
|
148
|
-
planned path's pre-restore ("rescue") state and persists an intent journal,
|
|
149
|
-
then marks each action done as it is applied. A crash at any point leaves
|
|
150
|
-
the journal on disk.
|
|
151
|
-
- **Disk is truth**: an interrupted op keeps its journal on disk, and the store's
|
|
152
|
-
recovery API re-derives the true state from the real disk — `reconcileRestores`
|
|
153
|
-
reports which paths already match the goal (restored) and which are pending,
|
|
154
|
-
`continueRestore` finishes the op, `rollbackRestore` undoes it to the exact
|
|
155
|
-
pre-restore state, and a journal whose goal is already reached auto-heals to
|
|
156
|
-
its terminal state. The plugin never runs them on its own: an interrupted op
|
|
157
|
-
stays interrupted until an explicit caller resumes or undoes it.
|
|
158
|
-
- **Fail-loud vs fail-soft**: a corrupt **journal** is reported
|
|
159
|
-
`recovery-required` — never silently dropped (dropping it would erase the
|
|
160
|
-
interrupted restore's recovery record). Corrupt **checkpoint entries** are
|
|
161
|
-
silently ignored (they only lose one backup, not the recovery path), and a
|
|
162
|
-
record whose sidecar is missing or truncated fails only that file — never a
|
|
163
|
-
delete of the live file.
|
|
164
|
-
- **Journal IO never fails the restore**: if the journal cannot be written the
|
|
165
|
-
restore proceeds with pre-journal semantics (crash safety degrades,
|
|
166
|
-
behavior does not).
|
|
167
|
-
|
|
168
|
-
## Explicit non-goals
|
|
169
|
-
|
|
170
|
-
- This plugin does **not** sandbox other processes or stop them from changing
|
|
171
|
-
files concurrently.
|
|
172
|
-
- It does **not** provide confidentiality or tamper resistance against the
|
|
173
|
-
same operating-system user: **store** files are created with the process
|
|
174
|
-
default permissions (a standard umask applies), and the host user remains
|
|
175
|
-
trusted. Restored **workspace** files do carry their recorded permission
|
|
176
|
-
bits: a content restore reapplies them (`chmod`, best-effort), while a mode
|
|
177
|
-
difference on its own never triggers a restore.
|
|
178
|
-
- It does **not** exclude paths from the store, and it never oversteps: the
|
|
179
|
-
plugin only adds backup/restore on top of permissions DSH already holds. Two
|
|
180
|
-
distinct cases follow:
|
|
181
|
-
1. **Sensitive / personal-information files** (e.g. a `.env` file, which may
|
|
182
|
-
sit inside the workspace) the model may read or edit are backed up and
|
|
183
|
-
restorable, unless the file is over the size cap.
|
|
184
|
-
2. **Files outside the session workspace** the model is allowed to touch are
|
|
185
|
-
likewise backed up and restored.
|
|
186
|
-
In both cases the plugin introduces no authority of its own — DSH already
|
|
187
|
-
granted the read/edit; the plugin merely records a before-backup and can
|
|
188
|
-
restore it. Keeping a path out is a **DSH model-permission** concern (a
|
|
189
|
-
per-path deny), not a rewind feature.
|
|
190
|
-
- It does **not** touch git (no refs, index, or worktree operations), makes
|
|
191
|
-
**no network requests**, and does **not** access credentials.
|
|
192
|
-
- It does **not** roll back whole-log state: telemetry, search, and `/export`
|
|
193
|
-
still see the withdrawn messages (documented behavior, not a bug).
|
|
194
|
-
- It does **not** restore files written by a cancelled tool call that never
|
|
195
|
-
committed a backup.
|
|
196
|
-
- **Subagent session edits are not tracked** (Claude Code alignment): a
|
|
197
|
-
subagent runs its own session, so the files it changes are not backed up and
|
|
198
|
-
cannot be restored by a rewind of the parent session; the plugin keeps no
|
|
199
|
-
snapshot for a subagent session and offers no rewind inside one.
|
|
200
|
-
|
|
201
|
-
## Reporting
|
|
202
|
-
|
|
203
|
-
Report a vulnerability through the repository's GitHub security channel or to
|
|
204
|
-
a repository maintainer. Include: the plugin version/commit, the DSH
|
|
205
|
-
(`@deepseek-ai/*`) version, the platform, and a minimal reproduction — and
|
|
206
|
-
whether the failure happened before or after workspace mutation.
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Only the latest stable release receives security fixes.
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Latest stable `0.x` release | Yes |
|
|
10
|
+
| Earlier stable releases | No — upgrade to the latest release |
|
|
11
|
+
| `alpha` / `rc` pre-releases | No — published for testing only |
|
|
12
|
+
|
|
13
|
+
## Reporting a vulnerability
|
|
14
|
+
|
|
15
|
+
Report privately through GitHub's private vulnerability reporting:
|
|
16
|
+
**[Report a vulnerability](https://github.com/SiriLee/dsh-rewind/security/advisories/new)**
|
|
17
|
+
— or open **Security** → **Report a vulnerability** from the repository. It
|
|
18
|
+
creates a private advisory that only you and the maintainer can see.
|
|
19
|
+
|
|
20
|
+
Please do not open a public issue, pull request, or discussion for a
|
|
21
|
+
vulnerability before a fix has been released.
|
|
22
|
+
|
|
23
|
+
Include as much of the following as you can:
|
|
24
|
+
|
|
25
|
+
- the plugin version or commit;
|
|
26
|
+
- the DeepSeek Harness version (`@deepseek-ai/*` packages);
|
|
27
|
+
- the platform and Node.js version;
|
|
28
|
+
- a minimal reproduction: configuration, steps, and the relevant log output;
|
|
29
|
+
- whether the failure happened before or after a workspace file was mutated.
|
|
30
|
+
|
|
31
|
+
If the problem is in DSH itself or in another plugin, report it to that project.
|
|
32
|
+
|
|
33
|
+
## What to expect
|
|
34
|
+
|
|
35
|
+
- We aim to acknowledge a report within 7 days, with a first assessment
|
|
36
|
+
(in scope / out of scope / need more information) within a further 7 days.
|
|
37
|
+
- The report stays private while a fix is developed, and you are kept updated in
|
|
38
|
+
the advisory.
|
|
39
|
+
- We ask for up to 90 days before public disclosure; the advisory can be
|
|
40
|
+
published earlier once a fix is ready and you agree.
|
|
41
|
+
- With your consent, the published advisory credits you, and it enters the
|
|
42
|
+
GitHub Advisory Database so `npm audit` and Dependabot can flag affected
|
|
43
|
+
installs.
|
|
44
|
+
- If the report is not a vulnerability, or is out of scope, we explain why and
|
|
45
|
+
close the advisory.
|
|
46
|
+
|
|
47
|
+
## Scope
|
|
48
|
+
|
|
49
|
+
**In scope** — behavior that breaks a guarantee in the security model:
|
|
50
|
+
|
|
51
|
+
- a restore that writes outside the paths and locations the session recorded, or
|
|
52
|
+
that runs without an explicit user action;
|
|
53
|
+
- path traversal, or a symlink/hard-link escape, through a session id, a store
|
|
54
|
+
entry, or restore planning;
|
|
55
|
+
- rewriting, deleting, or truncating the session log, or otherwise changing
|
|
56
|
+
recorded history;
|
|
57
|
+
- a restore crossing sessions, or touching a path the session's own tools did
|
|
58
|
+
not record;
|
|
59
|
+
- a crash or a corrupt journal that loses a live file, or that silently drops a
|
|
60
|
+
recovery journal;
|
|
61
|
+
- a hostile session-log entry or tool argument escaping the containment that
|
|
62
|
+
document describes.
|
|
63
|
+
|
|
64
|
+
**Out of scope**:
|
|
65
|
+
|
|
66
|
+
- attackers who already have the same OS account, the host process, or the
|
|
67
|
+
workspace: the host user is trusted, and store files use the process umask;
|
|
68
|
+
- vulnerabilities in DSH or other plugins — report them upstream;
|
|
69
|
+
- actions the model is allowed to take under DSH's permission model (for
|
|
70
|
+
example, editing a `.env` file), unless they escape that model;
|
|
71
|
+
- behavior the security model explicitly lists as a non-goal.
|
|
72
|
+
|
|
73
|
+
## Security model
|
|
74
|
+
|
|
75
|
+
The guarantees, the containment rules, and the explicit non-goals are
|
|
76
|
+
documented at
|
|
77
|
+
**https://github.com/SiriLee/dsh-rewind/blob/main/docs/security-model.md**.
|
|
78
|
+
|
|
79
|
+
Reporters are welcome to test those claims: a mismatch between that document and
|
|
80
|
+
the code is itself worth reporting.
|