@dexto/tools-filesystem 1.5.0
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/LICENSE +44 -0
- package/dist/directory-approval.integration.test.cjs +467 -0
- package/dist/directory-approval.integration.test.d.cts +2 -0
- package/dist/directory-approval.integration.test.d.ts +2 -0
- package/dist/directory-approval.integration.test.js +444 -0
- package/dist/edit-file-tool.cjs +181 -0
- package/dist/edit-file-tool.d.cts +17 -0
- package/dist/edit-file-tool.d.ts +17 -0
- package/dist/edit-file-tool.js +147 -0
- package/dist/error-codes.cjs +53 -0
- package/dist/error-codes.d.cts +32 -0
- package/dist/error-codes.d.ts +32 -0
- package/dist/error-codes.js +29 -0
- package/dist/errors.cjs +302 -0
- package/dist/errors.d.cts +112 -0
- package/dist/errors.d.ts +112 -0
- package/dist/errors.js +278 -0
- package/dist/file-tool-types.cjs +16 -0
- package/dist/file-tool-types.d.cts +46 -0
- package/dist/file-tool-types.d.ts +46 -0
- package/dist/file-tool-types.js +0 -0
- package/dist/filesystem-service.cjs +526 -0
- package/dist/filesystem-service.d.cts +107 -0
- package/dist/filesystem-service.d.ts +107 -0
- package/dist/filesystem-service.js +492 -0
- package/dist/glob-files-tool.cjs +70 -0
- package/dist/glob-files-tool.d.cts +16 -0
- package/dist/glob-files-tool.d.ts +16 -0
- package/dist/glob-files-tool.js +46 -0
- package/dist/grep-content-tool.cjs +86 -0
- package/dist/grep-content-tool.d.cts +16 -0
- package/dist/grep-content-tool.d.ts +16 -0
- package/dist/grep-content-tool.js +62 -0
- package/dist/index.cjs +55 -0
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +22 -0
- package/dist/path-validator.cjs +232 -0
- package/dist/path-validator.d.cts +90 -0
- package/dist/path-validator.d.ts +90 -0
- package/dist/path-validator.js +198 -0
- package/dist/path-validator.test.cjs +444 -0
- package/dist/path-validator.test.d.cts +2 -0
- package/dist/path-validator.test.d.ts +2 -0
- package/dist/path-validator.test.js +443 -0
- package/dist/read-file-tool.cjs +117 -0
- package/dist/read-file-tool.d.cts +17 -0
- package/dist/read-file-tool.d.ts +17 -0
- package/dist/read-file-tool.js +83 -0
- package/dist/tool-provider.cjs +108 -0
- package/dist/tool-provider.d.cts +74 -0
- package/dist/tool-provider.d.ts +74 -0
- package/dist/tool-provider.js +84 -0
- package/dist/types.cjs +16 -0
- package/dist/types.d.cts +172 -0
- package/dist/types.d.ts +172 -0
- package/dist/types.js +0 -0
- package/dist/write-file-tool.cjs +177 -0
- package/dist/write-file-tool.d.cts +17 -0
- package/dist/write-file-tool.d.ts +17 -0
- package/dist/write-file-tool.js +143 -0
- package/package.json +42 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { FileSystemConfig, PathValidation } from './types.js';
|
|
2
|
+
import { IDextoLogger } from '@dexto/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Path Validator
|
|
6
|
+
*
|
|
7
|
+
* Security-focused path validation for file system operations
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Callback type for checking if a path is in an approved directory.
|
|
12
|
+
* Used to consult ApprovalManager without creating a direct dependency.
|
|
13
|
+
*/
|
|
14
|
+
type DirectoryApprovalChecker = (filePath: string) => boolean;
|
|
15
|
+
/**
|
|
16
|
+
* PathValidator - Validates file paths for security and policy compliance
|
|
17
|
+
*
|
|
18
|
+
* Security checks:
|
|
19
|
+
* 1. Path traversal detection (../, symbolic links)
|
|
20
|
+
* 2. Allowed paths enforcement (whitelist + approved directories)
|
|
21
|
+
* 3. Blocked paths detection (blacklist)
|
|
22
|
+
* 4. File extension restrictions
|
|
23
|
+
* 5. Absolute path normalization
|
|
24
|
+
*
|
|
25
|
+
* PathValidator can optionally consult an external approval checker (e.g., ApprovalManager)
|
|
26
|
+
* to determine if paths outside the config's allowed paths are accessible.
|
|
27
|
+
*/
|
|
28
|
+
declare class PathValidator {
|
|
29
|
+
private config;
|
|
30
|
+
private normalizedAllowedPaths;
|
|
31
|
+
private normalizedBlockedPaths;
|
|
32
|
+
private normalizedBlockedExtensions;
|
|
33
|
+
private logger;
|
|
34
|
+
private directoryApprovalChecker;
|
|
35
|
+
constructor(config: FileSystemConfig, logger: IDextoLogger);
|
|
36
|
+
/**
|
|
37
|
+
* Set a callback to check if a path is in an approved directory.
|
|
38
|
+
* This allows PathValidator to consult ApprovalManager without a direct dependency.
|
|
39
|
+
*
|
|
40
|
+
* @param checker Function that returns true if path is in an approved directory
|
|
41
|
+
*/
|
|
42
|
+
setDirectoryApprovalChecker(checker: DirectoryApprovalChecker): void;
|
|
43
|
+
/**
|
|
44
|
+
* Validate a file path for security and policy compliance
|
|
45
|
+
*/
|
|
46
|
+
validatePath(filePath: string): PathValidation;
|
|
47
|
+
/**
|
|
48
|
+
* Check if path contains traversal attempts
|
|
49
|
+
*/
|
|
50
|
+
private hasPathTraversal;
|
|
51
|
+
/**
|
|
52
|
+
* Check if path is within allowed paths (whitelist check)
|
|
53
|
+
* Also consults the directory approval checker if configured.
|
|
54
|
+
*/
|
|
55
|
+
private isPathAllowed;
|
|
56
|
+
/**
|
|
57
|
+
* Check if path matches blocked patterns (blacklist check)
|
|
58
|
+
*/
|
|
59
|
+
private isPathBlocked;
|
|
60
|
+
/**
|
|
61
|
+
* Quick check if a path is allowed (for internal use)
|
|
62
|
+
*/
|
|
63
|
+
isPathAllowedQuick(normalizedPath: string): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Check if a file path is within the configured allowed paths (from config only).
|
|
66
|
+
* This method does NOT consult ApprovalManager - it only checks the static config paths.
|
|
67
|
+
*
|
|
68
|
+
* This is used by file tools to determine if a path needs directory approval.
|
|
69
|
+
* Paths within config-allowed directories don't need directory approval prompts.
|
|
70
|
+
*
|
|
71
|
+
* @param filePath The file path to check (can be relative or absolute)
|
|
72
|
+
* @returns true if the path is within config-allowed paths, false otherwise
|
|
73
|
+
*/
|
|
74
|
+
isPathWithinAllowed(filePath: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Check if path is within config-allowed paths only (no approval checker).
|
|
77
|
+
* Used for prompting decisions.
|
|
78
|
+
*/
|
|
79
|
+
private isInConfigAllowedPaths;
|
|
80
|
+
/**
|
|
81
|
+
* Get normalized allowed paths
|
|
82
|
+
*/
|
|
83
|
+
getAllowedPaths(): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Get blocked paths
|
|
86
|
+
*/
|
|
87
|
+
getBlockedPaths(): string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { type DirectoryApprovalChecker, PathValidator };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
class PathValidator {
|
|
4
|
+
config;
|
|
5
|
+
normalizedAllowedPaths;
|
|
6
|
+
normalizedBlockedPaths;
|
|
7
|
+
normalizedBlockedExtensions;
|
|
8
|
+
logger;
|
|
9
|
+
directoryApprovalChecker;
|
|
10
|
+
constructor(config, logger) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
const workingDir = config.workingDirectory || process.cwd();
|
|
14
|
+
this.normalizedAllowedPaths = config.allowedPaths.map((p) => path.resolve(workingDir, p));
|
|
15
|
+
this.normalizedBlockedPaths = config.blockedPaths.map((p) => path.normalize(p));
|
|
16
|
+
this.normalizedBlockedExtensions = (config.blockedExtensions || []).map((ext) => {
|
|
17
|
+
const e = ext.startsWith(".") ? ext : `.${ext}`;
|
|
18
|
+
return e.toLowerCase();
|
|
19
|
+
});
|
|
20
|
+
this.logger.debug(
|
|
21
|
+
`PathValidator initialized with ${this.normalizedAllowedPaths.length} allowed paths`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Set a callback to check if a path is in an approved directory.
|
|
26
|
+
* This allows PathValidator to consult ApprovalManager without a direct dependency.
|
|
27
|
+
*
|
|
28
|
+
* @param checker Function that returns true if path is in an approved directory
|
|
29
|
+
*/
|
|
30
|
+
setDirectoryApprovalChecker(checker) {
|
|
31
|
+
this.directoryApprovalChecker = checker;
|
|
32
|
+
this.logger.debug("Directory approval checker configured");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate a file path for security and policy compliance
|
|
36
|
+
*/
|
|
37
|
+
validatePath(filePath) {
|
|
38
|
+
if (!filePath || filePath.trim() === "") {
|
|
39
|
+
return {
|
|
40
|
+
isValid: false,
|
|
41
|
+
error: "Path cannot be empty"
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const workingDir = this.config.workingDirectory || process.cwd();
|
|
45
|
+
let normalizedPath;
|
|
46
|
+
try {
|
|
47
|
+
normalizedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(workingDir, filePath);
|
|
48
|
+
try {
|
|
49
|
+
normalizedPath = realpathSync.native(normalizedPath);
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
isValid: false,
|
|
55
|
+
error: `Failed to normalize path: ${error instanceof Error ? error.message : String(error)}`
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (this.hasPathTraversal(filePath, normalizedPath)) {
|
|
59
|
+
return {
|
|
60
|
+
isValid: false,
|
|
61
|
+
error: "Path traversal detected"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (!this.isPathAllowed(normalizedPath)) {
|
|
65
|
+
return {
|
|
66
|
+
isValid: false,
|
|
67
|
+
error: `Path is not within allowed paths. Allowed: ${this.normalizedAllowedPaths.join(", ")}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const blockedReason = this.isPathBlocked(normalizedPath);
|
|
71
|
+
if (blockedReason) {
|
|
72
|
+
return {
|
|
73
|
+
isValid: false,
|
|
74
|
+
error: `Path is blocked: ${blockedReason}`
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const ext = path.extname(normalizedPath).toLowerCase();
|
|
78
|
+
if (ext && this.normalizedBlockedExtensions.includes(ext)) {
|
|
79
|
+
return {
|
|
80
|
+
isValid: false,
|
|
81
|
+
error: `File extension ${ext} is not allowed`
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
isValid: true,
|
|
86
|
+
normalizedPath
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if path contains traversal attempts
|
|
91
|
+
*/
|
|
92
|
+
hasPathTraversal(originalPath, normalizedPath) {
|
|
93
|
+
if (originalPath.includes("../") || originalPath.includes("..\\")) {
|
|
94
|
+
const workingDir = this.config.workingDirectory || process.cwd();
|
|
95
|
+
const relative = path.relative(workingDir, normalizedPath);
|
|
96
|
+
if (relative.startsWith("..")) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if path is within allowed paths (whitelist check)
|
|
104
|
+
* Also consults the directory approval checker if configured.
|
|
105
|
+
*/
|
|
106
|
+
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;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Check if path matches blocked patterns (blacklist check)
|
|
124
|
+
*/
|
|
125
|
+
isPathBlocked(normalizedPath) {
|
|
126
|
+
const roots = this.normalizedAllowedPaths.length > 0 ? this.normalizedAllowedPaths : [this.config.workingDirectory || process.cwd()];
|
|
127
|
+
for (const blocked of this.normalizedBlockedPaths) {
|
|
128
|
+
for (const root of roots) {
|
|
129
|
+
const blockedFull = path.isAbsolute(blocked) ? path.normalize(blocked) : path.resolve(root, blocked);
|
|
130
|
+
if (normalizedPath === blockedFull || normalizedPath.startsWith(blockedFull + path.sep)) {
|
|
131
|
+
return `Within blocked directory: ${blocked}`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Quick check if a path is allowed (for internal use)
|
|
139
|
+
*/
|
|
140
|
+
isPathAllowedQuick(normalizedPath) {
|
|
141
|
+
return this.isPathAllowed(normalizedPath) && !this.isPathBlocked(normalizedPath);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if a file path is within the configured allowed paths (from config only).
|
|
145
|
+
* This method does NOT consult ApprovalManager - it only checks the static config paths.
|
|
146
|
+
*
|
|
147
|
+
* This is used by file tools to determine if a path needs directory approval.
|
|
148
|
+
* Paths within config-allowed directories don't need directory approval prompts.
|
|
149
|
+
*
|
|
150
|
+
* @param filePath The file path to check (can be relative or absolute)
|
|
151
|
+
* @returns true if the path is within config-allowed paths, false otherwise
|
|
152
|
+
*/
|
|
153
|
+
isPathWithinAllowed(filePath) {
|
|
154
|
+
if (!filePath || filePath.trim() === "") {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const workingDir = this.config.workingDirectory || process.cwd();
|
|
158
|
+
let normalizedPath;
|
|
159
|
+
try {
|
|
160
|
+
normalizedPath = path.isAbsolute(filePath) ? path.resolve(filePath) : path.resolve(workingDir, filePath);
|
|
161
|
+
try {
|
|
162
|
+
normalizedPath = realpathSync.native(normalizedPath);
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return this.isInConfigAllowedPaths(normalizedPath);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check if path is within config-allowed paths only (no approval checker).
|
|
172
|
+
* Used for prompting decisions.
|
|
173
|
+
*/
|
|
174
|
+
isInConfigAllowedPaths(normalizedPath) {
|
|
175
|
+
if (this.normalizedAllowedPaths.length === 0) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return this.normalizedAllowedPaths.some((allowedPath) => {
|
|
179
|
+
const relative = path.relative(allowedPath, normalizedPath);
|
|
180
|
+
return !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get normalized allowed paths
|
|
185
|
+
*/
|
|
186
|
+
getAllowedPaths() {
|
|
187
|
+
return [...this.normalizedAllowedPaths];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get blocked paths
|
|
191
|
+
*/
|
|
192
|
+
getBlockedPaths() {
|
|
193
|
+
return [...this.normalizedBlockedPaths];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
export {
|
|
197
|
+
PathValidator
|
|
198
|
+
};
|