dsh-rewind-plugin 0.20.0 → 0.21.1

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 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: [SECURITY.md](SECURITY.md).
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
 
@@ -166,7 +166,7 @@ This plugin deliberately stays lightweight and focused on one thing — "convers
166
166
 
167
167
  ## Security
168
168
 
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: [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).
170
170
 
171
171
  ## Development
172
172
 
package/README.md CHANGED
@@ -15,7 +15,7 @@ DeepSeek Harness 插件:**一键就地回退对话到任意更早的用户消
15
15
 
16
16
  - **就地消息回退**——目标消息及其之后的内容同时从**模型上下文**和**渲染对话**中撤回,目标消息文本自动回填输入框。始终在原会话内完成,不新建分支、不残留副本。
17
17
  - **轻量备份还原**——对齐 Claude Code:追踪写类工具编辑过的文件,回退时一并还原,已跟踪文件的外部变更也能还原。**局部追踪、写前备份、不变不存**,不依赖 git。
18
- - **安全保障**——会话日志仅追加回退标记,从不删改;文件备份与还原经安全加固;**完备测试**,随 DSH 升级长期维护。安全模型:[SECURITY.md](SECURITY.md)。
18
+ - **安全保障**——会话日志仅追加回退标记,从不删改;文件备份与还原经安全加固;**完备测试**,随 DSH 升级长期维护。安全模型:[docs/security-model.md](docs/security-model.md)。
19
19
 
20
20
  ## 效果预览
21
21
 
@@ -153,7 +153,7 @@ rm <dsh home>/snapshot-cleanup-last-sweep.json
153
153
 
154
154
  ## 安全
155
155
 
