opencode-self-improvement 0.3.0 → 0.3.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/package.json +1 -1
- package/src/undo.ts +9 -2
- package/src/viewer/server.ts +9 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-self-improvement",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Durable memory for opencode: remember facts, decisions, and commands across sessions, plus a one-command per-repo setup (/repo-init).",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
package/src/undo.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// must survive" — that call belongs to the caller, so the default mode CHECK
|
|
10
10
|
// refuses and lists the divergent rows; MERGE replays them on top of the
|
|
11
11
|
// snapshot; FORCE is the old exact rollback.
|
|
12
|
-
import { join,
|
|
12
|
+
import { basename, join, resolve, sep } from "node:path";
|
|
13
13
|
import { snapshotDir } from "./config.ts";
|
|
14
14
|
import { ensureDir } from "./logfmt.ts";
|
|
15
15
|
|
|
@@ -78,7 +78,14 @@ export async function restoreSnapshot(
|
|
|
78
78
|
if (!(await f.exists())) throw new Error(`snapshot not found: ${snapshot}`);
|
|
79
79
|
|
|
80
80
|
const original = name.replace(/^\d+-/, "");
|
|
81
|
-
|
|
81
|
+
// Defense in depth (issue #21): `name` is basenamed above, so `original`
|
|
82
|
+
// carries no separators — but a restore must never write outside the memory
|
|
83
|
+
// root no matter what a future caller passes.
|
|
84
|
+
const rootAbs = resolve(memoryRootDir);
|
|
85
|
+
const target = resolve(rootAbs, original);
|
|
86
|
+
if (!target.startsWith(rootAbs + sep)) {
|
|
87
|
+
throw new Error(`snapshot target escapes the memory root: ${original}`);
|
|
88
|
+
}
|
|
82
89
|
const snapLines = rows(await f.text());
|
|
83
90
|
const cur = Bun.file(target);
|
|
84
91
|
const curLines = (await cur.exists()) ? rows(await cur.text()) : [];
|
package/src/viewer/server.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// through the same loadAll() used by recall/inject (so blocked entries are
|
|
5
5
|
// hidden), and all mutations are non-destructive (snapshot + audit + supersede).
|
|
6
6
|
// I2: serves only file data; never writes into any repo.
|
|
7
|
-
import { join } from "node:path";
|
|
7
|
+
import { basename, join } from "node:path";
|
|
8
8
|
import { serve, type Server } from "bun";
|
|
9
9
|
import { deletionsFile, memoryRoot, snapshotDir } from "../config.ts";
|
|
10
10
|
import { blockedEntries } from "../blocklist.ts";
|
|
@@ -213,7 +213,14 @@ async function restoreEntryRoute(body: Record<string, unknown>): Promise<Respons
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
async function restoreRoute(body: Record<string, unknown>): Promise<Response> {
|
|
216
|
-
|
|
216
|
+
// Path containment (issue #21): name arrives raw from the wire. Basename it,
|
|
217
|
+
// then pin it to the real snapshot-name grammar — `<epoch-ms>-<YYYY-MM-DD>.logfmt`
|
|
218
|
+
// — so neither path join below can be steered outside snapshots/ and the
|
|
219
|
+
// store root with a `..` or absolute name.
|
|
220
|
+
const name = basename(String(body.name ?? ""));
|
|
221
|
+
if (!/^\d+-\d{4}-\d{2}-\d{2}\.logfmt$/.test(name)) {
|
|
222
|
+
return error("invalid snapshot name", 400);
|
|
223
|
+
}
|
|
217
224
|
const original = name.replace(/^\d+-/, "");
|
|
218
225
|
// Explicit human admin action: exact rollback — but the rollback is itself
|
|
219
226
|
// rollback-able. Snapshot the current file first so concurrent-session rows
|