kibi-mcp 3.16.0 → 3.16.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.
@@ -20,6 +20,7 @@ import { access, readFile, rename, unlink, writeFile } from "node:fs/promises";
20
20
  import path from "node:path";
21
21
  import { dump as dumpYAML, load as parseYAML } from "js-yaml";
22
22
  import { enrichSymbolCoordinates, withSymbolCompilerLock, } from "kibi-runtime";
23
+ import { resolveWorkspaceSourceAbsolute } from "../workspace-path.js";
23
24
  import { resolveWorkspaceRoot } from "../workspace.js";
24
25
  const SYMBOL_COORDINATES_FORMAT_VERSION = 2;
25
26
  const SYMBOL_COORDINATES_COMMENT_BLOCK = `# symbol-coordinates.yaml
@@ -214,10 +215,8 @@ function hasGeneratedCoordinates(entry) {
214
215
  async function isEligible(sourceFile, workspaceRoot) {
215
216
  if (!sourceFile)
216
217
  return false;
217
- const absolute = path.isAbsolute(sourceFile)
218
- ? sourceFile
219
- : path.resolve(workspaceRoot, sourceFile);
220
- if (!(await fileExists(absolute)))
218
+ const absolute = resolveWorkspaceSourceAbsolute(workspaceRoot, sourceFile);
219
+ if (absolute === null || !(await fileExists(absolute)))
221
220
  return false;
222
221
  return SOURCE_EXTENSIONS.has(path.extname(absolute).toLowerCase());
223
222
  }
@@ -253,9 +252,10 @@ async function fillMissingCoordinates(before, after, workspaceRoot) {
253
252
  if (!sourceFile) {
254
253
  return after;
255
254
  }
256
- const absolutePath = path.isAbsolute(sourceFile)
257
- ? sourceFile
258
- : path.resolve(workspaceRoot, sourceFile);
255
+ const absolutePath = resolveWorkspaceSourceAbsolute(workspaceRoot, sourceFile);
256
+ if (absolutePath === null) {
257
+ return after;
258
+ }
259
259
  try {
260
260
  const content = await readFile(absolutePath, "utf8");
261
261
  const lines = content.split(/\r?\n/);
@@ -357,9 +357,9 @@ function coordinateSourceHash(sourceText) {
357
357
  return createHash("sha256").update(sourceText).digest("hex");
358
358
  }
359
359
  async function readSourceText(sourceFile, workspaceRoot) {
360
- const absolutePath = path.isAbsolute(sourceFile)
361
- ? sourceFile
362
- : path.resolve(workspaceRoot, sourceFile);
360
+ const absolutePath = resolveWorkspaceSourceAbsolute(workspaceRoot, sourceFile);
361
+ if (absolutePath === null)
362
+ return null;
363
363
  try {
364
364
  return await readFile(absolutePath, "utf8");
365
365
  }
@@ -0,0 +1,69 @@
1
+ /*
2
+ Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ Copyright (C) 2026 Piotr Franczyk
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as published by
7
+ the Free Software Foundation, either version 3 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ import { realpathSync } from "node:fs";
19
+ import path from "node:path";
20
+ /**
21
+ * Canonical repository-relative form of a recorded path. Returns null when the
22
+ * candidate escapes `workspaceRoot` (absolute foreign path, `..` segment).
23
+ * Kept in lockstep with packages/cli/src/utils/repo-relative-path.ts.
24
+ */
25
+ export function normalizeRepoRelativePath(workspaceRoot, candidate) {
26
+ if (candidate.trim() === "")
27
+ return null;
28
+ let slashed = candidate.replaceAll("\\", "/");
29
+ const foreignAbsolute = /^[A-Za-z]:\//.test(slashed) || slashed.startsWith("//");
30
+ if (slashed.startsWith("/") || foreignAbsolute) {
31
+ if (foreignAbsolute && path.sep === "/")
32
+ return null;
33
+ const root = path.resolve(workspaceRoot);
34
+ const relative = path.relative(root, path.resolve(candidate));
35
+ if (relative === "" || path.isAbsolute(relative))
36
+ return null;
37
+ slashed = relative.replaceAll("\\", "/");
38
+ }
39
+ const normalized = path.posix.normalize(slashed).replace(/\/+$/, "");
40
+ if (normalized === "" ||
41
+ normalized === "." ||
42
+ normalized === ".." ||
43
+ normalized.startsWith("../")) {
44
+ return null;
45
+ }
46
+ return normalized;
47
+ }
48
+ /**
49
+ * Resolve a recorded source path to an absolute path confined under
50
+ * `workspaceRoot`. Returns null for escapes; callers must not read on null.
51
+ */
52
+ // implements REQ-symbol-sourcefile-workspace-confinement
53
+ export function resolveWorkspaceSourceAbsolute(workspaceRoot, sourceFile) {
54
+ const relative = normalizeRepoRelativePath(workspaceRoot, sourceFile);
55
+ if (relative === null)
56
+ return null;
57
+ const absolute = path.resolve(workspaceRoot, relative);
58
+ try {
59
+ const realRoot = realpathSync.native(path.resolve(workspaceRoot));
60
+ const realFile = realpathSync.native(absolute);
61
+ const confined = path.relative(realRoot, realFile);
62
+ if (confined.startsWith("..") || path.isAbsolute(confined))
63
+ return null;
64
+ return realFile;
65
+ }
66
+ catch {
67
+ return absolute;
68
+ }
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-mcp",
3
- "version": "3.16.0",
3
+ "version": "3.16.1",
4
4
  "mcpName": "io.github.Looted/kibi-mcp",
5
5
  "dependencies": {
6
6
  "@modelcontextprotocol/sdk": "^1.32.1",
@@ -10,9 +10,9 @@
10
10
  "fast-glob": "^3.2.12",
11
11
  "gray-matter": "^4.0.3",
12
12
  "js-yaml": "^4.1.0",
13
- "kibi-runtime": "^2.18.0",
14
- "kibi-core": "^0.22.3",
15
- "zod": "^4.3.6"
13
+ "kibi-runtime": "^2.18.1",
14
+ "kibi-core": "^0.22.4",
15
+ "zod": "^4.6.7"
16
16
  },
17
17
  "type": "module",
18
18
  "description": "Model Context Protocol server for Kibi knowledge base",