codecartographer-pi 0.12.9 → 0.12.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.
@@ -3,6 +3,15 @@ export declare function pathExists(path: string): Promise<boolean>;
3
3
  export declare function canonicalPath(path: string): Promise<string>;
4
4
  export declare function normalizeForComparison(path: string): string;
5
5
  export declare function isWithinPath(path: string, root: string): boolean;
6
+ /**
7
+ * Symlink-aware version of isWithinPath. Resolves symlinks on both the path
8
+ * and root before comparing, preventing bypass via symlinks inside the
9
+ * allowed root that point outside it.
10
+ *
11
+ * Falls back to lexical isWithinPath if realpath fails (e.g., path does not
12
+ * exist yet), which is safe for write targets that haven't been created.
13
+ */
14
+ export declare function isWithinPathResolved(path: string, root: string): Promise<boolean>;
6
15
  export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
7
16
  export declare function uniqueStrings(items: string[]): string[];
8
17
  export declare function dateOnly(timestamp: string): string;
@@ -36,6 +36,27 @@ export function isWithinPath(path, root) {
36
36
  return true;
37
37
  return normalizedPath.startsWith(`${normalizedRoot}${process.platform === "win32" ? "\\" : "/"}`);
38
38
  }
39
+ /**
40
+ * Symlink-aware version of isWithinPath. Resolves symlinks on both the path
41
+ * and root before comparing, preventing bypass via symlinks inside the
42
+ * allowed root that point outside it.
43
+ *
44
+ * Falls back to lexical isWithinPath if realpath fails (e.g., path does not
45
+ * exist yet), which is safe for write targets that haven't been created.
46
+ */
47
+ export async function isWithinPathResolved(path, root) {
48
+ try {
49
+ const resolvedPath = await realpath(path);
50
+ const resolvedRoot = await realpath(root);
51
+ return isWithinPath(resolvedPath, resolvedRoot);
52
+ }
53
+ catch {
54
+ // If realpath fails (path doesn't exist yet, broken symlink, etc.),
55
+ // fall back to lexical check. For write targets this is safe because
56
+ // the parent directory should already be within the root.
57
+ return isWithinPath(path, root);
58
+ }
59
+ }
39
60
  export function isPlainObject(value) {
40
61
  return typeof value === "object" && value !== null && !Array.isArray(value);
41
62
  }
@@ -8,7 +8,7 @@ import { narrateDashboard } from "./dashboard-narrator.js";
8
8
  import { writeDashboard } from "./dashboard-writer.js";
9
9
  import { parseNextFlags } from "./next-flags.js";
10
10
  import { phaseCompactionExtension } from "./phase-compaction.js";
11
- import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPath, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
11
+ import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, canonicalPath, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isWithinPathResolved, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, packagedWorkspaceDir, pathExists, PACKAGE_VERSION, PhasePreflightError, PIPELINE_ALIASES, publishEntry, resolvePhase, resolvePipelineChoice, runPhasePreflight, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../../core/index.js";
12
12
  import { initLibrary } from "../../core/library.js";
13
13
  import { resolveUserConfigPath } from "../../core/orchestrator-config.js";
14
14
  const STATUS_WIDGET_ID = "codecarto-widget";
@@ -167,7 +167,8 @@ export default function codeCartographerExtension(pi) {
167
167
  if (config.library.path && await discoverLibrary(config.library.path)) {
168
168
  allowedRoots.push(await canonicalPath(config.library.path));
169
169
  }
170
- if (!allowedRoots.some((allowedRoot) => isWithinPath(targetPath, allowedRoot))) {
170
+ const withinAllowed = await Promise.all(allowedRoots.map((allowedRoot) => isWithinPathResolved(targetPath, allowedRoot)));
171
+ if (!withinAllowed.some((result) => result)) {
171
172
  if (ctx.hasUI) {
172
173
  ctx.ui.notify(`Blocked ${event.toolName} outside .codecarto/ or configured library: ${inputPath}`, "warning");
173
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecartographer-pi",
3
- "version": "0.12.9",
3
+ "version": "0.12.10",
4
4
  "mcpName": "io.github.HuginnIndustries/codecartographer",
5
5
  "description": "Evidence-backed reverse engineering and human-gated software planning for Pi and MCP coding agents.",
6
6
  "type": "module",