@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.
- package/dist/directory-approval.integration.test.cjs +36 -32
- package/dist/directory-approval.integration.test.js +36 -32
- package/dist/edit-file-tool.cjs +43 -19
- package/dist/edit-file-tool.js +43 -19
- package/dist/edit-file-tool.test.cjs +203 -0
- package/dist/edit-file-tool.test.d.cts +2 -0
- package/dist/edit-file-tool.test.d.ts +2 -0
- package/dist/edit-file-tool.test.js +180 -0
- package/dist/filesystem-service.cjs +24 -14
- package/dist/filesystem-service.d.cts +8 -3
- package/dist/filesystem-service.d.ts +8 -3
- package/dist/filesystem-service.js +24 -14
- package/dist/filesystem-service.test.cjs +233 -0
- package/dist/filesystem-service.test.d.cts +2 -0
- package/dist/filesystem-service.test.d.ts +2 -0
- package/dist/filesystem-service.test.js +210 -0
- package/dist/glob-files-tool.cjs +56 -3
- package/dist/glob-files-tool.d.cts +4 -3
- package/dist/glob-files-tool.d.ts +4 -3
- package/dist/glob-files-tool.js +46 -3
- package/dist/grep-content-tool.cjs +55 -3
- package/dist/grep-content-tool.d.cts +4 -3
- package/dist/grep-content-tool.d.ts +4 -3
- package/dist/grep-content-tool.js +45 -3
- package/dist/path-validator.cjs +29 -20
- package/dist/path-validator.d.cts +9 -2
- package/dist/path-validator.d.ts +9 -2
- package/dist/path-validator.js +29 -20
- package/dist/path-validator.test.cjs +54 -48
- package/dist/path-validator.test.js +54 -48
- package/dist/read-file-tool.cjs +2 -2
- package/dist/read-file-tool.js +2 -2
- package/dist/tool-provider.cjs +22 -7
- package/dist/tool-provider.d.cts +4 -1
- package/dist/tool-provider.d.ts +4 -1
- package/dist/tool-provider.js +22 -7
- package/dist/types.d.cts +6 -0
- package/dist/types.d.ts +6 -0
- package/dist/write-file-tool.cjs +41 -7
- package/dist/write-file-tool.js +46 -8
- package/dist/write-file-tool.test.cjs +217 -0
- package/dist/write-file-tool.test.d.cts +2 -0
- package/dist/write-file-tool.test.d.ts +2 -0
- package/dist/write-file-tool.test.js +194 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InternalTool } from '@dexto/core';
|
|
2
|
-
import {
|
|
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(
|
|
15
|
+
declare function createGlobFilesTool(options: FileToolOptions): InternalTool;
|
|
15
16
|
|
|
16
17
|
export { createGlobFilesTool };
|
package/dist/glob-files-tool.js
CHANGED
|
@@ -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(
|
|
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:
|
|
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(
|
|
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 {
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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,
|
package/dist/path-validator.cjs
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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.
|
package/dist/path-validator.d.ts
CHANGED
|
@@ -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.
|
package/dist/path-validator.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
171
|
+
normalizedPath = await fs.realpath(normalizedPath);
|
|
163
172
|
} catch {
|
|
164
173
|
}
|
|
165
174
|
} catch {
|