156
- 本插件只向会话日志追加回退标记事件,从不删除或改写已记录的历史。工作区文件仅在「回退对话和文件」时被改写,备份存储于 `<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)。
157
157
 
158
158
  ## 开发
159
159
 
package/SECURITY.md CHANGED
@@ -1,183 +1,80 @@
1
- # Security model
2
-
3
- This document is the security model of **dsh-rewind** — how the plugin treats
4
- untrusted input, what it is allowed to mutate, how it contains filesystem
5
- writes, and how it survives crashes. It is derived from the implementation; if
6
- this document and the code ever disagree, the code wins and this document is a
7
- bug.
8
-
9
- ## Trusted boundary
10
-
11
- The plugin runs in the DSH host process and therefore holds the host user's
12
- filesystem authority — it reads and writes files with plain `node:fs`. The
13
- following are treated as **untrusted inputs**:
14
-
15
- - **Model arguments** — the `file_path` field of `write` / `edit` tool calls
16
- (it names the paths the checkpoint store records).
17
- - **Session log contents** — events are parsed structurally; a hostile or
18
- malformed id must never escape the store root.
19
- - **Current worktree state** — restore planning reconciles against the live
20
- disk, which anything may have changed.
21
- - **Concurrent external modifications** — a restore never assumes the disk
22
- still matches its records.
23
-
24
- The DSH host (and its other plugins) is trusted; this plugin does not re-verify
25
- the host's own authority boundaries.
26
-
27
- ## Mutation gates
28
-
29
- When snapshot backups are on, the plugin **automatically captures**
30
- before-backups of tracked mutations, but **never automatically applies** one.
31
- A workspace restore happens only through an explicit user action (the
32
- per-message ↶ button or `/rewind`), and only when all of the following hold:
33
-
34
- 1. **A validated target** — a `user/message` seq currently on the session
35
- surface.
36
- 2. **A fresh plan** — derived from the current events and surface at execution
37
- time, never cached across events.
38
- 3. **Committed backups exist** — every planned action comes from a committed
39
- checkpoint entry anchored at or after the target.
40
- 4. **Live-disk reconciliation** — each entry is compared with the current disk
41
- and only actions that would actually change it are planned; an
42
- already-matching state is a no-op (restores are idempotent).
43
- 5. **Exclusive execution** — a per-session in-flight guard rejects concurrent
44
- rewinds; a running turn is cancelled (queued messages are kept) and
45
- quiescence is awaited before the surface is cut.
46
- 6. **Session binding** — the restore reads and writes only the rewound
47
- session's store, over paths that session's own tools touched.
48
-
49
- **Concurrency scope:** the guard serializes concurrent *rewinds* of one session;
50
- it does not stop other processes from changing the workspace. The plan is
51
- reconciled against the live disk at plan time, with no re-validation before
52
- apply, so a file changed in that window is overwritten by the restore. The
53
- pre-restore state is captured at apply start, so a rollback still returns the
54
- workspace to exactly that state, and per-file failures are reported rather than
55
- hidden.
56
-
57
- A failed gate fails closed: an invalid target, a missing store, an absent
58
- backup, or a cancelled invocation aborts the rewind with an error. A store
59
- written by a **newer** build is refused as a whole — no partial restore, no new
60
- entry, and no automatic format-change clear — while the conversation rewind
61
- still works. An explicit clear or the age-based sweep, being user-directed
62
- deletion of whole session directories, is deliberately not version-gated.
63
-
64
- **Automatic store deletion.** Two things delete session snapshots: the opt-in
65
- `snapshot-auto-cleanup` sweep (off by default) removes whole directories of
66
- long-inactive sessions; separately, a session's snapshots are cleared when the
67
- session format has changed (a DSH upgrade), because the backups are anchored to
68
- message positions that no longer line up. Both stay confined to the store root,
69
- never follow a symlink out of it, never touch the conversation log, and the
70
- sweep never targets the active session. The format guard runs before the clear,
71
- so a store newer than this build is not cleared by it. See
72
- [snapshot auto-cleanup](docs/snapshot-auto-cleanup.md) and
73
- [the store format](docs/format.md).
74
-
75
- ## Conversation integrity
76
-
77
- The session log is **append-only** — the plugin never deletes or rewrites
78
- recorded history. A rewind appends a single marker event whose content is a
79
- constant placeholder and whose surface operation replaces every surface node
80
- after the target. The raw log (audit trail, search, `/export`) is untouched;
81
- only the model-visible surface is cut, so the next request derives its context
82
- from the target onward. The marker's constant content carries no untrusted text
83
- into the model context, and the client hides the withdrawn span from the
84
- rendered transcript. The marker shape is specified in
85
- [architecture](docs/architecture.md).
86
-
87
- ## Filesystem containment
88
-
89
- - **Store root**: `<dsh home>/rewind-snapshots/` by default (`$DSH_HOME` feeds
90
- the base, `~/.dsh` is the fallback); the `snapshotDir` config overrides the
91
- whole path. The plugin reads no environment variable of its own. Deleting the
92
- root only removes file backups; the store rebuilds.
93
- - **Path sanitization**: session and call ids are scrubbed to a safe character
94
- set before joining the store root; bare `.` and `..` are replaced — hostile
95
- ids cannot traverse out of the root.
96
- - **Links are refused**: planning skips a path whose last component is a
97
- symlink or a hard link and reports it — a symlink would redirect the write
98
- outside the checkpoint, and a hard link would clobber every other name of the
99
- same inode. A link swapped in after planning is not re-checked.
100
- - **Recorded locations are re-checked**: every record written by this build
101
- carries the directory location resolved at commit time; a restore re-checks
102
- it before touching the path, including after a restart. A directory that no
103
- longer resolves there is refused and reported; a missing parent chain is
104
- recreated only inside the recorded location. Data with no recorded location
105
- falls back to the link check. This matches Claude Code's checkpoint behavior.
106
- - **Observation sync (best-effort)**: after a restore, the plugin re-emits the
107
- host's `fs/observed` event for the paths it changed (absent, or present with
108
- the current version), so a host with the observation policy enabled does not
109
- judge the next write against a pre-restore observation. It heals only the
110
- inconsistency the restore itself created; later external changes are still
111
- seen normally.
112
- - **Restores name only recorded paths**: the store contains resolved display
113
- paths of the session's own write-class tool calls (plus boundary re-checks
114
- over that same tracked set) — a restore can never write an arbitrary path.
115
- - **Path resolution**: relative paths resolve against the session's current
116
- working directory, read from the service the fs tools resolve through.
117
- - **Bounded backups**: the store keeps a bounded number of anchor groups per
118
- session, except groups a non-terminal restore still references (pinned so the
119
- operation can be finished); the exact cap is in
120
- [the store format](docs/format.md). Across sessions, the opt-in sweep removes
121
- whole long-inactive session directories and never targets the active session.
122
-
123
- ## Crash safety
124
-
125
- - **Ordered commits**: a checkpoint writes the before-sidecar (a raw byte copy)
126
- before publishing the entry, so a crash can leave an unreferenced sidecar but
127
- never an entry whose bytes are missing. Every JSON write goes to a sibling
128
- temp file and is renamed over the target: a crash can leave only an inert
129
- `.tmp`, never a readable half-written file, and readers never pick up temp
130
- files.
131
- - **Journaled restores**: before mutating anything, a restore captures each
132
- planned path's pre-restore state and persists an intent journal, then marks
133
- each action done as it is applied. A crash at any point leaves the journal on
134
- disk.
135
- - **Disk is truth**: an interrupted operation keeps its journal, and recovery
136
- re-derives the true state from the real disk — which paths already match the
137
- goal and which are pending, can finish the operation, and can undo it to the
138
- exact pre-restore state. A journal whose goal is already reached auto-heals.
139
- The plugin never runs recovery on its own: an interrupted operation stays
140
- interrupted until an explicit caller resumes or undoes it.
141
- - **Fail-loud vs fail-soft**: a corrupt journal is reported as
142
- `recovery-required`, never silently dropped — dropping it would erase the
143
- recovery record. Corrupt entries are ignored (they lose one backup, not the
144
- recovery path), and a missing or truncated sidecar fails only that file —
145
- never a delete of the live file.
146
- - **Journal IO never fails the restore**: if the journal cannot be written, the
147
- restore proceeds with pre-journal semantics; crash safety degrades, behavior
148
- does not.
149
-
150
- ## Explicit non-goals
151
-
152
- This section declares scope, not a gap list: each item names what stays outside
153
- the plugin and what remains the host's or the user's responsibility. The
154
- boundary contract is in [design boundaries](docs/boundaries.md).
155
-
156
- - It does **not** sandbox other processes or stop them from changing files
157
- concurrently.
158
- - It does **not** provide confidentiality or tamper resistance against the same
159
- OS user: **store** files use the process default permissions under the
160
- process umask, and the host user remains trusted. Restored **workspace** files
161
- carry their recorded permission bits (a content restore reapplies them,
162
- best-effort), while a mode difference alone never triggers a restore.
163
- - It does **not** exclude paths from the store, and it introduces no authority of
164
- its own: sensitive files (e.g. a `.env` the model may edit) and files outside
165
- the workspace the model may touch are backed up and restorable, unless over
166
- the size cap. Keeping a path out is a **DSH model-permission** concern, not a
167
- rewind feature.
168
- - It does **not** touch git, make network requests, or access credentials.
169
- - It does **not** roll back whole-log state: search, telemetry and `/export`
170
- still see withdrawn messages (by design; see [boundaries](docs/boundaries.md)).
171
- - It does **not** restore files written by a cancelled tool call that never
172
- committed a backup.
173
- - **Subagent session edits are not tracked**: a subagent runs its own session,
174
- so its files cannot be restored by a rewind of the parent, and the plugin
175
- offers no rewind inside one. See [boundaries](docs/boundaries.md).
176
-
177
- ## Reporting
178
-
179
- Report a vulnerability through the repository's GitHub security channel or to
180
- a repository maintainer. Include the plugin version/commit, the DSH
181
- (`@deepseek-ai/*`) version, the platform, and a minimal reproduction — and
182
- whether the failure happened before or after workspace mutation. Do not open a
183
- public issue for a vulnerability.
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.