@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
|
@@ -16,7 +16,7 @@ const createMockLogger = () => ({
|
|
|
16
16
|
});
|
|
17
17
|
(0, import_vitest.describe)("validatePath", () => {
|
|
18
18
|
(0, import_vitest.describe)("Empty and Invalid Paths", () => {
|
|
19
|
-
(0, import_vitest.it)("should reject empty path", () => {
|
|
19
|
+
(0, import_vitest.it)("should reject empty path", async () => {
|
|
20
20
|
const validator = new import_path_validator.PathValidator(
|
|
21
21
|
{
|
|
22
22
|
allowedPaths: ["/home/user/project"],
|
|
@@ -29,11 +29,11 @@ const createMockLogger = () => ({
|
|
|
29
29
|
},
|
|
30
30
|
mockLogger
|
|
31
31
|
);
|
|
32
|
-
const result = validator.validatePath("");
|
|
32
|
+
const result = await validator.validatePath("");
|
|
33
33
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
34
34
|
(0, import_vitest.expect)(result.error).toBe("Path cannot be empty");
|
|
35
35
|
});
|
|
36
|
-
(0, import_vitest.it)("should reject whitespace-only path", () => {
|
|
36
|
+
(0, import_vitest.it)("should reject whitespace-only path", async () => {
|
|
37
37
|
const validator = new import_path_validator.PathValidator(
|
|
38
38
|
{
|
|
39
39
|
allowedPaths: ["/home/user/project"],
|
|
@@ -46,13 +46,13 @@ const createMockLogger = () => ({
|
|
|
46
46
|
},
|
|
47
47
|
mockLogger
|
|
48
48
|
);
|
|
49
|
-
const result = validator.validatePath(" ");
|
|
49
|
+
const result = await validator.validatePath(" ");
|
|
50
50
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
51
51
|
(0, import_vitest.expect)(result.error).toBe("Path cannot be empty");
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
(0, import_vitest.describe)("Allowed Paths", () => {
|
|
55
|
-
(0, import_vitest.it)("should allow paths within allowed directories", () => {
|
|
55
|
+
(0, import_vitest.it)("should allow paths within allowed directories", async () => {
|
|
56
56
|
const validator = new import_path_validator.PathValidator(
|
|
57
57
|
{
|
|
58
58
|
allowedPaths: ["/home/user/project"],
|
|
@@ -65,11 +65,11 @@ const createMockLogger = () => ({
|
|
|
65
65
|
},
|
|
66
66
|
mockLogger
|
|
67
67
|
);
|
|
68
|
-
const result = validator.validatePath("/home/user/project/src/file.ts");
|
|
68
|
+
const result = await validator.validatePath("/home/user/project/src/file.ts");
|
|
69
69
|
(0, import_vitest.expect)(result.isValid).toBe(true);
|
|
70
70
|
(0, import_vitest.expect)(result.normalizedPath).toBeDefined();
|
|
71
71
|
});
|
|
72
|
-
(0, import_vitest.it)("should allow relative paths within working directory", () => {
|
|
72
|
+
(0, import_vitest.it)("should allow relative paths within working directory", async () => {
|
|
73
73
|
const validator = new import_path_validator.PathValidator(
|
|
74
74
|
{
|
|
75
75
|
allowedPaths: ["/home/user/project"],
|
|
@@ -82,10 +82,10 @@ const createMockLogger = () => ({
|
|
|
82
82
|
},
|
|
83
83
|
mockLogger
|
|
84
84
|
);
|
|
85
|
-
const result = validator.validatePath("src/file.ts");
|
|
85
|
+
const result = await validator.validatePath("src/file.ts");
|
|
86
86
|
(0, import_vitest.expect)(result.isValid).toBe(true);
|
|
87
87
|
});
|
|
88
|
-
(0, import_vitest.it)("should reject paths outside allowed directories", () => {
|
|
88
|
+
(0, import_vitest.it)("should reject paths outside allowed directories", async () => {
|
|
89
89
|
const validator = new import_path_validator.PathValidator(
|
|
90
90
|
{
|
|
91
91
|
allowedPaths: ["/home/user/project"],
|
|
@@ -98,11 +98,11 @@ const createMockLogger = () => ({
|
|
|
98
98
|
},
|
|
99
99
|
mockLogger
|
|
100
100
|
);
|
|
101
|
-
const result = validator.validatePath("/external/project/file.ts");
|
|
101
|
+
const result = await validator.validatePath("/external/project/file.ts");
|
|
102
102
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
103
103
|
(0, import_vitest.expect)(result.error).toContain("not within allowed paths");
|
|
104
104
|
});
|
|
105
|
-
(0, import_vitest.it)("should allow all paths when allowedPaths is empty", () => {
|
|
105
|
+
(0, import_vitest.it)("should allow all paths when allowedPaths is empty", async () => {
|
|
106
106
|
const validator = new import_path_validator.PathValidator(
|
|
107
107
|
{
|
|
108
108
|
allowedPaths: [],
|
|
@@ -115,12 +115,12 @@ const createMockLogger = () => ({
|
|
|
115
115
|
},
|
|
116
116
|
mockLogger
|
|
117
117
|
);
|
|
118
|
-
const result = validator.validatePath("/anywhere/file.ts");
|
|
118
|
+
const result = await validator.validatePath("/anywhere/file.ts");
|
|
119
119
|
(0, import_vitest.expect)(result.isValid).toBe(true);
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
(0, import_vitest.describe)("Path Traversal Detection", () => {
|
|
123
|
-
(0, import_vitest.it)("should reject path traversal attempts", () => {
|
|
123
|
+
(0, import_vitest.it)("should reject path traversal attempts", async () => {
|
|
124
124
|
const validator = new import_path_validator.PathValidator(
|
|
125
125
|
{
|
|
126
126
|
allowedPaths: ["/home/user/project"],
|
|
@@ -133,13 +133,15 @@ const createMockLogger = () => ({
|
|
|
133
133
|
},
|
|
134
134
|
mockLogger
|
|
135
135
|
);
|
|
136
|
-
const result = validator.validatePath(
|
|
136
|
+
const result = await validator.validatePath(
|
|
137
|
+
"/home/user/project/../../../etc/passwd"
|
|
138
|
+
);
|
|
137
139
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
138
140
|
(0, import_vitest.expect)(result.error).toBe("Path traversal detected");
|
|
139
141
|
});
|
|
140
142
|
});
|
|
141
143
|
(0, import_vitest.describe)("Blocked Paths", () => {
|
|
142
|
-
(0, import_vitest.it)("should reject paths in blocked directories", () => {
|
|
144
|
+
(0, import_vitest.it)("should reject paths in blocked directories", async () => {
|
|
143
145
|
const validator = new import_path_validator.PathValidator(
|
|
144
146
|
{
|
|
145
147
|
allowedPaths: ["/home/user/project"],
|
|
@@ -152,11 +154,11 @@ const createMockLogger = () => ({
|
|
|
152
154
|
},
|
|
153
155
|
mockLogger
|
|
154
156
|
);
|
|
155
|
-
const result = validator.validatePath("/home/user/project/.git/config");
|
|
157
|
+
const result = await validator.validatePath("/home/user/project/.git/config");
|
|
156
158
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
157
159
|
(0, import_vitest.expect)(result.error).toContain("blocked");
|
|
158
160
|
});
|
|
159
|
-
(0, import_vitest.it)("should reject paths in node_modules", () => {
|
|
161
|
+
(0, import_vitest.it)("should reject paths in node_modules", async () => {
|
|
160
162
|
const validator = new import_path_validator.PathValidator(
|
|
161
163
|
{
|
|
162
164
|
allowedPaths: ["/home/user/project"],
|
|
@@ -169,7 +171,7 @@ const createMockLogger = () => ({
|
|
|
169
171
|
},
|
|
170
172
|
mockLogger
|
|
171
173
|
);
|
|
172
|
-
const result = validator.validatePath(
|
|
174
|
+
const result = await validator.validatePath(
|
|
173
175
|
"/home/user/project/node_modules/lodash/index.js"
|
|
174
176
|
);
|
|
175
177
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
@@ -177,7 +179,7 @@ const createMockLogger = () => ({
|
|
|
177
179
|
});
|
|
178
180
|
});
|
|
179
181
|
(0, import_vitest.describe)("Blocked Extensions", () => {
|
|
180
|
-
(0, import_vitest.it)("should reject files with blocked extensions", () => {
|
|
182
|
+
(0, import_vitest.it)("should reject files with blocked extensions", async () => {
|
|
181
183
|
const validator = new import_path_validator.PathValidator(
|
|
182
184
|
{
|
|
183
185
|
allowedPaths: ["/home/user/project"],
|
|
@@ -190,11 +192,11 @@ const createMockLogger = () => ({
|
|
|
190
192
|
},
|
|
191
193
|
mockLogger
|
|
192
194
|
);
|
|
193
|
-
const result = validator.validatePath("/home/user/project/malware.exe");
|
|
195
|
+
const result = await validator.validatePath("/home/user/project/malware.exe");
|
|
194
196
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
195
197
|
(0, import_vitest.expect)(result.error).toContain(".exe is not allowed");
|
|
196
198
|
});
|
|
197
|
-
(0, import_vitest.it)("should handle extensions without leading dot", () => {
|
|
199
|
+
(0, import_vitest.it)("should handle extensions without leading dot", async () => {
|
|
198
200
|
const validator = new import_path_validator.PathValidator(
|
|
199
201
|
{
|
|
200
202
|
allowedPaths: ["/home/user/project"],
|
|
@@ -208,10 +210,10 @@ const createMockLogger = () => ({
|
|
|
208
210
|
},
|
|
209
211
|
mockLogger
|
|
210
212
|
);
|
|
211
|
-
const result = validator.validatePath("/home/user/project/file.exe");
|
|
213
|
+
const result = await validator.validatePath("/home/user/project/file.exe");
|
|
212
214
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
213
215
|
});
|
|
214
|
-
(0, import_vitest.it)("should be case-insensitive for extensions", () => {
|
|
216
|
+
(0, import_vitest.it)("should be case-insensitive for extensions", async () => {
|
|
215
217
|
const validator = new import_path_validator.PathValidator(
|
|
216
218
|
{
|
|
217
219
|
allowedPaths: ["/home/user/project"],
|
|
@@ -224,12 +226,12 @@ const createMockLogger = () => ({
|
|
|
224
226
|
},
|
|
225
227
|
mockLogger
|
|
226
228
|
);
|
|
227
|
-
const result = validator.validatePath("/home/user/project/file.EXE");
|
|
229
|
+
const result = await validator.validatePath("/home/user/project/file.EXE");
|
|
228
230
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
229
231
|
});
|
|
230
232
|
});
|
|
231
233
|
(0, import_vitest.describe)("Directory Approval Checker Integration", () => {
|
|
232
|
-
(0, import_vitest.it)("should consult approval checker for external paths", () => {
|
|
234
|
+
(0, import_vitest.it)("should consult approval checker for external paths", async () => {
|
|
233
235
|
const validator = new import_path_validator.PathValidator(
|
|
234
236
|
{
|
|
235
237
|
allowedPaths: ["/home/user/project"],
|
|
@@ -242,16 +244,16 @@ const createMockLogger = () => ({
|
|
|
242
244
|
},
|
|
243
245
|
mockLogger
|
|
244
246
|
);
|
|
245
|
-
let result = validator.validatePath("/external/project/file.ts");
|
|
247
|
+
let result = await validator.validatePath("/external/project/file.ts");
|
|
246
248
|
(0, import_vitest.expect)(result.isValid).toBe(false);
|
|
247
249
|
const approvalChecker = (filePath) => {
|
|
248
250
|
return filePath.startsWith("/external/project");
|
|
249
251
|
};
|
|
250
252
|
validator.setDirectoryApprovalChecker(approvalChecker);
|
|
251
|
-
result = validator.validatePath("/external/project/file.ts");
|
|
253
|
+
result = await validator.validatePath("/external/project/file.ts");
|
|
252
254
|
(0, import_vitest.expect)(result.isValid).toBe(true);
|
|
253
255
|
});
|
|
254
|
-
(0, import_vitest.it)("should not use approval checker for config-allowed paths", () => {
|
|
256
|
+
(0, import_vitest.it)("should not use approval checker for config-allowed paths", async () => {
|
|
255
257
|
const approvalChecker = import_vitest.vi.fn().mockReturnValue(false);
|
|
256
258
|
const validator = new import_path_validator.PathValidator(
|
|
257
259
|
{
|
|
@@ -266,14 +268,14 @@ const createMockLogger = () => ({
|
|
|
266
268
|
mockLogger
|
|
267
269
|
);
|
|
268
270
|
validator.setDirectoryApprovalChecker(approvalChecker);
|
|
269
|
-
const result = validator.validatePath("/home/user/project/src/file.ts");
|
|
271
|
+
const result = await validator.validatePath("/home/user/project/src/file.ts");
|
|
270
272
|
(0, import_vitest.expect)(result.isValid).toBe(true);
|
|
271
273
|
(0, import_vitest.expect)(approvalChecker).not.toHaveBeenCalled();
|
|
272
274
|
});
|
|
273
275
|
});
|
|
274
276
|
});
|
|
275
277
|
(0, import_vitest.describe)("isPathWithinAllowed", () => {
|
|
276
|
-
(0, import_vitest.it)("should return true for paths within config-allowed directories", () => {
|
|
278
|
+
(0, import_vitest.it)("should return true for paths within config-allowed directories", async () => {
|
|
277
279
|
const validator = new import_path_validator.PathValidator(
|
|
278
280
|
{
|
|
279
281
|
allowedPaths: ["/home/user/project"],
|
|
@@ -286,12 +288,14 @@ const createMockLogger = () => ({
|
|
|
286
288
|
},
|
|
287
289
|
mockLogger
|
|
288
290
|
);
|
|
289
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/home/user/project/src/file.ts")).toBe(
|
|
290
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/home/user/project/deep/nested/file.ts")).toBe(
|
|
291
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/home/user/project/src/file.ts")).toBe(
|
|
291
292
|
true
|
|
292
293
|
);
|
|
294
|
+
(0, import_vitest.expect)(
|
|
295
|
+
await validator.isPathWithinAllowed("/home/user/project/deep/nested/file.ts")
|
|
296
|
+
).toBe(true);
|
|
293
297
|
});
|
|
294
|
-
(0, import_vitest.it)("should return false for paths outside config-allowed directories", () => {
|
|
298
|
+
(0, import_vitest.it)("should return false for paths outside config-allowed directories", async () => {
|
|
295
299
|
const validator = new import_path_validator.PathValidator(
|
|
296
300
|
{
|
|
297
301
|
allowedPaths: ["/home/user/project"],
|
|
@@ -304,10 +308,10 @@ const createMockLogger = () => ({
|
|
|
304
308
|
},
|
|
305
309
|
mockLogger
|
|
306
310
|
);
|
|
307
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
308
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/home/user/other/file.ts")).toBe(false);
|
|
311
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
312
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/home/user/other/file.ts")).toBe(false);
|
|
309
313
|
});
|
|
310
|
-
(0, import_vitest.it)("should NOT consult approval checker (used for prompting decisions)", () => {
|
|
314
|
+
(0, import_vitest.it)("should NOT consult approval checker (used for prompting decisions)", async () => {
|
|
311
315
|
const approvalChecker = import_vitest.vi.fn().mockReturnValue(true);
|
|
312
316
|
const validator = new import_path_validator.PathValidator(
|
|
313
317
|
{
|
|
@@ -322,10 +326,10 @@ const createMockLogger = () => ({
|
|
|
322
326
|
mockLogger
|
|
323
327
|
);
|
|
324
328
|
validator.setDirectoryApprovalChecker(approvalChecker);
|
|
325
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
329
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
326
330
|
(0, import_vitest.expect)(approvalChecker).not.toHaveBeenCalled();
|
|
327
331
|
});
|
|
328
|
-
(0, import_vitest.it)("should return false for empty path", () => {
|
|
332
|
+
(0, import_vitest.it)("should return false for empty path", async () => {
|
|
329
333
|
const validator = new import_path_validator.PathValidator(
|
|
330
334
|
{
|
|
331
335
|
allowedPaths: ["/home/user/project"],
|
|
@@ -338,10 +342,10 @@ const createMockLogger = () => ({
|
|
|
338
342
|
},
|
|
339
343
|
mockLogger
|
|
340
344
|
);
|
|
341
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("")).toBe(false);
|
|
342
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed(" ")).toBe(false);
|
|
345
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("")).toBe(false);
|
|
346
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed(" ")).toBe(false);
|
|
343
347
|
});
|
|
344
|
-
(0, import_vitest.it)("should return true when allowedPaths is empty (all paths allowed)", () => {
|
|
348
|
+
(0, import_vitest.it)("should return true when allowedPaths is empty (all paths allowed)", async () => {
|
|
345
349
|
const validator = new import_path_validator.PathValidator(
|
|
346
350
|
{
|
|
347
351
|
allowedPaths: [],
|
|
@@ -354,11 +358,11 @@ const createMockLogger = () => ({
|
|
|
354
358
|
},
|
|
355
359
|
mockLogger
|
|
356
360
|
);
|
|
357
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/anywhere/file.ts")).toBe(true);
|
|
361
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/anywhere/file.ts")).toBe(true);
|
|
358
362
|
});
|
|
359
363
|
});
|
|
360
364
|
(0, import_vitest.describe)("Path Containment (Parent Directory Coverage)", () => {
|
|
361
|
-
(0, import_vitest.it)("should recognize that approving parent covers child paths", () => {
|
|
365
|
+
(0, import_vitest.it)("should recognize that approving parent covers child paths", async () => {
|
|
362
366
|
const validator = new import_path_validator.PathValidator(
|
|
363
367
|
{
|
|
364
368
|
allowedPaths: ["/external/sub"],
|
|
@@ -371,9 +375,11 @@ const createMockLogger = () => ({
|
|
|
371
375
|
},
|
|
372
376
|
mockLogger
|
|
373
377
|
);
|
|
374
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/external/sub/deep/nested/file.ts")).toBe(
|
|
378
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/external/sub/deep/nested/file.ts")).toBe(
|
|
379
|
+
true
|
|
380
|
+
);
|
|
375
381
|
});
|
|
376
|
-
(0, import_vitest.it)("should not allow sibling directories", () => {
|
|
382
|
+
(0, import_vitest.it)("should not allow sibling directories", async () => {
|
|
377
383
|
const validator = new import_path_validator.PathValidator(
|
|
378
384
|
{
|
|
379
385
|
allowedPaths: ["/external/sub"],
|
|
@@ -386,9 +392,9 @@ const createMockLogger = () => ({
|
|
|
386
392
|
},
|
|
387
393
|
mockLogger
|
|
388
394
|
);
|
|
389
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/external/other/file.ts")).toBe(false);
|
|
395
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/external/other/file.ts")).toBe(false);
|
|
390
396
|
});
|
|
391
|
-
(0, import_vitest.it)("should not allow parent directories when child is approved", () => {
|
|
397
|
+
(0, import_vitest.it)("should not allow parent directories when child is approved", async () => {
|
|
392
398
|
const validator = new import_path_validator.PathValidator(
|
|
393
399
|
{
|
|
394
400
|
allowedPaths: ["/external/sub/deep"],
|
|
@@ -401,7 +407,7 @@ const createMockLogger = () => ({
|
|
|
401
407
|
},
|
|
402
408
|
mockLogger
|
|
403
409
|
);
|
|
404
|
-
(0, import_vitest.expect)(validator.isPathWithinAllowed("/external/sub/file.ts")).toBe(false);
|
|
410
|
+
(0, import_vitest.expect)(await validator.isPathWithinAllowed("/external/sub/file.ts")).toBe(false);
|
|
405
411
|
});
|
|
406
412
|
});
|
|
407
413
|
(0, import_vitest.describe)("getAllowedPaths and getBlockedPaths", () => {
|
|
@@ -15,7 +15,7 @@ describe("PathValidator", () => {
|
|
|
15
15
|
});
|
|
16
16
|
describe("validatePath", () => {
|
|
17
17
|
describe("Empty and Invalid Paths", () => {
|
|
18
|
-
it("should reject empty path", () => {
|
|
18
|
+
it("should reject empty path", async () => {
|
|
19
19
|
const validator = new PathValidator(
|
|
20
20
|
{
|
|
21
21
|
allowedPaths: ["/home/user/project"],
|
|
@@ -28,11 +28,11 @@ describe("PathValidator", () => {
|
|
|
28
28
|
},
|
|
29
29
|
mockLogger
|
|
30
30
|
);
|
|
31
|
-
const result = validator.validatePath("");
|
|
31
|
+
const result = await validator.validatePath("");
|
|
32
32
|
expect(result.isValid).toBe(false);
|
|
33
33
|
expect(result.error).toBe("Path cannot be empty");
|
|
34
34
|
});
|
|
35
|
-
it("should reject whitespace-only path", () => {
|
|
35
|
+
it("should reject whitespace-only path", async () => {
|
|
36
36
|
const validator = new PathValidator(
|
|
37
37
|
{
|
|
38
38
|
allowedPaths: ["/home/user/project"],
|
|
@@ -45,13 +45,13 @@ describe("PathValidator", () => {
|
|
|
45
45
|
},
|
|
46
46
|
mockLogger
|
|
47
47
|
);
|
|
48
|
-
const result = validator.validatePath(" ");
|
|
48
|
+
const result = await validator.validatePath(" ");
|
|
49
49
|
expect(result.isValid).toBe(false);
|
|
50
50
|
expect(result.error).toBe("Path cannot be empty");
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
53
|
describe("Allowed Paths", () => {
|
|
54
|
-
it("should allow paths within allowed directories", () => {
|
|
54
|
+
it("should allow paths within allowed directories", async () => {
|
|
55
55
|
const validator = new PathValidator(
|
|
56
56
|
{
|
|
57
57
|
allowedPaths: ["/home/user/project"],
|
|
@@ -64,11 +64,11 @@ describe("PathValidator", () => {
|
|
|
64
64
|
},
|
|
65
65
|
mockLogger
|
|
66
66
|
);
|
|
67
|
-
const result = validator.validatePath("/home/user/project/src/file.ts");
|
|
67
|
+
const result = await validator.validatePath("/home/user/project/src/file.ts");
|
|
68
68
|
expect(result.isValid).toBe(true);
|
|
69
69
|
expect(result.normalizedPath).toBeDefined();
|
|
70
70
|
});
|
|
71
|
-
it("should allow relative paths within working directory", () => {
|
|
71
|
+
it("should allow relative paths within working directory", async () => {
|
|
72
72
|
const validator = new PathValidator(
|
|
73
73
|
{
|
|
74
74
|
allowedPaths: ["/home/user/project"],
|
|
@@ -81,10 +81,10 @@ describe("PathValidator", () => {
|
|
|
81
81
|
},
|
|
82
82
|
mockLogger
|
|
83
83
|
);
|
|
84
|
-
const result = validator.validatePath("src/file.ts");
|
|
84
|
+
const result = await validator.validatePath("src/file.ts");
|
|
85
85
|
expect(result.isValid).toBe(true);
|
|
86
86
|
});
|
|
87
|
-
it("should reject paths outside allowed directories", () => {
|
|
87
|
+
it("should reject paths outside allowed directories", async () => {
|
|
88
88
|
const validator = new PathValidator(
|
|
89
89
|
{
|
|
90
90
|
allowedPaths: ["/home/user/project"],
|
|
@@ -97,11 +97,11 @@ describe("PathValidator", () => {
|
|
|
97
97
|
},
|
|
98
98
|
mockLogger
|
|
99
99
|
);
|
|
100
|
-
const result = validator.validatePath("/external/project/file.ts");
|
|
100
|
+
const result = await validator.validatePath("/external/project/file.ts");
|
|
101
101
|
expect(result.isValid).toBe(false);
|
|
102
102
|
expect(result.error).toContain("not within allowed paths");
|
|
103
103
|
});
|
|
104
|
-
it("should allow all paths when allowedPaths is empty", () => {
|
|
104
|
+
it("should allow all paths when allowedPaths is empty", async () => {
|
|
105
105
|
const validator = new PathValidator(
|
|
106
106
|
{
|
|
107
107
|
allowedPaths: [],
|
|
@@ -114,12 +114,12 @@ describe("PathValidator", () => {
|
|
|
114
114
|
},
|
|
115
115
|
mockLogger
|
|
116
116
|
);
|
|
117
|
-
const result = validator.validatePath("/anywhere/file.ts");
|
|
117
|
+
const result = await validator.validatePath("/anywhere/file.ts");
|
|
118
118
|
expect(result.isValid).toBe(true);
|
|
119
119
|
});
|
|
120
120
|
});
|
|
121
121
|
describe("Path Traversal Detection", () => {
|
|
122
|
-
it("should reject path traversal attempts", () => {
|
|
122
|
+
it("should reject path traversal attempts", async () => {
|
|
123
123
|
const validator = new PathValidator(
|
|
124
124
|
{
|
|
125
125
|
allowedPaths: ["/home/user/project"],
|
|
@@ -132,13 +132,15 @@ describe("PathValidator", () => {
|
|
|
132
132
|
},
|
|
133
133
|
mockLogger
|
|
134
134
|
);
|
|
135
|
-
const result = validator.validatePath(
|
|
135
|
+
const result = await validator.validatePath(
|
|
136
|
+
"/home/user/project/../../../etc/passwd"
|
|
137
|
+
);
|
|
136
138
|
expect(result.isValid).toBe(false);
|
|
137
139
|
expect(result.error).toBe("Path traversal detected");
|
|
138
140
|
});
|
|
139
141
|
});
|
|
140
142
|
describe("Blocked Paths", () => {
|
|
141
|
-
it("should reject paths in blocked directories", () => {
|
|
143
|
+
it("should reject paths in blocked directories", async () => {
|
|
142
144
|
const validator = new PathValidator(
|
|
143
145
|
{
|
|
144
146
|
allowedPaths: ["/home/user/project"],
|
|
@@ -151,11 +153,11 @@ describe("PathValidator", () => {
|
|
|
151
153
|
},
|
|
152
154
|
mockLogger
|
|
153
155
|
);
|
|
154
|
-
const result = validator.validatePath("/home/user/project/.git/config");
|
|
156
|
+
const result = await validator.validatePath("/home/user/project/.git/config");
|
|
155
157
|
expect(result.isValid).toBe(false);
|
|
156
158
|
expect(result.error).toContain("blocked");
|
|
157
159
|
});
|
|
158
|
-
it("should reject paths in node_modules", () => {
|
|
160
|
+
it("should reject paths in node_modules", async () => {
|
|
159
161
|
const validator = new PathValidator(
|
|
160
162
|
{
|
|
161
163
|
allowedPaths: ["/home/user/project"],
|
|
@@ -168,7 +170,7 @@ describe("PathValidator", () => {
|
|
|
168
170
|
},
|
|
169
171
|
mockLogger
|
|
170
172
|
);
|
|
171
|
-
const result = validator.validatePath(
|
|
173
|
+
const result = await validator.validatePath(
|
|
172
174
|
"/home/user/project/node_modules/lodash/index.js"
|
|
173
175
|
);
|
|
174
176
|
expect(result.isValid).toBe(false);
|
|
@@ -176,7 +178,7 @@ describe("PathValidator", () => {
|
|
|
176
178
|
});
|
|
177
179
|
});
|
|
178
180
|
describe("Blocked Extensions", () => {
|
|
179
|
-
it("should reject files with blocked extensions", () => {
|
|
181
|
+
it("should reject files with blocked extensions", async () => {
|
|
180
182
|
const validator = new PathValidator(
|
|
181
183
|
{
|
|
182
184
|
allowedPaths: ["/home/user/project"],
|
|
@@ -189,11 +191,11 @@ describe("PathValidator", () => {
|
|
|
189
191
|
},
|
|
190
192
|
mockLogger
|
|
191
193
|
);
|
|
192
|
-
const result = validator.validatePath("/home/user/project/malware.exe");
|
|
194
|
+
const result = await validator.validatePath("/home/user/project/malware.exe");
|
|
193
195
|
expect(result.isValid).toBe(false);
|
|
194
196
|
expect(result.error).toContain(".exe is not allowed");
|
|
195
197
|
});
|
|
196
|
-
it("should handle extensions without leading dot", () => {
|
|
198
|
+
it("should handle extensions without leading dot", async () => {
|
|
197
199
|
const validator = new PathValidator(
|
|
198
200
|
{
|
|
199
201
|
allowedPaths: ["/home/user/project"],
|
|
@@ -207,10 +209,10 @@ describe("PathValidator", () => {
|
|
|
207
209
|
},
|
|
208
210
|
mockLogger
|
|
209
211
|
);
|
|
210
|
-
const result = validator.validatePath("/home/user/project/file.exe");
|
|
212
|
+
const result = await validator.validatePath("/home/user/project/file.exe");
|
|
211
213
|
expect(result.isValid).toBe(false);
|
|
212
214
|
});
|
|
213
|
-
it("should be case-insensitive for extensions", () => {
|
|
215
|
+
it("should be case-insensitive for extensions", async () => {
|
|
214
216
|
const validator = new PathValidator(
|
|
215
217
|
{
|
|
216
218
|
allowedPaths: ["/home/user/project"],
|
|
@@ -223,12 +225,12 @@ describe("PathValidator", () => {
|
|
|
223
225
|
},
|
|
224
226
|
mockLogger
|
|
225
227
|
);
|
|
226
|
-
const result = validator.validatePath("/home/user/project/file.EXE");
|
|
228
|
+
const result = await validator.validatePath("/home/user/project/file.EXE");
|
|
227
229
|
expect(result.isValid).toBe(false);
|
|
228
230
|
});
|
|
229
231
|
});
|
|
230
232
|
describe("Directory Approval Checker Integration", () => {
|
|
231
|
-
it("should consult approval checker for external paths", () => {
|
|
233
|
+
it("should consult approval checker for external paths", async () => {
|
|
232
234
|
const validator = new PathValidator(
|
|
233
235
|
{
|
|
234
236
|
allowedPaths: ["/home/user/project"],
|
|
@@ -241,16 +243,16 @@ describe("PathValidator", () => {
|
|
|
241
243
|
},
|
|
242
244
|
mockLogger
|
|
243
245
|
);
|
|
244
|
-
let result = validator.validatePath("/external/project/file.ts");
|
|
246
|
+
let result = await validator.validatePath("/external/project/file.ts");
|
|
245
247
|
expect(result.isValid).toBe(false);
|
|
246
248
|
const approvalChecker = (filePath) => {
|
|
247
249
|
return filePath.startsWith("/external/project");
|
|
248
250
|
};
|
|
249
251
|
validator.setDirectoryApprovalChecker(approvalChecker);
|
|
250
|
-
result = validator.validatePath("/external/project/file.ts");
|
|
252
|
+
result = await validator.validatePath("/external/project/file.ts");
|
|
251
253
|
expect(result.isValid).toBe(true);
|
|
252
254
|
});
|
|
253
|
-
it("should not use approval checker for config-allowed paths", () => {
|
|
255
|
+
it("should not use approval checker for config-allowed paths", async () => {
|
|
254
256
|
const approvalChecker = vi.fn().mockReturnValue(false);
|
|
255
257
|
const validator = new PathValidator(
|
|
256
258
|
{
|
|
@@ -265,14 +267,14 @@ describe("PathValidator", () => {
|
|
|
265
267
|
mockLogger
|
|
266
268
|
);
|
|
267
269
|
validator.setDirectoryApprovalChecker(approvalChecker);
|
|
268
|
-
const result = validator.validatePath("/home/user/project/src/file.ts");
|
|
270
|
+
const result = await validator.validatePath("/home/user/project/src/file.ts");
|
|
269
271
|
expect(result.isValid).toBe(true);
|
|
270
272
|
expect(approvalChecker).not.toHaveBeenCalled();
|
|
271
273
|
});
|
|
272
274
|
});
|
|
273
275
|
});
|
|
274
276
|
describe("isPathWithinAllowed", () => {
|
|
275
|
-
it("should return true for paths within config-allowed directories", () => {
|
|
277
|
+
it("should return true for paths within config-allowed directories", async () => {
|
|
276
278
|
const validator = new PathValidator(
|
|
277
279
|
{
|
|
278
280
|
allowedPaths: ["/home/user/project"],
|
|
@@ -285,12 +287,14 @@ describe("PathValidator", () => {
|
|
|
285
287
|
},
|
|
286
288
|
mockLogger
|
|
287
289
|
);
|
|
288
|
-
expect(validator.isPathWithinAllowed("/home/user/project/src/file.ts")).toBe(
|
|
289
|
-
expect(validator.isPathWithinAllowed("/home/user/project/deep/nested/file.ts")).toBe(
|
|
290
|
+
expect(await validator.isPathWithinAllowed("/home/user/project/src/file.ts")).toBe(
|
|
290
291
|
true
|
|
291
292
|
);
|
|
293
|
+
expect(
|
|
294
|
+
await validator.isPathWithinAllowed("/home/user/project/deep/nested/file.ts")
|
|
295
|
+
).toBe(true);
|
|
292
296
|
});
|
|
293
|
-
it("should return false for paths outside config-allowed directories", () => {
|
|
297
|
+
it("should return false for paths outside config-allowed directories", async () => {
|
|
294
298
|
const validator = new PathValidator(
|
|
295
299
|
{
|
|
296
300
|
allowedPaths: ["/home/user/project"],
|
|
@@ -303,10 +307,10 @@ describe("PathValidator", () => {
|
|
|
303
307
|
},
|
|
304
308
|
mockLogger
|
|
305
309
|
);
|
|
306
|
-
expect(validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
307
|
-
expect(validator.isPathWithinAllowed("/home/user/other/file.ts")).toBe(false);
|
|
310
|
+
expect(await validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
311
|
+
expect(await validator.isPathWithinAllowed("/home/user/other/file.ts")).toBe(false);
|
|
308
312
|
});
|
|
309
|
-
it("should NOT consult approval checker (used for prompting decisions)", () => {
|
|
313
|
+
it("should NOT consult approval checker (used for prompting decisions)", async () => {
|
|
310
314
|
const approvalChecker = vi.fn().mockReturnValue(true);
|
|
311
315
|
const validator = new PathValidator(
|
|
312
316
|
{
|
|
@@ -321,10 +325,10 @@ describe("PathValidator", () => {
|
|
|
321
325
|
mockLogger
|
|
322
326
|
);
|
|
323
327
|
validator.setDirectoryApprovalChecker(approvalChecker);
|
|
324
|
-
expect(validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
328
|
+
expect(await validator.isPathWithinAllowed("/external/project/file.ts")).toBe(false);
|
|
325
329
|
expect(approvalChecker).not.toHaveBeenCalled();
|
|
326
330
|
});
|
|
327
|
-
it("should return false for empty path", () => {
|
|
331
|
+
it("should return false for empty path", async () => {
|
|
328
332
|
const validator = new PathValidator(
|
|
329
333
|
{
|
|
330
334
|
allowedPaths: ["/home/user/project"],
|
|
@@ -337,10 +341,10 @@ describe("PathValidator", () => {
|
|
|
337
341
|
},
|
|
338
342
|
mockLogger
|
|
339
343
|
);
|
|
340
|
-
expect(validator.isPathWithinAllowed("")).toBe(false);
|
|
341
|
-
expect(validator.isPathWithinAllowed(" ")).toBe(false);
|
|
344
|
+
expect(await validator.isPathWithinAllowed("")).toBe(false);
|
|
345
|
+
expect(await validator.isPathWithinAllowed(" ")).toBe(false);
|
|
342
346
|
});
|
|
343
|
-
it("should return true when allowedPaths is empty (all paths allowed)", () => {
|
|
347
|
+
it("should return true when allowedPaths is empty (all paths allowed)", async () => {
|
|
344
348
|
const validator = new PathValidator(
|
|
345
349
|
{
|
|
346
350
|
allowedPaths: [],
|
|
@@ -353,11 +357,11 @@ describe("PathValidator", () => {
|
|
|
353
357
|
},
|
|
354
358
|
mockLogger
|
|
355
359
|
);
|
|
356
|
-
expect(validator.isPathWithinAllowed("/anywhere/file.ts")).toBe(true);
|
|
360
|
+
expect(await validator.isPathWithinAllowed("/anywhere/file.ts")).toBe(true);
|
|
357
361
|
});
|
|
358
362
|
});
|
|
359
363
|
describe("Path Containment (Parent Directory Coverage)", () => {
|
|
360
|
-
it("should recognize that approving parent covers child paths", () => {
|
|
364
|
+
it("should recognize that approving parent covers child paths", async () => {
|
|
361
365
|
const validator = new PathValidator(
|
|
362
366
|
{
|
|
363
367
|
allowedPaths: ["/external/sub"],
|
|
@@ -370,9 +374,11 @@ describe("PathValidator", () => {
|
|
|
370
374
|
},
|
|
371
375
|
mockLogger
|
|
372
376
|
);
|
|
373
|
-
expect(validator.isPathWithinAllowed("/external/sub/deep/nested/file.ts")).toBe(
|
|
377
|
+
expect(await validator.isPathWithinAllowed("/external/sub/deep/nested/file.ts")).toBe(
|
|
378
|
+
true
|
|
379
|
+
);
|
|
374
380
|
});
|
|
375
|
-
it("should not allow sibling directories", () => {
|
|
381
|
+
it("should not allow sibling directories", async () => {
|
|
376
382
|
const validator = new PathValidator(
|
|
377
383
|
{
|
|
378
384
|
allowedPaths: ["/external/sub"],
|
|
@@ -385,9 +391,9 @@ describe("PathValidator", () => {
|
|
|
385
391
|
},
|
|
386
392
|
mockLogger
|
|
387
393
|
);
|
|
388
|
-
expect(validator.isPathWithinAllowed("/external/other/file.ts")).toBe(false);
|
|
394
|
+
expect(await validator.isPathWithinAllowed("/external/other/file.ts")).toBe(false);
|
|
389
395
|
});
|
|
390
|
-
it("should not allow parent directories when child is approved", () => {
|
|
396
|
+
it("should not allow parent directories when child is approved", async () => {
|
|
391
397
|
const validator = new PathValidator(
|
|
392
398
|
{
|
|
393
399
|
allowedPaths: ["/external/sub/deep"],
|
|
@@ -400,7 +406,7 @@ describe("PathValidator", () => {
|
|
|
400
406
|
},
|
|
401
407
|
mockLogger
|
|
402
408
|
);
|
|
403
|
-
expect(validator.isPathWithinAllowed("/external/sub/file.ts")).toBe(false);
|
|
409
|
+
expect(await validator.isPathWithinAllowed("/external/sub/file.ts")).toBe(false);
|
|
404
410
|
});
|
|
405
411
|
});
|
|
406
412
|
describe("getAllowedPaths and getBlockedPaths", () => {
|
package/dist/read-file-tool.cjs
CHANGED
|
@@ -50,10 +50,10 @@ function createReadFileTool(options) {
|
|
|
50
50
|
* Check if this read operation needs directory access approval.
|
|
51
51
|
* Returns custom approval request if the file is outside allowed paths.
|
|
52
52
|
*/
|
|
53
|
-
getApprovalOverride: (args) => {
|
|
53
|
+
getApprovalOverride: async (args) => {
|
|
54
54
|
const { file_path } = args;
|
|
55
55
|
if (!file_path) return null;
|
|
56
|
-
const isAllowed = fileSystemService.isPathWithinConfigAllowed(file_path);
|
|
56
|
+
const isAllowed = await fileSystemService.isPathWithinConfigAllowed(file_path);
|
|
57
57
|
if (isAllowed) {
|
|
58
58
|
return null;
|
|
59
59
|
}
|