@dexto/tools-filesystem 1.5.2 → 1.5.4

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.
Files changed (45) hide show
  1. package/dist/directory-approval.integration.test.cjs +36 -32
  2. package/dist/directory-approval.integration.test.js +36 -32
  3. package/dist/edit-file-tool.cjs +43 -19
  4. package/dist/edit-file-tool.js +43 -19
  5. package/dist/edit-file-tool.test.cjs +203 -0
  6. package/dist/edit-file-tool.test.d.cts +2 -0
  7. package/dist/edit-file-tool.test.d.ts +2 -0
  8. package/dist/edit-file-tool.test.js +180 -0
  9. package/dist/filesystem-service.cjs +24 -14
  10. package/dist/filesystem-service.d.cts +8 -3
  11. package/dist/filesystem-service.d.ts +8 -3
  12. package/dist/filesystem-service.js +24 -14
  13. package/dist/filesystem-service.test.cjs +233 -0
  14. package/dist/filesystem-service.test.d.cts +2 -0
  15. package/dist/filesystem-service.test.d.ts +2 -0
  16. package/dist/filesystem-service.test.js +210 -0
  17. package/dist/glob-files-tool.cjs +56 -3
  18. package/dist/glob-files-tool.d.cts +4 -3
  19. package/dist/glob-files-tool.d.ts +4 -3
  20. package/dist/glob-files-tool.js +46 -3
  21. package/dist/grep-content-tool.cjs +55 -3
  22. package/dist/grep-content-tool.d.cts +4 -3
  23. package/dist/grep-content-tool.d.ts +4 -3
  24. package/dist/grep-content-tool.js +45 -3
  25. package/dist/path-validator.cjs +29 -20
  26. package/dist/path-validator.d.cts +9 -2
  27. package/dist/path-validator.d.ts +9 -2
  28. package/dist/path-validator.js +29 -20
  29. package/dist/path-validator.test.cjs +54 -48
  30. package/dist/path-validator.test.js +54 -48
  31. package/dist/read-file-tool.cjs +2 -2
  32. package/dist/read-file-tool.js +2 -2
  33. package/dist/tool-provider.cjs +22 -7
  34. package/dist/tool-provider.d.cts +4 -1
  35. package/dist/tool-provider.d.ts +4 -1
  36. package/dist/tool-provider.js +22 -7
  37. package/dist/types.d.cts +6 -0
  38. package/dist/types.d.ts +6 -0
  39. package/dist/write-file-tool.cjs +41 -7
  40. package/dist/write-file-tool.js +46 -8
  41. package/dist/write-file-tool.test.cjs +217 -0
  42. package/dist/write-file-tool.test.d.cts +2 -0
  43. package/dist/write-file-tool.test.d.ts +2 -0
  44. package/dist/write-file-tool.test.js +194 -0
  45. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { InternalTool } from '@dexto/core';
2
- import { FileSystemService } from './filesystem-service.js';
2
+ import { FileToolOptions } from './file-tool-types.js';
3
+ import './filesystem-service.js';
3
4
  import './types.js';
4
5
 
5
6
  /**
@@ -9,8 +10,8 @@ import './types.js';
9
10
  */
10
11
 
11
12
  /**
12
- * Create the glob_files internal tool
13
+ * Create the glob_files internal tool with directory approval support
13
14
  */
14
- declare function createGlobFilesTool(fileSystemService: FileSystemService): InternalTool;
15
+ declare function createGlobFilesTool(options: FileToolOptions): InternalTool;
15
16
 
16
17
  export { createGlobFilesTool };
@@ -1,18 +1,61 @@
1
+ import * as path from "node:path";
1
2
  import { z } from "zod";
3
+ import { ApprovalType } from "@dexto/core";
2
4
  const GlobFilesInputSchema = z.object({
3
5
  pattern: z.string().describe('Glob pattern to match files (e.g., "**/*.ts", "src/**/*.js")'),
4
6
  path: z.string().optional().describe("Base directory to search from (defaults to working directory)"),
5
7
  max_results: z.number().int().positive().optional().default(1e3).describe("Maximum number of results to return (default: 1000)")
6
8
  }).strict();
