@j0hanz/filesystem-mcp 2.8.1 → 2.9.1
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/README.md +33 -27
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +10 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +4 -4
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +1 -1
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +7 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/file-uri.d.ts +12 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +35 -7
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +2 -0
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +16 -0
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +16 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +127 -32
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +15 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +119 -14
- package/dist/core/glob.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +5 -3
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-utils.d.ts +42 -6
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +56 -10
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +111 -7
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +453 -65
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +6 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +26 -3
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +9 -6
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +22 -19
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +161 -33
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +64 -15
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +1 -0
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +3 -0
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +12 -1
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +16 -1
- package/dist/core/util.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +20 -11
- package/dist/resources.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +121 -43
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +8 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +36 -9
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +50 -11
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.js +1 -1
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +14 -12
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +15 -9
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +141 -86
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +72 -26
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.js +13 -6
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +113 -48
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +50 -35
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +15 -33
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +26 -8
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +1 -0
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +5 -5
package/dist/core/fs.js
CHANGED
|
@@ -51,38 +51,23 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
53
|
import { randomUUID } from 'node:crypto';
|
|
54
|
+
import { constants, existsSync } from 'node:fs';
|
|
54
55
|
import { chmod as fsChmod, cp as fsCp, lstat as fsLstat, mkdir as fsMkdir, open as fsOpen, opendir as fsOpendir, readFile as fsReadFile, readlink as fsReadlink, rename as fsRename, rm as fsRm, rmdir as fsRmdir, stat as fsStat, unlink as fsUnlink, writeFile as fsWriteFile, } from 'node:fs/promises';
|
|
55
|
-
import {
|
|
56
|
+
import { isAbsolute, join } from 'node:path';
|
|
56
57
|
import { runExclusive, withAbort } from "./concurrency.js";
|
|
57
58
|
import { ErrorCode, formatUnknownErrorMessage, FsError, isFsError, isNodeError, isNotFoundErrno, } from "./errors.js";
|
|
58
59
|
import { detectMimeFromContent, MIME_SAMPLE_SIZE } from "./mime.js";
|
|
59
60
|
import { Logger } from "./observability.js";
|
|
60
|
-
import { normalizePath,
|
|
61
|
-
import { APPEND_CREATE_NOFOLLOW, APPEND_NOFOLLOW, assertFileStats, createTooLargeError, READ_NOFOLLOW, readFileWithStats, } from "./read.js";
|
|
61
|
+
import { isSamePath, normalizePath, respellSpellingOnlyTarget } from "./path-utils.js";
|
|
62
|
+
import { APPEND_CREATE_NOFOLLOW, APPEND_NOFOLLOW, assertFileStats, createTooLargeError, O_NOFOLLOW, READ_NOFOLLOW, readFileWithStats, } from "./read.js";
|
|
62
63
|
import { getMaxTextFileSize } from "./util.js";
|
|
64
|
+
// Directory open for countEntries. O_NOFOLLOW refuses a symlink named as the
|
|
65
|
+
// directory; used only under COUNT_THROUGH_FD, like the re-check it feeds.
|
|
66
|
+
const DIR_NOFOLLOW = constants.O_RDONLY | constants.O_DIRECTORY | O_NOFOLLOW;
|
|
67
|
+
// The descriptor re-check and the read through /proc/self/fd need Linux with procfs mounted,
|
|
68
|
+
// as the read path's re-check does; elsewhere the count reads the directory by path.
|
|
69
|
+
const COUNT_THROUGH_FD = process.platform === 'linux' && existsSync('/proc/self/fd');
|
|
63
70
|
// ─── Domain primitives ────────────────────────────────────────────────────────
|
|
64
|
-
/**
|
|
65
|
-
* validatePathForWrite + symlink resolution: a write aimed at a link must land
|
|
66
|
-
* on its revalidated target, not create a new file beside the link. ENOENT is
|
|
67
|
-
* the normal new-file case, so it falls through with the original path.
|
|
68
|
-
*/
|
|
69
|
-
async function resolveForWrite(pathGuard, filePath) {
|
|
70
|
-
let validPath = await pathGuard.validatePathForWrite(filePath);
|
|
71
|
-
try {
|
|
72
|
-
const stats = await fsLstat(validPath);
|
|
73
|
-
if (stats.isSymbolicLink()) {
|
|
74
|
-
const target = await fsReadlink(validPath);
|
|
75
|
-
const resolvedTarget = isAbsolute(target) ? target : resolve(dirname(validPath), target);
|
|
76
|
-
validPath = await pathGuard.validatePathForWrite(resolvedTarget);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
if (!isNotFoundErrno(error)) {
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return validPath;
|
|
85
|
-
}
|
|
86
71
|
const CHAR_LF = 10;
|
|
87
72
|
const LINE_COUNT_CHUNK_BYTES = 64 * 1024;
|
|
88
73
|
export class GuardedFileSystem {
|
|
@@ -95,6 +80,7 @@ export class GuardedFileSystem {
|
|
|
95
80
|
* for a symlink after validation still passes. Re-read the open descriptor's
|
|
96
81
|
* real path (Linux `/proc/self/fd`; skipped elsewhere) and require it to stay
|
|
97
82
|
* inside the roots. Run before any byte is read or written through `handle`.
|
|
83
|
+
* Resolves to the normalized real path checked, or undefined when /proc is unreadable.
|
|
98
84
|
*/
|
|
99
85
|
async #assertFdWithinRoots(handle, requestedPath) {
|
|
100
86
|
let fdPath;
|
|
@@ -102,7 +88,7 @@ export class GuardedFileSystem {
|
|
|
102
88
|
fdPath = await fsReadlink(`/proc/self/fd/${String(handle.fd)}`);
|
|
103
89
|
}
|
|
104
90
|
catch {
|
|
105
|
-
return;
|
|
91
|
+
return undefined;
|
|
106
92
|
}
|
|
107
93
|
// A replaced target reads back as "<path> (deleted)".
|
|
108
94
|
const inRoots = isAbsolute(fdPath) &&
|
|
@@ -112,6 +98,7 @@ export class GuardedFileSystem {
|
|
|
112
98
|
if (!inRoots) {
|
|
113
99
|
throw new FsError(ErrorCode.ACCESS_DENIED, 'Path changed under validation and now resolves outside the allowed roots (TOCTOU).', requestedPath);
|
|
114
100
|
}
|
|
101
|
+
return normalizePath(fdPath);
|
|
115
102
|
}
|
|
116
103
|
/** The one read open: O_NOFOLLOW, then the descriptor re-check above. */
|
|
117
104
|
async #openVerified(validPath, requestedPath, signal) {
|
|
@@ -138,6 +125,23 @@ export class GuardedFileSystem {
|
|
|
138
125
|
throw error;
|
|
139
126
|
}
|
|
140
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Waits for `pending` until `signal` fires. A result that lands after the abort goes to
|
|
130
|
+
* `release` without anyone waiting on it, so a stalled open or read never holds the caller.
|
|
131
|
+
*/
|
|
132
|
+
async #abandonOnAbort(pending, signal, release, label) {
|
|
133
|
+
try {
|
|
134
|
+
return await withAbort(pending, signal);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
void pending
|
|
138
|
+
.then(release, () => undefined)
|
|
139
|
+
.catch((releaseErr) => {
|
|
140
|
+
Logger.warn(`Failed to release ${label} after abort: ${formatUnknownErrorMessage(releaseErr)}`);
|
|
141
|
+
});
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
141
145
|
/**
|
|
142
146
|
* Serialize a read-modify-write on the resolved write target, so two writers
|
|
143
147
|
* of one file (any spelling, including via a symlinked parent) cannot both
|
|
@@ -145,7 +149,7 @@ export class GuardedFileSystem {
|
|
|
145
149
|
* do not take the lock, so `fn` may call them.
|
|
146
150
|
*/
|
|
147
151
|
async lockForWrite(filePath, fn) {
|
|
148
|
-
const key = await
|
|
152
|
+
const key = await this.pathGuard.validatePathForWrite(filePath);
|
|
149
153
|
return runExclusive(key, () => fn(key));
|
|
150
154
|
}
|
|
151
155
|
async stat(filePath, options) {
|
|
@@ -176,14 +180,19 @@ export class GuardedFileSystem {
|
|
|
176
180
|
// link would rename its target and leave the link dangling.
|
|
177
181
|
const validOld = await this.pathGuard.validatePathForDelete(oldPath);
|
|
178
182
|
let validNew = await this.pathGuard.validatePathForWrite(newPath);
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
// A new path naming the old entry itself, in another case or Unicode
|
|
184
|
+
// spelling, renames that entry. Probed without following a final link:
|
|
185
|
+
// validatePathForWrite resolved a link to its target, and renaming onto
|
|
186
|
+
// that would replace the target with the link.
|
|
187
|
+
const sameEntry = await this.pathGuard.validatePathForDelete(newPath).then((entry) => isSamePath(entry, validOld), () => false);
|
|
188
|
+
if (sameEntry || validOld === validNew) {
|
|
189
|
+
validNew = respellSpellingOnlyTarget(newPath, sameEntry ? validOld : validNew);
|
|
190
|
+
}
|
|
182
191
|
await fsRename(validOld, validNew);
|
|
183
192
|
}
|
|
184
193
|
async writeFile(filePath, content, options = {}) {
|
|
185
194
|
const { signal } = options;
|
|
186
|
-
const validPath = await
|
|
195
|
+
const validPath = await this.pathGuard.validatePathForWrite(filePath);
|
|
187
196
|
const tempSuffix = randomUUID().replace(/-/g, '').slice(0, 12);
|
|
188
197
|
const tempPath = `${validPath}.${tempSuffix}.tmp`;
|
|
189
198
|
// The rename below swaps in the temp file's inode, so the target would
|
|
@@ -242,7 +251,7 @@ export class GuardedFileSystem {
|
|
|
242
251
|
*/
|
|
243
252
|
async appendFile(filePath, content, options = {}) {
|
|
244
253
|
const { signal } = options;
|
|
245
|
-
const validPath = await
|
|
254
|
+
const validPath = await this.pathGuard.validatePathForWrite(filePath);
|
|
246
255
|
// Non-regular targets must be rejected before the open: 'a' on a FIFO
|
|
247
256
|
// (POSIX) blocks until a reader appears, and fsOpen takes no signal, so
|
|
248
257
|
// the call would hang unabortable and pin its concurrency slot. The
|
|
@@ -443,7 +452,10 @@ export class GuardedFileSystem {
|
|
|
443
452
|
return {
|
|
444
453
|
content,
|
|
445
454
|
mimeType: mimeInfo.mimeType,
|
|
455
|
+
kind: mimeInfo.kind,
|
|
446
456
|
isBinary: mimeInfo.kind !== 'text',
|
|
457
|
+
validPath,
|
|
458
|
+
modified: stats.mtime.toISOString(),
|
|
447
459
|
};
|
|
448
460
|
}
|
|
449
461
|
catch (e_3) {
|
|
@@ -480,6 +492,89 @@ export class GuardedFileSystem {
|
|
|
480
492
|
});
|
|
481
493
|
}
|
|
482
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Direct children of a directory, leaving out entries the guard treats as
|
|
497
|
+
* sensitive so the count never reveals what listings hide. Hidden and
|
|
498
|
+
* ignored entries count: this answers "is it empty?", not "what would list show?".
|
|
499
|
+
* A symlinked child counts only when list would show it. The path validation, open, opendir,
|
|
500
|
+
* each read and each link check race the signal, so a stalled mount ends the call at its
|
|
501
|
+
* deadline.
|
|
502
|
+
*/
|
|
503
|
+
async countEntries(dirPath, options) {
|
|
504
|
+
const signal = options?.signal;
|
|
505
|
+
// Refuse an already-cancelled call before any validation or I/O.
|
|
506
|
+
signal?.throwIfAborted();
|
|
507
|
+
// A late validation result is a plain object, so a stalled realpath needs no release.
|
|
508
|
+
const { resolvedPath } = await withAbort(this.pathGuard.validateExistingPathDetailed(dirPath), signal);
|
|
509
|
+
// With procfs (COUNT_THROUGH_FD), open the directory itself with O_NOFOLLOW and check where
|
|
510
|
+
// the open descriptor really is, so a directory or ancestor swapped for a symlink after
|
|
511
|
+
// validation is refused. Then read entries through the descriptor, which a later swap cannot
|
|
512
|
+
// redirect.
|
|
513
|
+
const handle = COUNT_THROUGH_FD
|
|
514
|
+
? await this.#abandonOnAbort(fsOpen(resolvedPath, DIR_NOFOLLOW), signal, (late) => late.close(), `directory descriptor for ${resolvedPath}`)
|
|
515
|
+
: undefined;
|
|
516
|
+
let dir;
|
|
517
|
+
let pendingRead;
|
|
518
|
+
try {
|
|
519
|
+
const openedPath = handle ? await this.#assertFdWithinRoots(handle, dirPath) : undefined;
|
|
520
|
+
// An in-root swap passes the roots check, but the children would be filtered under the
|
|
521
|
+
// validated directory's spelling, not the one actually opened.
|
|
522
|
+
if (openedPath !== undefined && !isSamePath(openedPath, resolvedPath)) {
|
|
523
|
+
throw new FsError(ErrorCode.ACCESS_DENIED, 'Path changed under validation and now resolves to another directory (TOCTOU).', dirPath);
|
|
524
|
+
}
|
|
525
|
+
const readPath = handle ? `/proc/self/fd/${String(handle.fd)}` : resolvedPath;
|
|
526
|
+
dir = await this.#abandonOnAbort(fsOpendir(readPath), signal, (late) => late.close(), `directory stream for ${resolvedPath}`);
|
|
527
|
+
let count = 0;
|
|
528
|
+
for (;;) {
|
|
529
|
+
// Each read races the signal, so a read that stalls ends the call at its deadline.
|
|
530
|
+
const read = dir.read();
|
|
531
|
+
pendingRead = read;
|
|
532
|
+
const entry = await withAbort(read, signal);
|
|
533
|
+
pendingRead = undefined;
|
|
534
|
+
if (entry === null)
|
|
535
|
+
break;
|
|
536
|
+
if (this.pathGuard.isSensitive(join(dirPath, entry.name)))
|
|
537
|
+
continue;
|
|
538
|
+
// list walks the real directory, so it checks every child, links
|
|
539
|
+
// included, under its real spelling: a deny rule naming the real
|
|
540
|
+
// directory hides the child under an alias too.
|
|
541
|
+
if (this.pathGuard.isSensitive(join(resolvedPath, entry.name)))
|
|
542
|
+
continue;
|
|
543
|
+
// A link counts only when list would show it: same rule, same error policy.
|
|
544
|
+
if (entry.isSymbolicLink() &&
|
|
545
|
+
!(await withAbort(this.pathGuard.isEntryAccessible(join(resolvedPath, entry.name)), signal))) {
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
count += 1;
|
|
549
|
+
}
|
|
550
|
+
// A deadline that fired during the last read still ends the call, even with no entries left.
|
|
551
|
+
signal?.throwIfAborted();
|
|
552
|
+
return count;
|
|
553
|
+
}
|
|
554
|
+
finally {
|
|
555
|
+
if (dir) {
|
|
556
|
+
const stream = dir;
|
|
557
|
+
if (pendingRead) {
|
|
558
|
+
// close() queues behind a read still in flight, so awaiting it would hold the TIMEOUT
|
|
559
|
+
// response on a stalled mount. Close once that read settles, without waiting for it.
|
|
560
|
+
void pendingRead
|
|
561
|
+
.then(() => stream.close(), () => stream.close())
|
|
562
|
+
.catch((closeErr) => {
|
|
563
|
+
Logger.warn(`Failed to close dir handle for ${resolvedPath} after abort: ${formatUnknownErrorMessage(closeErr)}`);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
await stream.close().catch((closeErr) => {
|
|
568
|
+
Logger.warn(`Failed to close dir handle for ${resolvedPath}: ${formatUnknownErrorMessage(closeErr)}`);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
// A failed close must not replace the error the count threw, so it is logged, not thrown.
|
|
573
|
+
await handle?.close().catch((closeErr) => {
|
|
574
|
+
Logger.warn(`Failed to close dir descriptor for ${resolvedPath}: ${formatUnknownErrorMessage(closeErr)}`);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
483
578
|
}
|
|
484
579
|
/**
|
|
485
580
|
* Returns whether `path` exists, treating "not found" as absent and logging any
|
package/dist/core/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/core/fs.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport type { Stats } from 'node:fs';\nimport type { FileHandle } from 'node:fs/promises';\nimport {\n chmod as fsChmod,\n cp as fsCp,\n lstat as fsLstat,\n mkdir as fsMkdir,\n open as fsOpen,\n opendir as fsOpendir,\n readFile as fsReadFile,\n readlink as fsReadlink,\n rename as fsRename,\n rm as fsRm,\n rmdir as fsRmdir,\n stat as fsStat,\n unlink as fsUnlink,\n writeFile as fsWriteFile,\n} from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\n\nimport { runExclusive, withAbort } from './concurrency.ts';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n isFsError,\n isNodeError,\n isNotFoundErrno,\n} from './errors.ts';\nimport { detectMimeFromContent, MIME_SAMPLE_SIZE } from './mime.ts';\nimport { Logger } from './observability.ts';\nimport type { EntryType as FileType } from './path-utils.ts';\nimport { normalizePath, respellCaseOnlyTarget } from './path-utils.ts';\nimport type { PathGuard } from './path.ts';\nimport type { ReadFileResult, ReadSpec } from './read.ts';\nimport {\n APPEND_CREATE_NOFOLLOW,\n APPEND_NOFOLLOW,\n assertFileStats,\n createTooLargeError,\n READ_NOFOLLOW,\n readFileWithStats,\n} from './read.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\nexport type { FileType };\nexport type { Stats };\n\n// ─── Domain primitives ────────────────────────────────────────────────────────\n\n/**\n * validatePathForWrite + symlink resolution: a write aimed at a link must land\n * on its revalidated target, not create a new file beside the link. ENOENT is\n * the normal new-file case, so it falls through with the original path.\n */\nasync function resolveForWrite(pathGuard: PathGuard, filePath: string): Promise<string> {\n let validPath = await pathGuard.validatePathForWrite(filePath);\n\n try {\n const stats = await fsLstat(validPath);\n if (stats.isSymbolicLink()) {\n const target = await fsReadlink(validPath);\n const resolvedTarget = isAbsolute(target) ? target : resolve(dirname(validPath), target);\n validPath = await pathGuard.validatePathForWrite(resolvedTarget);\n }\n } catch (error) {\n if (!isNotFoundErrno(error)) {\n throw error;\n }\n }\n\n return validPath;\n}\n\nconst CHAR_LF = 10;\n\nconst LINE_COUNT_CHUNK_BYTES = 64 * 1024;\n\nexport class GuardedFileSystem {\n readonly pathGuard: PathGuard;\n\n constructor(pathGuard: PathGuard) {\n this.pathGuard = pathGuard;\n }\n\n /**\n * O_NOFOLLOW only guards the final component; an ANCESTOR directory swapped\n * for a symlink after validation still passes. Re-read the open descriptor's\n * real path (Linux `/proc/self/fd`; skipped elsewhere) and require it to stay\n * inside the roots. Run before any byte is read or written through `handle`.\n */\n async #assertFdWithinRoots(handle: FileHandle, requestedPath: string): Promise<void> {\n let fdPath: string;\n try {\n fdPath = await fsReadlink(`/proc/self/fd/${String(handle.fd)}`);\n } catch {\n return;\n }\n // A replaced target reads back as \"<path> (deleted)\".\n const inRoots =\n isAbsolute(fdPath) &&\n !fdPath.endsWith(' (deleted)') &&\n this.pathGuard.allowedRootContaining(normalizePath(fdPath)) !== undefined &&\n !this.pathGuard.isSensitive(normalizePath(fdPath));\n if (!inRoots) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Path changed under validation and now resolves outside the allowed roots (TOCTOU).',\n requestedPath,\n );\n }\n }\n\n /** The one read open: O_NOFOLLOW, then the descriptor re-check above. */\n async #openVerified(\n validPath: string,\n requestedPath: string,\n signal?: AbortSignal,\n ): Promise<FileHandle> {\n const opening = fsOpen(validPath, READ_NOFOLLOW);\n let handle: FileHandle;\n try {\n handle = await withAbort(opening, signal);\n } catch (error) {\n // An open the signal abandoned still lands; close it then.\n void opening\n .then(\n (late) => late.close(),\n () => undefined,\n )\n .catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close file handle for ${validPath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n throw error;\n }\n try {\n await this.#assertFdWithinRoots(handle, requestedPath);\n return handle;\n } catch (error) {\n await handle.close();\n throw error;\n }\n }\n\n /**\n * Serialize a read-modify-write on the resolved write target, so two writers\n * of one file (any spelling, including via a symlinked parent) cannot both\n * read the same base and have the last rename win. `writeFile`/`appendFile`\n * do not take the lock, so `fn` may call them.\n */\n async lockForWrite<T>(filePath: string, fn: (resolvedTarget: string) => Promise<T>): Promise<T> {\n const key = await resolveForWrite(this.pathGuard, filePath);\n return runExclusive(key, () => fn(key));\n }\n\n async stat(\n filePath: string,\n options?: { signal?: AbortSignal | undefined },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async lstat(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const stats = await withAbort(fsLstat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async readlink(\n filePath: string,\n options?: Parameters<typeof fsReadlink>[1],\n ): Promise<{ linkString: string; validPath: string }> {\n // validateExistingPath resolves through the symlink, so readlink would always\n // be handed the final target — a regular file — and fail EINVAL. This guard\n // keeps the link itself while still enforcing containment and the denylist.\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const raw = await fsReadlink(validPath, options);\n const linkString = Buffer.isBuffer(raw) ? raw.toString('utf-8') : raw;\n return { linkString, validPath };\n }\n\n async mkdir(filePath: string, options?: Parameters<typeof fsMkdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n await fsMkdir(validPath, options);\n }\n\n async rename(oldPath: string, newPath: string): Promise<void> {\n // Not validateExistingPath: that resolves through a symlink, so renaming a\n // link would rename its target and leave the link dangling.\n const validOld = await this.pathGuard.validatePathForDelete(oldPath);\n let validNew = await this.pathGuard.validatePathForWrite(newPath);\n // Renaming a path onto itself is only meaningful as a case-only rename.\n if (validOld === validNew) validNew = respellCaseOnlyTarget(newPath, validNew);\n await fsRename(validOld, validNew);\n }\n\n async writeFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await resolveForWrite(this.pathGuard, filePath);\n\n const tempSuffix = randomUUID().replace(/-/g, '').slice(0, 12);\n const tempPath = `${validPath}.${tempSuffix}.tmp`;\n\n // The rename below swaps in the temp file's inode, so the target would\n // inherit fsWriteFile's default 0o666 & ~umask — silently widening a 0600\n // file to 0644 on every write. Carry the existing mode across instead.\n let existingMode: number | undefined;\n try {\n existingMode = (await fsStat(validPath)).mode & 0o777;\n } catch (error) {\n // ENOENT is the normal new-file case: the default mode is correct there.\n // Anything else (EACCES, EIO) means the mode about to be overwritten could\n // not be read, and the write will silently widen the file — say so rather\n // than swallowing it.\n if (!isNotFoundErrno(error)) {\n Logger.warn(\n `atomicWriteFile: cannot read the existing mode of ${validPath}; the write will use the default mode: ${formatUnknownErrorMessage(error)}`,\n );\n }\n }\n\n try {\n signal?.throwIfAborted();\n // Create the temp file already narrowed to the target's mode: a 0600\n // file's new bytes must never sit at 0644 while the write is in flight.\n // `wx` fails on a name collision instead of overwriting; the chmod below\n // still restores bits the umask masked off at creation.\n await fsWriteFile(tempPath, content, {\n signal,\n flag: 'wx',\n ...(existingMode !== undefined ? { mode: existingMode } : {}),\n });\n if (existingMode !== undefined) {\n await fsChmod(tempPath, existingMode);\n }\n // Last cancellation point. The rename IS the commit: once it starts the\n // target may already be replaced, so it is never raced against the\n // signal — a withAbort race would report a finished write as failed, and\n // a client retry would apply it twice (same reasoning as appendFile).\n signal?.throwIfAborted();\n await fsRename(tempPath, validPath);\n } catch (error) {\n try {\n await fsUnlink(tempPath);\n } catch (cleanupError) {\n Logger.warn(\n `Failed to clean up temp file ${tempPath} after write error (${formatUnknownErrorMessage(error)}): ${formatUnknownErrorMessage(cleanupError)}`,\n );\n }\n throw error;\n }\n return { validPath };\n }\n\n /**\n * Append to the existing file (created if missing). Deliberately NOT the\n * writeFile temp+rename dance: the rename would swap in a fresh inode,\n * and the whole point of appending is to extend the file that's already\n * there — inode and mode survive by construction.\n */\n async appendFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await resolveForWrite(this.pathGuard, filePath);\n // Non-regular targets must be rejected before the open: 'a' on a FIFO\n // (POSIX) blocks until a reader appears, and fsOpen takes no signal, so\n // the call would hang unabortable and pin its concurrency slot. The\n // overwrite path can't hang — it writes a temp file and renames over.\n // resolveForWrite has already resolved any symlink to its target, so the\n // lstat here doubles as the new-vs-existing probe for the cleanup below.\n let existed = true;\n try {\n assertFileStats(validPath, await fsLstat(validPath));\n } catch (error) {\n if (!isNotFoundErrno(error)) throw error;\n existed = false;\n }\n // fs.promises.appendFile takes no signal, so a withAbort race would\n // report failure while the append still lands — a client retry then\n // appends twice. A handle opened with 'a' gets a genuinely abortable\n // FileHandle.writeFile instead, same as the writeFile path. 'a' on a\n // missing target CREATES it, so: check the signal before the open, and\n // if the call then fails after creating the file, remove what it made —\n // an aborted or errored call must not leave a phantom file behind.\n // The pre-open lstat only proves the file was missing a moment ago:\n // 'ax' makes creation exclusive, so a racer that created it in between\n // fails EEXIST, reopens plainly, and never claims cleanup rights over\n // the winner's file.\n signal?.throwIfAborted();\n let created = false;\n let handle: FileHandle;\n if (existed) {\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n } else {\n try {\n handle = await fsOpen(validPath, APPEND_CREATE_NOFOLLOW);\n created = true;\n } catch (error) {\n if (!isNodeError(error) || error.code !== 'EEXIST') throw error;\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n }\n }\n try {\n signal?.throwIfAborted();\n // A file we just O_EXCL-created is a fresh regular file; only an existing one\n // can have been reached through a swapped ancestor.\n if (!created) await this.#assertFdWithinRoots(handle, filePath);\n // Ceiling: an append is not transactional, so an abort landing\n // mid-write can leave a prefix of the content on disk. Unwiring the\n // signal would trade that for a silently-completed write; a client\n // that retries is double-appending either way, so the abortable\n // write stays.\n await handle.writeFile(content, { signal });\n } catch (error) {\n if (created) {\n await fsUnlink(validPath).catch((unlinkError: unknown) => {\n Logger.warn(\n `Failed to remove newly created ${validPath} after a failed append: ${formatUnknownErrorMessage(unlinkError)}`,\n );\n });\n }\n throw error;\n } finally {\n await handle.close();\n }\n return { validPath };\n }\n\n /**\n * Leading bytes of a file, for MIME sniffing without loading the whole\n * file — an append's result must describe the resulting file (which may be\n * a multi-GB log), not the chunk that was appended.\n */\n async readLeadingSample(filePath: string): Promise<Buffer> {\n await using handle = await this.open(filePath);\n const buffer = Buffer.alloc(MIME_SAMPLE_SIZE);\n const { bytesRead } = await handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0);\n return buffer.subarray(0, bytesRead);\n }\n\n /**\n * Line count for a file on disk, counting newline bytes in fixed 64 KiB\n * chunks so an arbitrarily large file (one append's result metadata) costs\n * bounded memory. NOT FileHandle.readLines: it decodes each physical line\n * whole, so a multi-GB single-line log OOMs the server after the append has\n * already landed — a reported failure invites a retry that appends twice.\n * Matches read.ts's countLines semantics: a trailing newline does not open\n * a new line, an unterminated final line counts, an empty file is 0.\n */\n async countFileLines(filePath: string): Promise<number> {\n await using handle = await this.open(filePath);\n let newlines = 0;\n let lastByte = 0;\n let totalBytes = 0;\n const chunk = Buffer.alloc(LINE_COUNT_CHUNK_BYTES);\n for (;;) {\n const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);\n if (bytesRead === 0) break;\n totalBytes += bytesRead;\n for (let i = 0; i < bytesRead; i++) {\n const byte = chunk[i] ?? 0;\n if (byte === CHAR_LF) newlines++;\n lastByte = byte;\n }\n }\n // totalBytes is the empty-file sentinel: a NUL (0x00) final byte is real\n // content and must count as an unterminated final line, not \"empty\".\n return totalBytes === 0 ? 0 : lastByte === CHAR_LF ? newlines : newlines + 1;\n }\n\n async rm(filePath: string, options?: Parameters<typeof fsRm>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRm(validPath, options);\n }\n\n async rmdir(filePath: string, options?: Parameters<typeof fsRmdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRmdir(validPath, options);\n }\n\n async cp(\n source: string,\n destination: string,\n options?: Parameters<typeof fsCp>[2],\n ): Promise<void> {\n // As in rename: keep the link itself so callers passing verbatimSymlinks\n // actually copy the link rather than a dereferenced target.\n const validSource = await this.pathGuard.validatePathForDelete(source);\n const validDest = await this.pathGuard.validatePathForWrite(destination);\n await fsCp(validSource, validDest, options);\n }\n\n async readFile(filePath: string, spec: ReadSpec): Promise<ReadFileResult> {\n spec.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), spec.signal);\n return readFileWithStats(filePath, validPath, stats, spec, () =>\n this.#openVerified(validPath, filePath, spec.signal),\n );\n }\n\n async readEditableText(\n filePath: string,\n options?: { signal?: AbortSignal; tool?: string },\n ): Promise<{ validPath: string; content: string; stats: Stats }> {\n const { stats, validPath } = await this.stat(filePath, options);\n const maxSize = getMaxTextFileSize();\n if (stats.size > maxSize) {\n // `tool` labels the TOO_LARGE message so the client sees which tool refused.\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large for ${options?.tool ?? 'edit'} (${stats.size} bytes > ${maxSize} bytes)`,\n filePath,\n { size: stats.size, maxFileSize: maxSize },\n );\n }\n const { content } = await readFileWithStats(\n filePath,\n validPath,\n stats,\n {\n kind: 'full',\n strictUtf8: true,\n ...(options?.signal ? { signal: options.signal } : {}),\n },\n () => this.#openVerified(validPath, filePath, options?.signal),\n );\n return { validPath, content, stats };\n }\n\n async readRaw(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ content: Buffer; mimeType: string; isBinary: boolean }> {\n options?.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n options?.signal?.throwIfAborted();\n const stats = await withAbort(fsStat(validPath), options?.signal);\n assertFileStats(filePath, stats);\n // Enforce size limit before reading to avoid loading large files into memory.\n // Binary detection is best-effort, but this is a hard limit.\n const maxTextFileSize = getMaxTextFileSize();\n if (stats.size > maxTextFileSize) {\n throw createTooLargeError(stats.size, maxTextFileSize, filePath);\n }\n let content: Buffer;\n await using handle = await this.#openVerified(validPath, filePath);\n try {\n content = await fsReadFile(handle, options);\n } catch (error) {\n // Node wraps even a TimeoutError signal reason in AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n options?.signal?.throwIfAborted();\n }\n throw error;\n }\n const mimeInfo = detectMimeFromContent(validPath, content);\n return {\n content,\n mimeType: mimeInfo.mimeType,\n isBinary: mimeInfo.kind !== 'text',\n };\n }\n\n async open(filePath: string): Promise<FileHandle> {\n return this.#openVerified(await this.pathGuard.validateExistingPath(filePath), filePath);\n }\n\n // Single resolution + stat: validateExistingPathDetailed resolves the real\n // path (following symlinks, re-checking sensitivity) once, then we stat the\n // resolved target. Replaces the tool-side validateExistingPathDetailed +\n // fs.stat pair that re-validated through the free stat() guard.\n async statDetailed(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ requestedPath: string; isSymlink: boolean; stats: Stats }> {\n const { requestedPath, resolvedPath, isSymlink } =\n await this.pathGuard.validateExistingPathDetailed(filePath);\n const stats = await withAbort(fsStat(resolvedPath), options?.signal);\n return { requestedPath, isSymlink, stats };\n }\n\n async hasChildrenUnchecked(dirPath: string): Promise<boolean> {\n const dir = await fsOpendir(dirPath);\n try {\n const entry = await dir.read();\n return entry !== null;\n } finally {\n await dir.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${dirPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n}\n\n/**\n * Returns whether `path` exists, treating \"not found\" as absent and logging any\n * other stat failure. Used for TOCTOU re-checks between plan and execute phases.\n */\nexport async function destExists(\n fs: Pick<GuardedFileSystem, 'stat'>,\n path: string,\n label: string,\n): Promise<boolean> {\n try {\n await fs.stat(path);\n return true;\n } catch (err) {\n const missing = isNotFoundErrno(err) || (isFsError(err) && err.code === ErrorCode.NOT_FOUND);\n if (!missing) {\n Logger.warn(`${label}: dest stat failed unexpectedly for \"${path}\": ${String(err)}`);\n }\n return false;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/core/fs.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { constants, existsSync } from 'node:fs';\nimport type { Dir, Dirent, Stats } from 'node:fs';\nimport type { FileHandle } from 'node:fs/promises';\nimport {\n chmod as fsChmod,\n cp as fsCp,\n lstat as fsLstat,\n mkdir as fsMkdir,\n open as fsOpen,\n opendir as fsOpendir,\n readFile as fsReadFile,\n readlink as fsReadlink,\n rename as fsRename,\n rm as fsRm,\n rmdir as fsRmdir,\n stat as fsStat,\n unlink as fsUnlink,\n writeFile as fsWriteFile,\n} from 'node:fs/promises';\nimport { isAbsolute, join } from 'node:path';\n\nimport { runExclusive, withAbort } from './concurrency.ts';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n isFsError,\n isNodeError,\n isNotFoundErrno,\n} from './errors.ts';\nimport { detectMimeFromContent, type FileKind, MIME_SAMPLE_SIZE } from './mime.ts';\nimport { Logger } from './observability.ts';\nimport type { EntryType as FileType } from './path-utils.ts';\nimport { isSamePath, normalizePath, respellSpellingOnlyTarget } from './path-utils.ts';\nimport type { PathGuard } from './path.ts';\nimport type { ReadFileResult, ReadSpec } from './read.ts';\nimport {\n APPEND_CREATE_NOFOLLOW,\n APPEND_NOFOLLOW,\n assertFileStats,\n createTooLargeError,\n O_NOFOLLOW,\n READ_NOFOLLOW,\n readFileWithStats,\n} from './read.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\nexport type { FileType };\nexport type { Stats };\n\n// Directory open for countEntries. O_NOFOLLOW refuses a symlink named as the\n// directory; used only under COUNT_THROUGH_FD, like the re-check it feeds.\nconst DIR_NOFOLLOW = constants.O_RDONLY | constants.O_DIRECTORY | O_NOFOLLOW;\n// The descriptor re-check and the read through /proc/self/fd need Linux with procfs mounted,\n// as the read path's re-check does; elsewhere the count reads the directory by path.\nconst COUNT_THROUGH_FD = process.platform === 'linux' && existsSync('/proc/self/fd');\n\n// ─── Domain primitives ────────────────────────────────────────────────────────\n\nconst CHAR_LF = 10;\n\nconst LINE_COUNT_CHUNK_BYTES = 64 * 1024;\n\nexport class GuardedFileSystem {\n readonly pathGuard: PathGuard;\n\n constructor(pathGuard: PathGuard) {\n this.pathGuard = pathGuard;\n }\n\n /**\n * O_NOFOLLOW only guards the final component; an ANCESTOR directory swapped\n * for a symlink after validation still passes. Re-read the open descriptor's\n * real path (Linux `/proc/self/fd`; skipped elsewhere) and require it to stay\n * inside the roots. Run before any byte is read or written through `handle`.\n * Resolves to the normalized real path checked, or undefined when /proc is unreadable.\n */\n async #assertFdWithinRoots(\n handle: FileHandle,\n requestedPath: string,\n ): Promise<string | undefined> {\n let fdPath: string;\n try {\n fdPath = await fsReadlink(`/proc/self/fd/${String(handle.fd)}`);\n } catch {\n return undefined;\n }\n // A replaced target reads back as \"<path> (deleted)\".\n const inRoots =\n isAbsolute(fdPath) &&\n !fdPath.endsWith(' (deleted)') &&\n this.pathGuard.allowedRootContaining(normalizePath(fdPath)) !== undefined &&\n !this.pathGuard.isSensitive(normalizePath(fdPath));\n if (!inRoots) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Path changed under validation and now resolves outside the allowed roots (TOCTOU).',\n requestedPath,\n );\n }\n return normalizePath(fdPath);\n }\n\n /** The one read open: O_NOFOLLOW, then the descriptor re-check above. */\n async #openVerified(\n validPath: string,\n requestedPath: string,\n signal?: AbortSignal,\n ): Promise<FileHandle> {\n const opening = fsOpen(validPath, READ_NOFOLLOW);\n let handle: FileHandle;\n try {\n handle = await withAbort(opening, signal);\n } catch (error) {\n // An open the signal abandoned still lands; close it then.\n void opening\n .then(\n (late) => late.close(),\n () => undefined,\n )\n .catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close file handle for ${validPath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n throw error;\n }\n try {\n await this.#assertFdWithinRoots(handle, requestedPath);\n return handle;\n } catch (error) {\n await handle.close();\n throw error;\n }\n }\n\n /**\n * Waits for `pending` until `signal` fires. A result that lands after the abort goes to\n * `release` without anyone waiting on it, so a stalled open or read never holds the caller.\n */\n async #abandonOnAbort<T>(\n pending: Promise<T>,\n signal: AbortSignal | undefined,\n release: (late: T) => Promise<unknown>,\n label: string,\n ): Promise<T> {\n try {\n return await withAbort(pending, signal);\n } catch (error) {\n void pending\n .then(release, () => undefined)\n .catch((releaseErr: unknown) => {\n Logger.warn(\n `Failed to release ${label} after abort: ${formatUnknownErrorMessage(releaseErr)}`,\n );\n });\n throw error;\n }\n }\n\n /**\n * Serialize a read-modify-write on the resolved write target, so two writers\n * of one file (any spelling, including via a symlinked parent) cannot both\n * read the same base and have the last rename win. `writeFile`/`appendFile`\n * do not take the lock, so `fn` may call them.\n */\n async lockForWrite<T>(filePath: string, fn: (resolvedTarget: string) => Promise<T>): Promise<T> {\n const key = await this.pathGuard.validatePathForWrite(filePath);\n return runExclusive(key, () => fn(key));\n }\n\n async stat(\n filePath: string,\n options?: { signal?: AbortSignal | undefined },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async lstat(\n filePath: string,\n options?: { signal?: AbortSignal | undefined },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const stats = await withAbort(fsLstat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async readlink(\n filePath: string,\n options?: Parameters<typeof fsReadlink>[1],\n ): Promise<{ linkString: string; validPath: string }> {\n // validateExistingPath resolves through the symlink, so readlink would always\n // be handed the final target — a regular file — and fail EINVAL. This guard\n // keeps the link itself while still enforcing containment and the denylist.\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const raw = await fsReadlink(validPath, options);\n const linkString = Buffer.isBuffer(raw) ? raw.toString('utf-8') : raw;\n return { linkString, validPath };\n }\n\n async mkdir(filePath: string, options?: Parameters<typeof fsMkdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n await fsMkdir(validPath, options);\n }\n\n async rename(oldPath: string, newPath: string): Promise<void> {\n // Not validateExistingPath: that resolves through a symlink, so renaming a\n // link would rename its target and leave the link dangling.\n const validOld = await this.pathGuard.validatePathForDelete(oldPath);\n let validNew = await this.pathGuard.validatePathForWrite(newPath);\n // A new path naming the old entry itself, in another case or Unicode\n // spelling, renames that entry. Probed without following a final link:\n // validatePathForWrite resolved a link to its target, and renaming onto\n // that would replace the target with the link.\n const sameEntry = await this.pathGuard.validatePathForDelete(newPath).then(\n (entry) => isSamePath(entry, validOld),\n () => false,\n );\n if (sameEntry || validOld === validNew) {\n validNew = respellSpellingOnlyTarget(newPath, sameEntry ? validOld : validNew);\n }\n await fsRename(validOld, validNew);\n }\n\n async writeFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n\n const tempSuffix = randomUUID().replace(/-/g, '').slice(0, 12);\n const tempPath = `${validPath}.${tempSuffix}.tmp`;\n\n // The rename below swaps in the temp file's inode, so the target would\n // inherit fsWriteFile's default 0o666 & ~umask — silently widening a 0600\n // file to 0644 on every write. Carry the existing mode across instead.\n let existingMode: number | undefined;\n try {\n existingMode = (await fsStat(validPath)).mode & 0o777;\n } catch (error) {\n // ENOENT is the normal new-file case: the default mode is correct there.\n // Anything else (EACCES, EIO) means the mode about to be overwritten could\n // not be read, and the write will silently widen the file — say so rather\n // than swallowing it.\n if (!isNotFoundErrno(error)) {\n Logger.warn(\n `atomicWriteFile: cannot read the existing mode of ${validPath}; the write will use the default mode: ${formatUnknownErrorMessage(error)}`,\n );\n }\n }\n\n try {\n signal?.throwIfAborted();\n // Create the temp file already narrowed to the target's mode: a 0600\n // file's new bytes must never sit at 0644 while the write is in flight.\n // `wx` fails on a name collision instead of overwriting; the chmod below\n // still restores bits the umask masked off at creation.\n await fsWriteFile(tempPath, content, {\n signal,\n flag: 'wx',\n ...(existingMode !== undefined ? { mode: existingMode } : {}),\n });\n if (existingMode !== undefined) {\n await fsChmod(tempPath, existingMode);\n }\n // Last cancellation point. The rename IS the commit: once it starts the\n // target may already be replaced, so it is never raced against the\n // signal — a withAbort race would report a finished write as failed, and\n // a client retry would apply it twice (same reasoning as appendFile).\n signal?.throwIfAborted();\n await fsRename(tempPath, validPath);\n } catch (error) {\n try {\n await fsUnlink(tempPath);\n } catch (cleanupError) {\n Logger.warn(\n `Failed to clean up temp file ${tempPath} after write error (${formatUnknownErrorMessage(error)}): ${formatUnknownErrorMessage(cleanupError)}`,\n );\n }\n throw error;\n }\n return { validPath };\n }\n\n /**\n * Append to the existing file (created if missing). Deliberately NOT the\n * writeFile temp+rename dance: the rename would swap in a fresh inode,\n * and the whole point of appending is to extend the file that's already\n * there — inode and mode survive by construction.\n */\n async appendFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n // Non-regular targets must be rejected before the open: 'a' on a FIFO\n // (POSIX) blocks until a reader appears, and fsOpen takes no signal, so\n // the call would hang unabortable and pin its concurrency slot. The\n // overwrite path can't hang — it writes a temp file and renames over.\n // resolveForWrite has already resolved any symlink to its target, so the\n // lstat here doubles as the new-vs-existing probe for the cleanup below.\n let existed = true;\n try {\n assertFileStats(validPath, await fsLstat(validPath));\n } catch (error) {\n if (!isNotFoundErrno(error)) throw error;\n existed = false;\n }\n // fs.promises.appendFile takes no signal, so a withAbort race would\n // report failure while the append still lands — a client retry then\n // appends twice. A handle opened with 'a' gets a genuinely abortable\n // FileHandle.writeFile instead, same as the writeFile path. 'a' on a\n // missing target CREATES it, so: check the signal before the open, and\n // if the call then fails after creating the file, remove what it made —\n // an aborted or errored call must not leave a phantom file behind.\n // The pre-open lstat only proves the file was missing a moment ago:\n // 'ax' makes creation exclusive, so a racer that created it in between\n // fails EEXIST, reopens plainly, and never claims cleanup rights over\n // the winner's file.\n signal?.throwIfAborted();\n let created = false;\n let handle: FileHandle;\n if (existed) {\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n } else {\n try {\n handle = await fsOpen(validPath, APPEND_CREATE_NOFOLLOW);\n created = true;\n } catch (error) {\n if (!isNodeError(error) || error.code !== 'EEXIST') throw error;\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n }\n }\n try {\n signal?.throwIfAborted();\n // A file we just O_EXCL-created is a fresh regular file; only an existing one\n // can have been reached through a swapped ancestor.\n if (!created) await this.#assertFdWithinRoots(handle, filePath);\n // Ceiling: an append is not transactional, so an abort landing\n // mid-write can leave a prefix of the content on disk. Unwiring the\n // signal would trade that for a silently-completed write; a client\n // that retries is double-appending either way, so the abortable\n // write stays.\n await handle.writeFile(content, { signal });\n } catch (error) {\n if (created) {\n await fsUnlink(validPath).catch((unlinkError: unknown) => {\n Logger.warn(\n `Failed to remove newly created ${validPath} after a failed append: ${formatUnknownErrorMessage(unlinkError)}`,\n );\n });\n }\n throw error;\n } finally {\n await handle.close();\n }\n return { validPath };\n }\n\n /**\n * Leading bytes of a file, for MIME sniffing without loading the whole\n * file — an append's result must describe the resulting file (which may be\n * a multi-GB log), not the chunk that was appended.\n */\n async readLeadingSample(filePath: string): Promise<Buffer> {\n await using handle = await this.open(filePath);\n const buffer = Buffer.alloc(MIME_SAMPLE_SIZE);\n const { bytesRead } = await handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0);\n return buffer.subarray(0, bytesRead);\n }\n\n /**\n * Line count for a file on disk, counting newline bytes in fixed 64 KiB\n * chunks so an arbitrarily large file (one append's result metadata) costs\n * bounded memory. NOT FileHandle.readLines: it decodes each physical line\n * whole, so a multi-GB single-line log OOMs the server after the append has\n * already landed — a reported failure invites a retry that appends twice.\n * Matches read.ts's countLines semantics: a trailing newline does not open\n * a new line, an unterminated final line counts, an empty file is 0.\n */\n async countFileLines(filePath: string): Promise<number> {\n await using handle = await this.open(filePath);\n let newlines = 0;\n let lastByte = 0;\n let totalBytes = 0;\n const chunk = Buffer.alloc(LINE_COUNT_CHUNK_BYTES);\n for (;;) {\n const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);\n if (bytesRead === 0) break;\n totalBytes += bytesRead;\n for (let i = 0; i < bytesRead; i++) {\n const byte = chunk[i] ?? 0;\n if (byte === CHAR_LF) newlines++;\n lastByte = byte;\n }\n }\n // totalBytes is the empty-file sentinel: a NUL (0x00) final byte is real\n // content and must count as an unterminated final line, not \"empty\".\n return totalBytes === 0 ? 0 : lastByte === CHAR_LF ? newlines : newlines + 1;\n }\n\n async rm(filePath: string, options?: Parameters<typeof fsRm>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRm(validPath, options);\n }\n\n async rmdir(filePath: string, options?: Parameters<typeof fsRmdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRmdir(validPath, options);\n }\n\n async cp(\n source: string,\n destination: string,\n options?: Parameters<typeof fsCp>[2],\n ): Promise<void> {\n // As in rename: keep the link itself so callers passing verbatimSymlinks\n // actually copy the link rather than a dereferenced target.\n const validSource = await this.pathGuard.validatePathForDelete(source);\n const validDest = await this.pathGuard.validatePathForWrite(destination);\n await fsCp(validSource, validDest, options);\n }\n\n async readFile(filePath: string, spec: ReadSpec): Promise<ReadFileResult> {\n spec.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), spec.signal);\n return readFileWithStats(filePath, validPath, stats, spec, () =>\n this.#openVerified(validPath, filePath, spec.signal),\n );\n }\n\n async readEditableText(\n filePath: string,\n options?: { signal?: AbortSignal; tool?: string },\n ): Promise<{ validPath: string; content: string; stats: Stats }> {\n const { stats, validPath } = await this.stat(filePath, options);\n const maxSize = getMaxTextFileSize();\n if (stats.size > maxSize) {\n // `tool` labels the TOO_LARGE message so the client sees which tool refused.\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large for ${options?.tool ?? 'edit'} (${stats.size} bytes > ${maxSize} bytes)`,\n filePath,\n { size: stats.size, maxFileSize: maxSize },\n );\n }\n const { content } = await readFileWithStats(\n filePath,\n validPath,\n stats,\n {\n kind: 'full',\n strictUtf8: true,\n ...(options?.signal ? { signal: options.signal } : {}),\n },\n () => this.#openVerified(validPath, filePath, options?.signal),\n );\n return { validPath, content, stats };\n }\n\n async readRaw(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{\n content: Buffer;\n mimeType: string;\n kind: FileKind;\n isBinary: boolean;\n validPath: string;\n modified: string;\n }> {\n options?.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n options?.signal?.throwIfAborted();\n const stats = await withAbort(fsStat(validPath), options?.signal);\n assertFileStats(filePath, stats);\n // Enforce size limit before reading to avoid loading large files into memory.\n // Binary detection is best-effort, but this is a hard limit.\n const maxTextFileSize = getMaxTextFileSize();\n if (stats.size > maxTextFileSize) {\n throw createTooLargeError(stats.size, maxTextFileSize, filePath);\n }\n let content: Buffer;\n await using handle = await this.#openVerified(validPath, filePath);\n try {\n content = await fsReadFile(handle, options);\n } catch (error) {\n // Node wraps even a TimeoutError signal reason in AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n options?.signal?.throwIfAborted();\n }\n throw error;\n }\n const mimeInfo = detectMimeFromContent(validPath, content);\n return {\n content,\n mimeType: mimeInfo.mimeType,\n kind: mimeInfo.kind,\n isBinary: mimeInfo.kind !== 'text',\n validPath,\n modified: stats.mtime.toISOString(),\n };\n }\n\n async open(filePath: string): Promise<FileHandle> {\n return this.#openVerified(await this.pathGuard.validateExistingPath(filePath), filePath);\n }\n\n // Single resolution + stat: validateExistingPathDetailed resolves the real\n // path (following symlinks, re-checking sensitivity) once, then we stat the\n // resolved target. Replaces the tool-side validateExistingPathDetailed +\n // fs.stat pair that re-validated through the free stat() guard.\n async statDetailed(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ requestedPath: string; isSymlink: boolean; stats: Stats }> {\n const { requestedPath, resolvedPath, isSymlink } =\n await this.pathGuard.validateExistingPathDetailed(filePath);\n const stats = await withAbort(fsStat(resolvedPath), options?.signal);\n return { requestedPath, isSymlink, stats };\n }\n\n async hasChildrenUnchecked(dirPath: string): Promise<boolean> {\n const dir = await fsOpendir(dirPath);\n try {\n const entry = await dir.read();\n return entry !== null;\n } finally {\n await dir.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${dirPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n\n /**\n * Direct children of a directory, leaving out entries the guard treats as\n * sensitive so the count never reveals what listings hide. Hidden and\n * ignored entries count: this answers \"is it empty?\", not \"what would list show?\".\n * A symlinked child counts only when list would show it. The path validation, open, opendir,\n * each read and each link check race the signal, so a stalled mount ends the call at its\n * deadline.\n */\n async countEntries(dirPath: string, options?: { signal?: AbortSignal }): Promise<number> {\n const signal = options?.signal;\n // Refuse an already-cancelled call before any validation or I/O.\n signal?.throwIfAborted();\n // A late validation result is a plain object, so a stalled realpath needs no release.\n const { resolvedPath } = await withAbort(\n this.pathGuard.validateExistingPathDetailed(dirPath),\n signal,\n );\n // With procfs (COUNT_THROUGH_FD), open the directory itself with O_NOFOLLOW and check where\n // the open descriptor really is, so a directory or ancestor swapped for a symlink after\n // validation is refused. Then read entries through the descriptor, which a later swap cannot\n // redirect.\n const handle = COUNT_THROUGH_FD\n ? await this.#abandonOnAbort(\n fsOpen(resolvedPath, DIR_NOFOLLOW),\n signal,\n (late) => late.close(),\n `directory descriptor for ${resolvedPath}`,\n )\n : undefined;\n let dir: Dir | undefined;\n let pendingRead: Promise<Dirent | null> | undefined;\n try {\n const openedPath = handle ? await this.#assertFdWithinRoots(handle, dirPath) : undefined;\n // An in-root swap passes the roots check, but the children would be filtered under the\n // validated directory's spelling, not the one actually opened.\n if (openedPath !== undefined && !isSamePath(openedPath, resolvedPath)) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Path changed under validation and now resolves to another directory (TOCTOU).',\n dirPath,\n );\n }\n const readPath = handle ? `/proc/self/fd/${String(handle.fd)}` : resolvedPath;\n dir = await this.#abandonOnAbort(\n fsOpendir(readPath),\n signal,\n (late) => late.close(),\n `directory stream for ${resolvedPath}`,\n );\n let count = 0;\n for (;;) {\n // Each read races the signal, so a read that stalls ends the call at its deadline.\n const read = dir.read();\n pendingRead = read;\n const entry = await withAbort(read, signal);\n pendingRead = undefined;\n if (entry === null) break;\n if (this.pathGuard.isSensitive(join(dirPath, entry.name))) continue;\n // list walks the real directory, so it checks every child, links\n // included, under its real spelling: a deny rule naming the real\n // directory hides the child under an alias too.\n if (this.pathGuard.isSensitive(join(resolvedPath, entry.name))) continue;\n // A link counts only when list would show it: same rule, same error policy.\n if (\n entry.isSymbolicLink() &&\n !(await withAbort(\n this.pathGuard.isEntryAccessible(join(resolvedPath, entry.name)),\n signal,\n ))\n ) {\n continue;\n }\n count += 1;\n }\n // A deadline that fired during the last read still ends the call, even with no entries left.\n signal?.throwIfAborted();\n return count;\n } finally {\n if (dir) {\n const stream = dir;\n if (pendingRead) {\n // close() queues behind a read still in flight, so awaiting it would hold the TIMEOUT\n // response on a stalled mount. Close once that read settles, without waiting for it.\n void pendingRead\n .then(\n () => stream.close(),\n () => stream.close(),\n )\n .catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${resolvedPath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n } else {\n await stream.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${resolvedPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n // A failed close must not replace the error the count threw, so it is logged, not thrown.\n await handle?.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir descriptor for ${resolvedPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n}\n\n/**\n * Returns whether `path` exists, treating \"not found\" as absent and logging any\n * other stat failure. Used for TOCTOU re-checks between plan and execute phases.\n */\nexport async function destExists(\n fs: Pick<GuardedFileSystem, 'stat'>,\n path: string,\n label: string,\n): Promise<boolean> {\n try {\n await fs.stat(path);\n return true;\n } catch (err) {\n const missing = isNotFoundErrno(err) || (isFsError(err) && err.code === ErrorCode.NOT_FOUND);\n if (!missing) {\n Logger.warn(`${label}: dest stat failed unexpectedly for \"${path}\": ${String(err)}`);\n }\n return false;\n }\n}\n"]}
|
package/dist/core/glob.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DirentLike } from './path-utils.ts';
|
|
2
|
+
import type { PathGuard } from './path.ts';
|
|
2
3
|
/**
|
|
3
4
|
* More brace alternatives than this is not a file filter. fs.glob's matcher
|
|
4
5
|
* dedupes its alternatives pairwise, synchronously: `{1..100000}` pins the
|
|
@@ -7,6 +8,14 @@ import type { DirentLike } from './path-utils.ts';
|
|
|
7
8
|
export declare const MAX_BRACE_ALTERNATIVES = 1024;
|
|
8
9
|
/** Bound synchronous glob compilation when checking candidate traversal segments. */
|
|
9
10
|
export declare const MAX_DOTDOT_CHECK_CHARS = 4096;
|
|
11
|
+
/**
|
|
12
|
+
* fs.glob tests each name against a regex compiled from the segment,
|
|
13
|
+
* synchronously. Every run of `*` and every extglob group (`?(`, `+(`, `@(`,
|
|
14
|
+
* `!(`, `*(`) adds a degree of backtracking: on a 255-character name three cost
|
|
15
|
+
* under 100 ms, four about 1 s, and five do not finish; a chain of `?(a|aa)`
|
|
16
|
+
* groups grows the same way.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_SEGMENT_WILDCARDS = 3;
|
|
10
19
|
export declare function isSafeGlobSyntax(pattern: string): boolean;
|
|
11
20
|
export interface GlobEntry {
|
|
12
21
|
path: string;
|
|
@@ -28,6 +37,12 @@ export interface GlobEntriesOptions {
|
|
|
28
37
|
* consulted; nothing above it is ever read. Ignored without `skipIgnored`.
|
|
29
38
|
*/
|
|
30
39
|
ignoreCeiling?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Prune directories whose own name the sensitive denylist covers: the walk
|
|
42
|
+
* never descends into them nor yields them. Opt-in — a walk that must see
|
|
43
|
+
* sensitive entries (the move-protection check) does not pass it.
|
|
44
|
+
*/
|
|
45
|
+
pathGuard?: Pick<PathGuard, 'isSensitive'>;
|
|
31
46
|
/** Bounds the `.gitignore` discovery `skipIgnored` runs before the walk. */
|
|
32
47
|
signal?: AbortSignal;
|
|
33
48
|
}
|
package/dist/core/glob.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/core/glob.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/core/glob.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAG3C;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,qFAAqF;AACrF,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAqKvC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAyBzD;AA8ND,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAC3C,4EAA4E;IAC5E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAyRD,wBAAuB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,cAAc,CAAC,SAAS,CAAC,CA0BzF"}
|