oh-my-adhd 0.2.16 → 0.2.18
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/dist/mcp/lib/brain.js
CHANGED
|
@@ -14,6 +14,17 @@ export const SCHEMA_VERSION = 1;
|
|
|
14
14
|
export const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
15
15
|
export const SENSITIVE_DIRS = [".ssh", ".aws", ".gnupg", ".kube", ".docker",
|
|
16
16
|
path.join(".config", "git"), path.join(".config", "gh")];
|
|
17
|
+
export async function isSensitivePath(filePath) {
|
|
18
|
+
const homeDir = os.homedir();
|
|
19
|
+
let realDir = path.dirname(filePath);
|
|
20
|
+
try {
|
|
21
|
+
realDir = await fs.realpath(realDir);
|
|
22
|
+
}
|
|
23
|
+
catch { /* dir may not exist yet */ }
|
|
24
|
+
const realHome = await fs.realpath(homeDir).catch(() => homeDir);
|
|
25
|
+
const rel = path.relative(realHome, realDir).toLowerCase();
|
|
26
|
+
return SENSITIVE_DIRS.some(d => rel === d.toLowerCase() || rel.startsWith(d.toLowerCase() + path.sep));
|
|
27
|
+
}
|
|
17
28
|
async function appendLog(level, msg) {
|
|
18
29
|
try {
|
|
19
30
|
const entry = `${new Date().toISOString()} [${level}] ${msg}\n`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { getThreads, getThread, getPages,
|
|
2
|
+
import { getThreads, getThread, getPages, isSensitivePath } from "../../lib/brain.js";
|
|
3
3
|
import fs from "fs/promises";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import os from "os";
|
|
@@ -30,16 +30,8 @@ export function registerWikiExport(server) {
|
|
|
30
30
|
isError: true,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
-
// Block writes into known sensitive dirs
|
|
34
|
-
|
|
35
|
-
let realResolved = resolved;
|
|
36
|
-
try {
|
|
37
|
-
realResolved = await fs.realpath(path.dirname(resolved));
|
|
38
|
-
}
|
|
39
|
-
catch { /* dir may not exist yet */ }
|
|
40
|
-
const realHome = await fs.realpath(homeDir).catch(() => homeDir);
|
|
41
|
-
const relDir = path.relative(realHome, realResolved).toLowerCase();
|
|
42
|
-
if (SENSITIVE_DIRS.some(d => relDir === d.toLowerCase() || relDir.startsWith(d.toLowerCase() + path.sep))) {
|
|
33
|
+
// Block writes into known sensitive dirs
|
|
34
|
+
if (await isSensitivePath(resolved)) {
|
|
43
35
|
return {
|
|
44
36
|
content: [{ type: "text", text: "오류: 보안상 해당 경로에는 내보낼 수 없습니다." }],
|
|
45
37
|
isError: true,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { ensureBrainDirs, BRAIN_DIR, SCHEMA_VERSION, UUID_RE,
|
|
2
|
+
import { ensureBrainDirs, BRAIN_DIR, SCHEMA_VERSION, UUID_RE, isSensitivePath, withBrainLock } from "../../lib/brain.js";
|
|
3
3
|
import fs from "fs/promises";
|
|
4
4
|
import path from "path";
|
|
5
|
-
import os from "os";
|
|
6
5
|
const SLUG_RE = /^[a-z0-9가-힣][a-z0-9가-힣_-]{0,127}$/;
|
|
7
6
|
const MAX_CONTENT_BYTES = 5 * 1024 * 1024; // 5MB per thread
|
|
8
7
|
const MAX_ITEMS = 10000; // max threads or pages per import
|
|
@@ -20,15 +19,7 @@ export function registerWikiImport(server) {
|
|
|
20
19
|
};
|
|
21
20
|
}
|
|
22
21
|
// Block reads from sensitive dirs — mirrors wiki_export denylist
|
|
23
|
-
|
|
24
|
-
let realInputDir = path.dirname(resolved);
|
|
25
|
-
try {
|
|
26
|
-
realInputDir = await fs.realpath(realInputDir);
|
|
27
|
-
}
|
|
28
|
-
catch { /* dir may not exist */ }
|
|
29
|
-
const realHome = await fs.realpath(homeDir).catch(() => homeDir);
|
|
30
|
-
const relInputDir = path.relative(realHome, realInputDir).toLowerCase();
|
|
31
|
-
if (SENSITIVE_DIRS.some(d => relInputDir === d.toLowerCase() || relInputDir.startsWith(d.toLowerCase() + path.sep))) {
|
|
22
|
+
if (await isSensitivePath(resolved)) {
|
|
32
23
|
return {
|
|
33
24
|
content: [{ type: "text", text: "오류: 보안상 해당 경로에서는 가져올 수 없습니다." }],
|
|
34
25
|
isError: true,
|
|
@@ -95,6 +86,7 @@ export function registerWikiImport(server) {
|
|
|
95
86
|
let importedThreads = 0;
|
|
96
87
|
let skippedThreads = 0;
|
|
97
88
|
let importedPages = 0;
|
|
89
|
+
let skippedPages = 0;
|
|
98
90
|
await withBrainLock(async () => {
|
|
99
91
|
// Load existing manifest inside lock
|
|
100
92
|
let manifest = [];
|
|
@@ -165,8 +157,10 @@ export function registerWikiImport(server) {
|
|
|
165
157
|
const content = typeof page.content === "string" ? page.content : "";
|
|
166
158
|
if (!SLUG_RE.test(slug) || !content)
|
|
167
159
|
continue;
|
|
168
|
-
if (Buffer.byteLength(content, "utf-8") > MAX_CONTENT_BYTES)
|
|
160
|
+
if (Buffer.byteLength(content, "utf-8") > MAX_CONTENT_BYTES) {
|
|
161
|
+
skippedPages++;
|
|
169
162
|
continue;
|
|
163
|
+
}
|
|
170
164
|
const pageFile = path.join(pagesDir, `${slug}.md`);
|
|
171
165
|
const pageTmp = pageFile + ".tmp";
|
|
172
166
|
await fs.writeFile(pageTmp, content, "utf-8");
|
|
@@ -181,7 +175,7 @@ export function registerWikiImport(server) {
|
|
|
181
175
|
text: [
|
|
182
176
|
"가져오기 완료 ✓",
|
|
183
177
|
`스레드: ${importedThreads}개 가져옴${skippedThreads > 0 ? ` (${skippedThreads}개 건너뜀)` : ""}`,
|
|
184
|
-
`페이지: ${importedPages}개
|
|
178
|
+
`페이지: ${importedPages}개 가져옴${skippedPages > 0 ? ` (${skippedPages}개 건너뜀)` : ""}`,
|
|
185
179
|
`원본 파일: ${resolved}`,
|
|
186
180
|
].join("\n"),
|
|
187
181
|
}],
|
package/package.json
CHANGED