7
- function createGlobFilesTool(fileSystemService) {
9
+ function createGlobFilesTool(options) {
10
+ const { fileSystemService, directoryApproval } = options;
11
+ let pendingApprovalSearchDir;
8
12
  return {
9
13
  id: "glob_files",
10
14
  description: "Find files matching a glob pattern. Supports standard glob syntax like **/*.js for recursive matches, *.ts for files in current directory, and src/**/*.tsx for nested paths. Returns array of file paths with metadata (size, modified date). Results are limited to allowed paths only.",
11
15
  inputSchema: GlobFilesInputSchema,
16
+ /**
17
+ * Check if this glob operation needs directory access approval.
18
+ * Returns custom approval request if the search directory is outside allowed paths.
19
+ */
20
+ getApprovalOverride: async (args) => {
21
+ const { path: searchPath } = args;
22
+ const baseDir = fileSystemService.getWorkingDirectory();
23
+ const searchDir = path.resolve(baseDir, searchPath || ".");
24
+ const isAllowed = await fileSystemService.isPathWithinConfigAllowed(searchDir);
25
+ if (isAllowed) {
26
+ return null;
27
+ }
28
+ if (directoryApproval?.isSessionApproved(searchDir)) {
29
+ return null;
30
+ }
31
+ pendingApprovalSearchDir = searchDir;
32
+ return {
33
+ type: ApprovalType.DIRECTORY_ACCESS,
34
+ metadata: {
35
+ path: searchDir,
36
+ parentDir: searchDir,
37
+ operation: "search",
38
+ toolName: "glob_files"
39
+ }
40
+ };
41
+ },
42
+ /**
43
+ * Handle approved directory access - remember the directory for session
44
+ */
45
+ onApprovalGranted: (response) => {
46
+ if (!directoryApproval || !pendingApprovalSearchDir) return;
47
+ const data = response.data;
48
+ const rememberDirectory = data?.rememberDirectory ?? false;
49
+ directoryApproval.addApproved(
50
+ pendingApprovalSearchDir,
51
+ rememberDirectory ? "session" : "once"
52
+ );
53
+ pendingApprovalSearchDir = void 0;
54
+ },
12
55
  execute: async (input, _context) => {
13
- const { pattern, path, max_results } = input;
56
+ const { pattern, path: path2, max_results } = input;
14
57
  const result = await fileSystemService.globFiles(pattern, {
15
- cwd: path,
58
+ cwd: path2,
16
59
  maxResults: max_results,
17
60
  includeMetadata: true
18
61
  });
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,13 +17,23 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var grep_content_tool_exports = {};
20
30
  __export(grep_content_tool_exports, {
21
31
  createGrepContentTool: () => createGrepContentTool
22
32
  });
23
33
  module.exports = __toCommonJS(grep_content_tool_exports);
34
+ var path = __toESM(require("node:path"), 1);
24
35
  var import_zod = require("zod");
36
+ var import_core = require("@dexto/core");
25
37
  const GrepContentInputSchema = import_zod.z.object({
26
38
  pattern: import_zod.z.string().describe("Regular expression pattern to search for"),
27
39
  path: import_zod.z.string().optional().describe("Directory to search in (defaults to working directory)"),
@@ -32,15 +44,55 @@ const GrepContentInputSchema = import_zod.z.object({
32
44
  case_insensitive: import_zod.z.boolean().optional().default(false).describe("Perform case-insensitive search (default: false)"),
33
45
  max_results: import_zod.z.number().int().positive().optional().default(100).describe("Maximum number of results to return (default: 100)")
34
46
  }).strict();
35
- function createGrepContentTool(fileSystemService) {
47
+ function createGrepContentTool(options) {
48
+ const { fileSystemService, directoryApproval } = options;
49
+ let pendingApprovalSearchDir;
36
50
  return {
37
51
  id: "grep_content",
38
52
  description: 'Search for text patterns in files using regular expressions. Returns matching lines with file path, line number, and optional context lines. Use glob parameter to filter specific file types (e.g., "*.ts"). Supports case-insensitive search. Great for finding code patterns, function definitions, or specific text across multiple files.',
39
53
  inputSchema: GrepContentInputSchema,
54
+ /**
55
+ * Check if this grep operation needs directory access approval.
56
+ * Returns custom approval request if the search directory is outside allowed paths.
57
+ */
58
+ getApprovalOverride: async (args) => {
59
+ const { path: searchPath } = args;
60
+ const searchDir = path.resolve(searchPath || process.cwd());
61
+ const isAllowed = await fileSystemService.isPathWithinConfigAllowed(searchDir);
62
+ if (isAllowed) {
63
+ return null;
64
+ }
65
+ if (directoryApproval?.isSessionApproved(searchDir)) {
66
+ return null;
67
+ }
68
+ pendingApprovalSearchDir = searchDir;
69
+ return {
70
+ type: import_core.ApprovalType.DIRECTORY_ACCESS,
71
+ metadata: {
72
+ path: searchDir,
73
+ parentDir: searchDir,
74
+ operation: "search",
75
+ toolName: "grep_content"
76
+ }
77
+ };
78
+ },
79
+ /**
80
+ * Handle approved directory access - remember the directory for session
81
+ */
82
+ onApprovalGranted: (response) => {
83
+ if (!directoryApproval || !pendingApprovalSearchDir) return;
84
+ const data = response.data;
85
+ const rememberDirectory = data?.rememberDirectory ?? false;
86
+ directoryApproval.addApproved(
87
+ pendingApprovalSearchDir,
88
+ rememberDirectory ? "session" : "once"
89
+ );
90
+ pendingApprovalSearchDir = void 0;
91
+ },
40
92
  execute: async (input, _context) => {
41
- const { pattern, path, glob, context_lines, case_insensitive, max_results } = input;
93
+ const { pattern, path: path2, glob, context_lines, case_insensitive, max_results } = input;
42
94
  const result = await fileSystemService.searchContent(pattern, {
43
- path,
95
+ path: path2,
44
96
  glob,
45
97
  contextLines: context_lines,
46
98
  caseInsensitive: case_insensitive,
@@ -1,5 +1,6 @@
1
1
  import { InternalTool } from '@dexto/core';
2
- import { FileSystemService } from './filesystem-service.cjs';
2
+ import { FileToolOptions } from './file-tool-types.cjs';
3
+ import './filesystem-service.cjs';
3
4
  import './types.cjs';
4
5
 
5
6
  /**
@@ -9,8 +10,8 @@ import './types.cjs';
9
10
  */
10
11
 
11
12
  /**
12
- * Create the grep_content internal tool
13
+ * Create the grep_content internal tool with directory approval support
13
14
  */
14
- declare function createGrepContentTool(fileSystemService: FileSystemService): InternalTool;
15
+ declare function createGrepContentTool(options: FileToolOptions): InternalTool;
15
16
 
16
17
  export { createGrepContentTool };
@@ -1,5 +1,6 @@
1
1
  import { InternalTool } from '@dexto/core';
2
- import { FileSystemService } from './filesystem-service.js';
2
+ import { FileToolOptions } from './file-tool-types.js';
3
+ import './filesystem-service.js';
3
4
  import './types.js';
4
5
 
5
6
  /**
@@ -9,8 +10,8 @@ import './types.js';
9
10
  */
10
11
 
11
12
  /**
12
- * Create the grep_content internal tool
13
+ * Create the grep_content internal tool with directory approval support
13
14
  */
14
- declare function createGrepContentTool(fileSystemService: FileSystemService): InternalTool;
15
+ declare function createGrepContentTool(options: FileToolOptions): InternalTool;
15
16
 
16
17
  export { createGrepContentTool };
@@ -1,4 +1,6 @@
1
+ import * as path from "node:path";
1
2
  import { z } from "zod";
3
+ import { ApprovalType } from "@dexto/core";
2
4
  const GrepContentInputSchema = z.object({
3
5
  pattern: z.string().describe("Regular expression pattern to search for"),
4
6
  path: z.string().optional().describe("Directory to search in (defaults to working directory)"),
@@ -9,15 +11,55 @@ const GrepContentInputSchema = z.object({
9
11
  case_insensitive: z.boolean().optional().default(false).describe("Perform case-insensitive search (default: false)"),
10
12
  max_results: z.number().int().positive().optional().default(100).describe("Maximum number of results to return (default: 100)")
11
13
  }).strict();
12
- function createGrepContentTool(fileSystemService) {
14
+ function createGrepContentTool(options) {
15
+ const { fileSystemService, directoryApproval } = options;
16
+ let pendingApprovalSearchDir;
13
17
  return {
14
18
  id: "grep_content",
15
19
  description: 'Search for text patterns in files using regular expressions. Returns matching lines with file path, line number, and optional context lines. Use glob parameter to filter specific file types (e.g., "*.ts"). Supports case-insensitive search. Great for finding code patterns, function definitions, or specific text across multiple files.',
16
20
  inputSchema: GrepContentInputSchema,
21
+ /**
22
+ * Check if this grep operation needs directory access approval.
23
+ * Returns custom approval request if the search directory is outside allowed paths.
24
+ */
25
+ getApprovalOverride: async (args) => {
26
+ const { path: searchPath } = args;
27
+ const searchDir = path.resolve(searchPath || process.cwd());
28
+ const isAllowed = await fileSystemService.isPathWithinConfigAllowed(searchDir);
29
+ if (isAllowed) {
30
+ return null;
31
+ }
32
+ if (directoryApproval?.isSessionApproved(searchDir)) {
33
+ return null;
34
+ }
35
+ pendingApprovalSearchDir = searchDir;
36
+ return {
37
+ type: ApprovalType.DIRECTORY_ACCESS,
38
+ metadata: {
39
+ path: searchDir,
40
+ parentDir: searchDir,
41
+ operation: "search",
42
+ toolName: "grep_content"
43
+ }
44
+ };
45
+ },
46
+ /**
47
+ * Handle approved directory access - remember the directory for session
48
+ */
49
+ onApprovalGranted: (response) => {
50
+ if (!directoryApproval || !pendingApprovalSearchDir) return;
51
+ const data = response.data;
52
+ const rememberDirectory = data?.rememberDirectory ?? false;
53
+ directoryApproval.addApproved(
54
+ pendingApprovalSearchDir,
55
+ rememberDirectory ? "session" : "once"
56
+ );
57
+ pendingApprovalSearchDir = void 0;
58
+ },
17
59
  execute: async (input, _context) => {
18
- const { pattern, path, glob, context_lines, case_insensitive, max_results } = input;
60
+ const { pattern, path: path2, glob, context_lines, case_insensitive, max_results } = input;
19
61
  const result = await fileSystemService.searchContent(pattern, {
20
- path,
62
+ path: path2,
21
63
  glob,
22
64
  contextLines: context_lines,
23
65
  caseInsensitive: case_insensitive,
@@ -32,7 +32,7 @@ __export(path_validator_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(path_validator_exports);
34
34
  var path = __toESM(require("node:path"), 1);
35
- var import_node_fs = require("node:fs");
35
+ var fs = __toESM(require("node:fs/promises"), 1);
36
36
  class PathValidator {
37
37
  config;
38
38
  normalizedAllowedPaths;
@@ -67,7 +67,7 @@ class PathValidator {
67
67
  /**
68
68
  * Validate a file path for security and policy compliance
69
69
  */
70
- validatePath(filePath) {
70
+ async validatePath(filePath) {
71
71
  if (!filePath || filePath.trim() === "") {
72
72
  return {
73
73
  isValid: false,
@@ -79,7 +79,7 @@ class PathValidator {
79
79
  try {
80
80
  normalizedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(workingDir, filePath);
81
81
  try {
82
- normalizedPath = import_node_fs.realpathSync.native(normalizedPath);
82
+ normalizedPath = await fs.realpath(normalizedPath);
83
83
  } catch {
84
84
  }
85
85
  } catch (error) {
@@ -135,22 +135,10 @@ class PathValidator {
135
135
  /**
136
136
  * Check if path is within allowed paths (whitelist check)
137
137
  * Also consults the directory approval checker if configured.
138
+ * Uses the sync version since the path is already normalized at this point.
138
139
  */
139
140
  isPathAllowed(normalizedPath) {
140
- if (this.normalizedAllowedPaths.length === 0) {
141
- return true;
142
- }
143
- const isInConfigPaths = this.normalizedAllowedPaths.some((allowedPath) => {
144
- const relative = path.relative(allowedPath, normalizedPath);
145
- return !relative.startsWith("..") && !path.isAbsolute(relative);
146
- });
147
- if (isInConfigPaths) {
148
- return true;
149
- }
150
- if (this.directoryApprovalChecker) {
151
- return this.directoryApprovalChecker(normalizedPath);
152
- }
153
- return false;
141
+ return this.isPathAllowedSync(normalizedPath);
154
142
  }
155
143
  /**
156
144
  * Check if path matches blocked patterns (blacklist check)
@@ -169,9 +157,30 @@ class PathValidator {
169
157
  }
170
158
  /**
171
159
  * Quick check if a path is allowed (for internal use)
160
+ * Note: This assumes the path is already normalized/canonicalized
172
161
  */
173
162
  isPathAllowedQuick(normalizedPath) {
174
- return this.isPathAllowed(normalizedPath) && !this.isPathBlocked(normalizedPath);
163
+ return this.isPathAllowedSync(normalizedPath) && !this.isPathBlocked(normalizedPath);
164
+ }
165
+ /**
166
+ * Synchronous path allowed check (for already-normalized paths)
167
+ * This is used internally when we already have a canonicalized path
168
+ */
169
+ isPathAllowedSync(normalizedPath) {
170
+ if (this.normalizedAllowedPaths.length === 0) {
171
+ return true;
172
+ }
173
+ const isInConfigPaths = this.normalizedAllowedPaths.some((allowedPath) => {
174
+ const relative = path.relative(allowedPath, normalizedPath);
175
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
176
+ });
177
+ if (isInConfigPaths) {
178
+ return true;
179
+ }
180
+ if (this.directoryApprovalChecker) {
181
+ return this.directoryApprovalChecker(normalizedPath);
182
+ }
183
+ return false;
175
184
  }
176
185
  /**
177
186
  * Check if a file path is within the configured allowed paths (from config only).
@@ -183,7 +192,7 @@ class PathValidator {
183
192
  * @param filePath The file path to check (can be relative or absolute)
184
193
  * @returns true if the path is within config-allowed paths, false otherwise
185
194
  */
186
- isPathWithinAllowed(filePath) {
195
+ async isPathWithinAllowed(filePath) {
187
196
  if (!filePath || filePath.trim() === "") {
188
197
  return false;
189
198
  }
@@ -192,7 +201,7 @@ class PathValidator {
192
201
  try {
193
202
  normalizedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(workingDir, filePath);
194
203
  try {
195
- normalizedPath = import_node_fs.realpathSync.native(normalizedPath);
204
+ normalizedPath = await fs.realpath(normalizedPath);
196
205
  } catch {
197
206
  }
198
207
  } catch {
@@ -43,7 +43,7 @@ declare class PathValidator {
43
43
  /**
44
44
  * Validate a file path for security and policy compliance
45
45
  */
46
- validatePath(filePath: string): PathValidation;
46
+ validatePath(filePath: string): Promise<PathValidation>;
47
47
  /**
48
48
  * Check if path contains traversal attempts
49
49
  */
@@ -51,6 +51,7 @@ declare class PathValidator {
51
51
  /**
52
52
  * Check if path is within allowed paths (whitelist check)
53
53
  * Also consults the directory approval checker if configured.
54
+ * Uses the sync version since the path is already normalized at this point.
54
55
  */
55
56
  private isPathAllowed;
56
57
  /**
@@ -59,8 +60,14 @@ declare class PathValidator {
59
60
  private isPathBlocked;
60
61
  /**
61
62
  * Quick check if a path is allowed (for internal use)
63
+ * Note: This assumes the path is already normalized/canonicalized
62
64
  */
63
65
  isPathAllowedQuick(normalizedPath: string): boolean;
66
+ /**
67
+ * Synchronous path allowed check (for already-normalized paths)
68
+ * This is used internally when we already have a canonicalized path
69
+ */
70
+ private isPathAllowedSync;
64
71
  /**
65
72
  * Check if a file path is within the configured allowed paths (from config only).
66
73
  * This method does NOT consult ApprovalManager - it only checks the static config paths.
@@ -71,7 +78,7 @@ declare class PathValidator {
71
78
  * @param filePath The file path to check (can be relative or absolute)
72
79
  * @returns true if the path is within config-allowed paths, false otherwise
73
80
  */
74
- isPathWithinAllowed(filePath: string): boolean;
81
+ isPathWithinAllowed(filePath: string): Promise<boolean>;
75
82
  /**
76
83
  * Check if path is within config-allowed paths only (no approval checker).
77
84
  * Used for prompting decisions.
@@ -43,7 +43,7 @@ declare class PathValidator {
43
43
  /**
44
44
  * Validate a file path for security and policy compliance
45
45
  */
46
- validatePath(filePath: string): PathValidation;
46
+ validatePath(filePath: string): Promise<PathValidation>;
47
47
  /**
48
48
  * Check if path contains traversal attempts
49
49
  */
@@ -51,6 +51,7 @@ declare class PathValidator {
51
51
  /**
52
52
  * Check if path is within allowed paths (whitelist check)
53
53
  * Also consults the directory approval checker if configured.
54
+ * Uses the sync version since the path is already normalized at this point.
54
55
  */
55
56
  private isPathAllowed;
56
57
  /**
@@ -59,8 +60,14 @@ declare class PathValidator {
59
60
  private isPathBlocked;
60
61
  /**
61
62
  * Quick check if a path is allowed (for internal use)
63
+ * Note: This assumes the path is already normalized/canonicalized
62
64
  */
63
65
  isPathAllowedQuick(normalizedPath: string): boolean;
66
+ /**
67
+ * Synchronous path allowed check (for already-normalized paths)
68
+ * This is used internally when we already have a canonicalized path
69
+ */
70
+ private isPathAllowedSync;
64
71
  /**
65
72
  * Check if a file path is within the configured allowed paths (from config only).
66
73
  * This method does NOT consult ApprovalManager - it only checks the static config paths.
@@ -71,7 +78,7 @@ declare class PathValidator {
71
78
  * @param filePath The file path to check (can be relative or absolute)
72
79
  * @returns true if the path is within config-allowed paths, false otherwise
73
80
  */
74
- isPathWithinAllowed(filePath: string): boolean;
81
+ isPathWithinAllowed(filePath: string): Promise<boolean>;
75
82
  /**
76
83
  * Check if path is within config-allowed paths only (no approval checker).
77
84
  * Used for prompting decisions.
@@ -1,5 +1,5 @@
1
1
  import * as path from "node:path";
2
- import { realpathSync } from "node:fs";
2
+ import * as fs from "node:fs/promises";
3
3
  class PathValidator {
4
4
  config;
5
5
  normalizedAllowedPaths;
@@ -34,7 +34,7 @@ class PathValidator {
34
34
  /**
35
35
  * Validate a file path for security and policy compliance
36
36
  */
37
- validatePath(filePath) {
37
+ async validatePath(filePath) {
38
38
  if (!filePath || filePath.trim() === "") {
39
39
  return {
40
40
  isValid: false,
@@ -46,7 +46,7 @@ class PathValidator {
46
46
  try {
47
47
  normalizedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(workingDir, filePath);
48
48
  try {
49
- normalizedPath = realpathSync.native(normalizedPath);
49
+ normalizedPath = await fs.realpath(normalizedPath);
50
50
  } catch {
51
51
  }
52
52
  } catch (error) {
@@ -102,22 +102,10 @@ class PathValidator {
102
102
  /**
103
103
  * Check if path is within allowed paths (whitelist check)
104
104
  * Also consults the directory approval checker if configured.
105
+ * Uses the sync version since the path is already normalized at this point.
105
106
  */
106
107
  isPathAllowed(normalizedPath) {
107
- if (this.normalizedAllowedPaths.length === 0) {
108
- return true;
109
- }
110
- const isInConfigPaths = this.normalizedAllowedPaths.some((allowedPath) => {
111
- const relative = path.relative(allowedPath, normalizedPath);
112
- return !relative.startsWith("..") && !path.isAbsolute(relative);
113
- });
114
- if (isInConfigPaths) {
115
- return true;
116
- }
117
- if (this.directoryApprovalChecker) {
118
- return this.directoryApprovalChecker(normalizedPath);
119
- }
120
- return false;
108
+ return this.isPathAllowedSync(normalizedPath);
121
109
  }
122
110
  /**
123
111
  * Check if path matches blocked patterns (blacklist check)
@@ -136,9 +124,30 @@ class PathValidator {
136
124
  }
137
125
  /**
138
126
  * Quick check if a path is allowed (for internal use)
127
+ * Note: This assumes the path is already normalized/canonicalized
139
128
  */
140
129
  isPathAllowedQuick(normalizedPath) {
141
- return this.isPathAllowed(normalizedPath) && !this.isPathBlocked(normalizedPath);
130
+ return this.isPathAllowedSync(normalizedPath) && !this.isPathBlocked(normalizedPath);
131
+ }
132
+ /**
133
+ * Synchronous path allowed check (for already-normalized paths)
134
+ * This is used internally when we already have a canonicalized path
135
+ */
136
+ isPathAllowedSync(normalizedPath) {
137
+ if (this.normalizedAllowedPaths.length === 0) {
138
+ return true;
139
+ }
140
+ const isInConfigPaths = this.normalizedAllowedPaths.some((allowedPath) => {
141
+ const relative = path.relative(allowedPath, normalizedPath);
142
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
143
+ });
144
+ if (isInConfigPaths) {
145
+ return true;
146
+ }
147
+ if (this.directoryApprovalChecker) {
148
+ return this.directoryApprovalChecker(normalizedPath);
149
+ }
150
+ return false;
142
151
  }
143
152
  /**
144
153
  * Check if a file path is within the configured allowed paths (from config only).
@@ -150,7 +159,7 @@ class PathValidator {
150
159
  * @param filePath The file path to check (can be relative or absolute)
151
160
  * @returns true if the path is within config-allowed paths, false otherwise
152
161
  */
153
- isPathWithinAllowed(filePath) {
162
+ async isPathWithinAllowed(filePath) {
154
163
  if (!filePath || filePath.trim() === "") {
155
164
  return false;
156
165
  }
@@ -159,7 +168,7 @@ class PathValidator {
159
168
  try {
160
169
  normalizedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(workingDir, filePath);
161
170
  try {
162
- normalizedPath = realpathSync.native(normalizedPath);
171
+ normalizedPath = await fs.realpath(normalizedPath);
163
172
  } catch {
164
173
  }
165
174
  } catch {