oh-my-adhd 0.2.9 → 0.2.10

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.
@@ -21,7 +21,16 @@ export function registerWikiExport(server) {
21
21
  pages,
22
22
  };
23
23
  const date = new Date().toISOString().slice(0, 10);
24
- const filePath = outputPath ?? path.join(os.homedir(), `oh-my-adhd-export-${date}.json`);
24
+ const defaultPath = path.join(os.homedir(), `oh-my-adhd-export-${date}.json`);
25
+ const resolved = outputPath ? path.resolve(outputPath) : defaultPath;
26
+ // Require .json extension — prevents LLM-controlled path from clobbering arbitrary config files
27
+ if (!resolved.endsWith(".json")) {
28
+ return {
29
+ content: [{ type: "text", text: "오류: outputPath는 .json 확장자로 끝나야 합니다." }],
30
+ isError: true,
31
+ };
32
+ }
33
+ const filePath = resolved;
25
34
  const tmp = filePath + ".tmp";
26
35
  await fs.writeFile(tmp, JSON.stringify(exportData, null, 2), "utf-8");
27
36
  await fs.rename(tmp, filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-adhd",
3
- "version": "0.2.9",
3
+ "version": "0.2.10",
4
4
  "description": "ADHD second brain — zero-friction capture, auto context restore, unstick. MCP-native Claude Code plugin.",
5
5
  "author": "Yeachan Heo",
6
6
  "repository": {
@@ -31,17 +31,24 @@ try {
31
31
  const openThreads = manifest.filter(t => t.is_open).slice(0, 4);
32
32
  if (openThreads.length === 0) process.exit(0);
33
33
 
34
+ // Sanitize stored content before injecting into model context (prompt injection prevention)
35
+ const sanitize = (s, max) => String(s ?? "")
36
+ .replace(/[\x00-\x1F\x7F]/g, " ")
37
+ .replace(/[`$<>]/g, "")
38
+ .replace(/\bignore (all|previous)\b/gi, "[redacted]")
39
+ .slice(0, max);
40
+
34
41
  const lines = ["[Second Brain 복원]", ""];
35
42
  const top = openThreads[0];
36
- lines.push(`🔴 **${top.title}** (${gapLabel(top.updatedAt)})`);
37
- if (top.next_action) lines.push(`→ 다음: ${top.next_action.slice(0, 100)}`);
38
- if (top.blocker) lines.push(`⛔ 막힌것: ${top.blocker.slice(0, 80)}`);
43
+ lines.push(`🔴 **${sanitize(top.title, 40)}** (${gapLabel(top.updatedAt)})`);
44
+ if (top.next_action) lines.push(`→ 다음: ${sanitize(top.next_action, 100)}`);
45
+ if (top.blocker) lines.push(`⛔ 막힌것: ${sanitize(top.blocker, 80)}`);
39
46
 
40
47
  if (openThreads.length > 1) {
41
48
  lines.push("");
42
49
  for (const t of openThreads.slice(1)) {
43
- const hint = t.next_action ? ` → ${t.next_action.slice(0, 60)}` : "";
44
- lines.push(`• ${t.title} (${gapLabel(t.updatedAt)})${hint}`);
50
+ const hint = t.next_action ? ` → ${sanitize(t.next_action, 60)}` : "";
51
+ lines.push(`• ${sanitize(t.title, 30)} (${gapLabel(t.updatedAt)})${hint}`);
45
52
  }
46
53
  }
47
54