@j0hanz/filesystem-mcp 2.5.2 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -19
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +7 -4
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +9 -4
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/cursor.d.ts.map +1 -1
- package/dist/core/cursor.js +11 -1
- package/dist/core/cursor.js.map +1 -1
- package/dist/core/diff.d.ts +15 -2
- package/dist/core/diff.d.ts.map +1 -1
- package/dist/core/diff.js +38 -7
- package/dist/core/diff.js.map +1 -1
- package/dist/core/errors.d.ts +4 -5
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +14 -14
- package/dist/core/errors.js.map +1 -1
- package/dist/core/fmt.d.ts +3 -1
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +3 -3
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +2 -8
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +32 -20
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +1 -1
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +77 -80
- package/dist/core/glob.js.map +1 -1
- package/dist/core/mime.d.ts +1 -0
- package/dist/core/mime.d.ts.map +1 -1
- package/dist/core/mime.js +1 -1
- package/dist/core/mime.js.map +1 -1
- package/dist/core/observability.js +1 -1
- package/dist/core/observability.js.map +1 -1
- package/dist/core/path-completer.d.ts +2 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +14 -13
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.d.ts.map +1 -1
- package/dist/core/path-discovery.js +29 -21
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path-utils.d.ts +8 -0
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +24 -17
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +13 -1
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +27 -11
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +1 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +23 -12
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +10 -0
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +20 -6
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +12 -4
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +42 -26
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts +7 -0
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +21 -2
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/watcher-registry.d.ts.map +1 -1
- package/dist/core/watcher-registry.js +24 -9
- package/dist/core/watcher-registry.js.map +1 -1
- package/dist/instructions.js +4 -4
- package/dist/instructions.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +21 -3
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +2 -5
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +1 -2
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +21 -6
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +1 -1
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +2 -7
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +8 -5
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +74 -30
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +1 -2
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/list-roots.d.ts.map +1 -1
- package/dist/tools/list-roots.js +1 -3
- package/dist/tools/list-roots.js.map +1 -1
- package/dist/tools/list.js +4 -4
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts +2 -0
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +10 -4
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.js +1 -1
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +7 -29
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +13 -13
- 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 +3 -1
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +4 -9
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.d.ts +3 -3
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +10 -9
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +38 -14
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +2 -2
- package/dist/transport/shared.d.ts.map +1 -1
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +1 -1
package/dist/core/path.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Stats } from 'node:fs';\nimport { lstat, readlink, realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';\n\nimport { withAbort } from './concurrency.ts';\nimport { cli } from './config.ts';\nimport {\n ERRNO_MAP,\n ErrorCode,\n FsError,\n isFsError,\n isNodeError,\n isNotFoundErrno,\n rethrowIfAborted,\n SKIPPABLE_ERRNOS,\n SKIPPABLE_FS_CODES,\n} from './errors.ts';\nimport { Logger } from './observability.ts';\nimport { findProjectRoot, isUnsafeCwdPath, resolveConfiguredDirs } from './path-discovery.ts';\nimport {\n getReservedDeviceNameForPath,\n isPathWithinDirectories,\n isSamePath,\n isWindowsDriveRelativePath,\n normalizeAllowedDirectory,\n normalizePath,\n parseTrueEnvFlag,\n toPosixPath,\n} from './path-utils.ts';\nimport { SensitiveMatcher } from './sensitive.ts';\nimport { ROOTS_TIMEOUT_MS } from './util.ts';\n\n// Allowed-directory assembly and the PathGuard that enforces it. The\n// sensitive-file denylist lives in sensitive.ts, and the character-level\n// primitives (IS_WINDOWS, isAlpha, isSlash, toPosixPath) live in\n// path-utils.ts.\n\nexport interface ValidatedPathDetails {\n requestedPath: string;\n resolvedPath: string;\n isSymlink: boolean;\n}\n\nexport interface ServerOptions {\n allowCwd?: boolean;\n cliAllowedDirs?: string[];\n readOnly?: boolean;\n}\n\n/** True when `normalizedRoot` really resolves inside `bounds` (FS_ROOT_BOUNDARY). */\nasync function isRootWithin(\n normalizedRoot: string,\n bounds: readonly string[],\n signal?: AbortSignal,\n): Promise<boolean> {\n try {\n signal?.throwIfAborted();\n const realPath = await withAbort(realpath(normalizedRoot), signal);\n return isPathWithinDirectories(normalizePath(realPath), bounds);\n } catch (error) {\n rethrowIfAborted(error);\n if (isNotFoundErrno(error)) {\n return false;\n }\n Logger.warn('grantBoundary: realpath failed unexpectedly', {\n root: normalizedRoot,\n error: String(error),\n });\n return false;\n }\n}\n\nasync function filterRootsWithin(\n roots: readonly string[],\n bounds: readonly string[],\n signal?: AbortSignal,\n): Promise<string[]> {\n const normalizedBounds = normalizeAllowedDirectories(bounds);\n const normalizedRoots = roots.map(normalizePath);\n\n const results = await Promise.all(\n normalizedRoots.map((root) => isRootWithin(root, normalizedBounds, signal)),\n );\n\n return normalizedRoots.filter((_, i) => results[i]);\n}\n\n/** `path.relative` with forward slashes, so displayed paths match across platforms. */\nexport function toPosixRelative(from: string, to: string): string {\n return toPosixPath(relative(from, to));\n}\n\n// ---------------------------------------------------------------------------\n// Resolver pipeline\n// ---------------------------------------------------------------------------\n\nexport function normalizeAllowedDirectories(dirs: readonly string[]): string[] {\n return [...new Set(dirs.map(normalizeAllowedDirectory).filter((entry) => entry.length > 0))];\n}\n\nexport async function resolveRealPath(\n normalized: string,\n signal?: AbortSignal,\n): Promise<string | null> {\n try {\n signal?.throwIfAborted();\n const realPath = await withAbort(realpath(normalized), signal);\n return normalizeAllowedDirectory(realPath);\n } catch (error) {\n rethrowIfAborted(error);\n // Only suppress ENOENT — the path genuinely does not exist.\n // EACCES, EIO, and other unexpected errors are rethrown so callers\n // cannot silently operate with a narrowed allowed-directory set.\n if (isNotFoundErrno(error)) {\n return null;\n }\n throw error;\n }\n}\n\nexport async function resolveAllowedDirectoriesState(\n dirs: readonly string[],\n signal?: AbortSignal,\n): Promise<string[]> {\n const primary = normalizeAllowedDirectories(dirs);\n const reals = await Promise.all(primary.map((dir) => resolveRealPath(dir, signal)));\n return [\n ...new Set(\n primary.flatMap((dir, i) => {\n const real = reals[i];\n return real && !isSamePath(real, dir) ? [dir, real] : [dir];\n }),\n ),\n ];\n}\n\n// ---------------------------------------------------------------------------\n// Windows helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Accepted risk: validation methods resolve/verify a path (symlinks, boundaries,\n * sensitivity) and then return a plain string; the actual fs operation happens\n * afterward as a separate syscall (classic TOCTOU). A symlink swapped in that\n * exact window could redirect the follow-up operation. This is mitigated by\n * re-validating the resolved real path (not just the requested path) and by\n * walking ancestors to catch escapes before the target exists, but it is not\n * eliminated — doing so would require fd-based operations (open with\n * O_NOFOLLOW / operate on the resolved fd) throughout core/fs.ts. Acceptable\n * tradeoff for a local, single-user filesystem server today.\n */\nexport class PathGuard {\n private allowedDirectoriesState: string[] | undefined;\n private readonly sensitive = new SensitiveMatcher();\n /**\n * Directories added by an accepted access grant (R8) — from the tool\n * executor's grant round-trip, or from a client's declared workspace roots on\n * the legacy stdio leg. Held separately from the configured baseline because\n * `recomputeAllowedDirectories` rebuilds that baseline from CLI args and env\n * on every call: anything merged into it would be recomputed away.\n *\n * These are NOT filtered against the baseline. A granted directory is\n * out-of-baseline by definition, so a baseline filter dropped every one while\n * `applyGrant` still reported success — the whole round-trip prompted the\n * user and then changed nothing. FS_ROOT_BOUNDARY and the unsafe-path denylist\n * remain the limits, both checked in `applyGrant` — the only writer of this\n * field — so the recompute can rebuild from it without re-filtering.\n */\n private grantedDirectories: string[] = [];\n private rootBoundaries: string[] = [];\n\n // ponytail: one mutex per PathGuard. If per-session grant throughput ever\n // matters, split into per-grant-dir locks; a single lock is correct for the\n // stdio + InMemoryEventStore single-process model.\n #mutex = Promise.resolve();\n\n private readonly options: ServerOptions | undefined;\n\n constructor(options?: ServerOptions) {\n this.options = options;\n }\n\n initialize(expanded: readonly string[]): void {\n this.allowedDirectoriesState = normalizeAllowedDirectories(expanded);\n }\n\n isInitialized(): boolean {\n return this.allowedDirectoriesState !== undefined;\n }\n\n /**\n * Run `fn` as the next holder of the guard's single mutation lock. Every\n * change to the allowed set goes through this so concurrent grants cannot\n * interleave their read-`await`-write and lose a grant (GRANT-1).\n */\n private async runExclusive<T>(fn: () => Promise<T>): Promise<T> {\n const result = this.#mutex.then(fn, fn);\n this.#mutex = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n getAllowedDirectories(): string[] {\n if (!this.allowedDirectoriesState) {\n return [];\n }\n return [...this.allowedDirectoriesState];\n }\n\n isSensitive(filePath: string): boolean {\n return this.sensitive.isSensitive(filePath);\n }\n\n /**\n * True when `entryPath` is both within the guard's allowed directories and\n * not sensitive, checking the requested AND resolved paths. EVERY entry is\n * realpath-resolved via validateExistingPathDetailed: a symlinked ancestor\n * directory pointing outside the sandbox would otherwise pass the lexical\n * containment check (fs.glob follows symlinks and yields external entries as\n * non-symlink dirents). validateExistingPathDetailed re-checks containment on\n * the real path against this.allowedDirectoriesState (the guard's full\n * allowed set — a superset of the single-root `bounds` callers pass) and\n * re-checks sensitivity on the resolved target, throwing ACCESS_DENIED for\n * escapes/sensitive, which the catch below turns into a filter. Skippable\n * errno/fs errors return false (the entry is filtered, not fatal). The\n * accepted TOCTOU window is documented at the class docstring above.\n */\n async isEntryAccessible(entryPath: string): Promise<boolean> {\n const isSensitive = (requestedPath: string, resolvedPath: string): boolean =>\n this.isSensitive(requestedPath) || this.isSensitive(resolvedPath);\n try {\n const validated = await this.validateExistingPathDetailed(entryPath);\n return !isSensitive(validated.requestedPath, validated.resolvedPath);\n } catch (error) {\n if (isFsError(error)) {\n if (SKIPPABLE_FS_CODES.has(error.code)) return false;\n throw error;\n }\n if (isNodeError(error) && error.code !== undefined && SKIPPABLE_ERRNOS.has(error.code))\n return false;\n throw error;\n }\n }\n\n async validateExistingPath(requestedPath: string): Promise<string> {\n const details = await this.validateExistingPathDetailed(requestedPath);\n return details.resolvedPath;\n }\n\n /**\n * True when a grant must never admit `targetDir` — an unsafe path (home,\n * /etc, C:\\Windows, ...). Checked on the lexical path AND on the one it\n * resolves to: `resolveAllowedDirectoriesState` pushes each root's realpath\n * into the allowed set, so a lexical-only check let a symlink or junction\n * aliasing $HOME/C:\\Windows in under an innocuous name — and the confirmation\n * prompt showed the alias, not the target. A target that cannot be resolved\n * is judged lexically; it does not exist to escape into.\n */\n private async isUnsafeGrantTarget(targetDir: string): Promise<boolean> {\n const normalized = normalizePath(targetDir);\n if (isUnsafeCwdPath(normalized)) return true;\n let resolved: string;\n try {\n resolved = normalizePath(await realpath(normalized));\n } catch {\n return false;\n }\n return !isSamePath(resolved, normalized) && isUnsafeCwdPath(resolved);\n }\n\n /** Walk up from a blocked path to the closest existing ancestor directory. */\n private async resolveGrantTargetDir(blockedPath: string): Promise<string> {\n let targetDir = blockedPath;\n for (;;) {\n const parent = dirname(targetDir);\n try {\n // An existing directory is the grant target; an existing file grants\n // its parent directory.\n return (await stat(targetDir)).isDirectory() ? targetDir : parent;\n } catch {\n // Missing — keep walking up.\n }\n if (parent === targetDir) return targetDir;\n targetDir = parent;\n }\n }\n\n /**\n * Pre-check (no mutation, no callback): given the paths a tool is about to\n * operate on, return the sorted, de-duplicated grant-target directories for\n * those that are outside the allowed roots AND grantable (within FS_ROOT_BOUNDARY\n * when it is configured). A path whose nearest existing ancestor escapes the\n * boundary is NOT grantable and is omitted — the operation will fail with\n * ACCESS_DENIED for it rather than prompt. The caller returns an\n * `input_required` result carrying exactly this set (R7), and on retry applies\n * each accepted grant via {@link applyGrant} (R8). `requestState` binds this\n * set so a grant accepted for X cannot authorize Y (R9).\n */\n async precheckAccess(paths: readonly string[]): Promise<string[]> {\n if (!this.allowedDirectoriesState || paths.length === 0) {\n return [];\n }\n const allowedDirs = this.allowedDirectoriesState;\n const grantDirs: string[] = [];\n for (const requested of paths) {\n if (!requested) {\n continue;\n }\n const normalized = normalizePath(requested);\n if (isPathWithinDirectories(normalized, allowedDirs)) {\n continue;\n }\n const targetDir = normalizePath(await this.resolveGrantTargetDir(normalized));\n // Never offer a grant into a bare filesystem root or an unsafe path (home,\n // /etc, C:\\Windows, ...). Without FS_ROOT_BOUNDARY, isWithinBoundary returns\n // true for everything, so this is the one guard that still rejects roots a\n // misleading grant could reach. Mirrors the check gating --allow-cwd.\n if (await this.isUnsafeGrantTarget(targetDir)) {\n continue;\n }\n if (grantDirs.includes(targetDir)) {\n continue;\n }\n if (!(await this.isWithinBoundary(targetDir))) {\n continue;\n }\n grantDirs.push(targetDir);\n }\n return grantDirs.sort();\n }\n\n /**\n * Apply an accepted access grant: enforce FS_ROOT_BOUNDARY again (a TOCTOU\n * re-check against the boundary resolved at config time), then extend the\n * allowed roots for the remainder of the session (R8, A4). Returns false when\n * the boundary or the unsafe-path denylist blocks the grant, AND when the\n * recompute did not actually admit the directory — the return value now\n * reports what the guard's allowed set really holds, not merely that the\n * pre-checks passed. The caller leaves a refused path to fail with\n * ACCESS_DENIED during the operation. Idempotent: re-granting an already\n * allowed directory is a no-op via the dedup in `initialize`.\n */\n async applyGrant(targetDir: string): Promise<boolean> {\n // Defense-in-depth: even a tampered/accepted grant cannot extend roots into\n // an unsafe path. The precheckAccess guard already refuses to offer these;\n // this catches a grant that arrived by another route — the legacy stdio\n // `roots/list` seeding, which has no confirmation round-trip at all.\n if (await this.isUnsafeGrantTarget(targetDir)) {\n return false;\n }\n if (!(await this.isWithinBoundary(targetDir))) {\n return false;\n }\n // Read + write under the mutation lock so a concurrent grant cannot\n // interleave and lose this grant (GRANT-1). runExclusive is not reentrant:\n // nothing in this body may take the lock again.\n return this.runExclusive(async () => {\n const previous = this.grantedDirectories;\n this.grantedDirectories = [...previous, normalizePath(targetDir)];\n try {\n await this.recomputeAllowedDirectories();\n } catch (error) {\n // A failed recompute leaves the guard with its previous, consistent view.\n this.grantedDirectories = previous;\n throw error;\n }\n // Verify rather than assume: a boundary that no longer covers this\n // directory would drop it in the recompute above, and reporting success\n // for a grant that did not land is exactly the failure this replaces.\n if (isPathWithinDirectories(normalizePath(targetDir), this.getAllowedDirectories())) {\n return true;\n }\n this.grantedDirectories = previous;\n await this.recomputeAllowedDirectories();\n return false;\n });\n }\n\n // The guard already realpath-resolved FS_ROOT_BOUNDARY into rootBoundaries\n // during recomputeAllowedDirectories. Reuse that single source of truth so\n // grant paths check the same boundary the rest of the guard enforces,\n // instead of re-reading the env and re-resolving each entry.\n private async isWithinBoundary(targetDir: string): Promise<boolean> {\n if (this.rootBoundaries.length === 0) {\n return true;\n }\n let resolved: string;\n try {\n resolved = normalizePath(await realpath(targetDir));\n } catch {\n resolved = normalizePath(targetDir);\n }\n return isPathWithinDirectories(resolved, this.rootBoundaries);\n }\n\n private validateAccessAndSensitivity(requestedPath: string): {\n normalizedRequested: string;\n allowedDirs: string[];\n accessDeniedHint: string;\n } {\n const result = this.validateAccess(requestedPath);\n this.assertNotSensitiveFile(requestedPath, requestedPath);\n this.assertNotSensitiveFile(result.normalizedRequested, requestedPath);\n return result;\n }\n\n // Synchronous since the access-grant round-trip moved to the executor's\n // pre-check: validateAccess only does lexical containment math and throws,\n // no async I/O remains. Callers still `await` it for uniform control-flow;\n // awaiting a non-thenable returns it unchanged.\n private validateAccess(requestedPath: string): {\n normalizedRequested: string;\n allowedDirs: string[];\n accessDeniedHint: string;\n } {\n if (!this.allowedDirectoriesState) {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'PathGuard not initialized. Call initialize() first.',\n requestedPath,\n );\n }\n if (isWindowsDriveRelativePath(requestedPath)) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n 'Drive-relative paths are not allowed. Use C:\\\\path or C:/path instead of C:path.',\n requestedPath,\n );\n }\n const reservedDevice = getReservedDeviceNameForPath(requestedPath);\n if (reservedDevice) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n `Reserved Windows device name not allowed: ${reservedDevice}.`,\n requestedPath,\n );\n }\n\n const normalizedRequested = normalizePath(requestedPath);\n const allowedDirs = this.allowedDirectoriesState;\n\n const accessDeniedHint =\n allowedDirs.length > 0\n ? `Allowed: ${allowedDirs.join(', ')}`\n : 'No allowed directories configured.';\n\n if (!isPathWithinDirectories(normalizedRequested, allowedDirs)) {\n // Out of root. The access-grant `input_required` round-trip is driven by\n // the executor's pre-check (precheckAccess) BEFORE the operation runs, so\n // by the time validation reaches here any grantable out-of-root path has\n // already been accepted and added to the allowed set. A path still out of\n // root here was either declined, ungrantable (outside FS_ROOT_BOUNDARY), or\n // never pre-checked — fail closed.\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n\n return {\n normalizedRequested,\n allowedDirs: this.allowedDirectoriesState,\n accessDeniedHint,\n };\n }\n\n private async handleRealpathError(\n error: unknown,\n normalizedRequested: string,\n allowedDirs: string[],\n accessDeniedHint: string,\n requestedPath: string,\n ): Promise<never> {\n if (isNotFoundErrno(error)) {\n // Resolve the nearest existing ancestor to detect out-of-sandbox symlinks.\n // e.g. if `link -> C:\\external` and path is `link\\nonexistent.txt`, the\n // ancestor resolves outside allowed dirs → ACCESS_DENIED, not NOT_FOUND.\n try {\n const { realAncestor, resolvedTarget } = await this.resolveNearestExistingAncestor(\n requestedPath,\n normalizedRequested,\n );\n if (\n !isPathWithinDirectories(realAncestor, allowedDirs) ||\n !isPathWithinDirectories(resolvedTarget, allowedDirs)\n ) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n } catch (ancestorErr) {\n // Rethrow any FsError — collapsing e.g. UNKNOWN to NOT_FOUND would mask\n // incomplete sandbox checks and make bugs invisible to callers.\n if (isFsError(ancestorErr)) {\n throw ancestorErr;\n }\n }\n\n throw new FsError(ErrorCode.NOT_FOUND, 'Path not found', requestedPath, error);\n }\n\n const mapped =\n isNodeError(error) && error.code !== undefined ? ERRNO_MAP[error.code] : undefined;\n throw new FsError(\n mapped ?? ErrorCode.UNKNOWN,\n 'Cannot access path',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n\n async validateExistingPathDetailed(requestedPath: string): Promise<ValidatedPathDetails> {\n const { normalizedRequested, allowedDirs, accessDeniedHint } =\n this.validateAccessAndSensitivity(requestedPath);\n\n let realPath: string;\n try {\n realPath = await realpath(normalizedRequested);\n } catch (error) {\n realPath = await this.handleRealpathError(\n error,\n normalizedRequested,\n allowedDirs,\n accessDeniedHint,\n requestedPath,\n );\n }\n\n const normalizedReal = normalizePath(realPath);\n\n if (!isPathWithinDirectories(normalizedReal, allowedDirs)) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n\n // Re-check the resolved real path: a symlink inside an allowed root may\n // point at a sensitive file (e.g. link -> .env). The early check above only\n // sees the requested/normalized path, not the symlink target.\n this.assertNotSensitiveFile(normalizedReal, requestedPath);\n\n return {\n requestedPath: normalizedRequested,\n resolvedPath: normalizedReal,\n isSymlink: !isSamePath(normalizedRequested, normalizedReal),\n };\n }\n\n async validateExistingDirectory(requestedPath: string): Promise<string> {\n const details = await this.validateExistingPathDetailed(requestedPath);\n\n let stats: Stats;\n try {\n stats = await stat(details.resolvedPath);\n } catch (error) {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'Cannot access directory',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n\n if (!stats.isDirectory()) {\n throw new FsError(ErrorCode.NOT_DIRECTORY, 'Not a directory', requestedPath);\n }\n\n return details.resolvedPath;\n }\n\n resolvePathOrRoot(pathValue: string | undefined): string {\n if (pathValue && pathValue.trim().length > 0) {\n return pathValue;\n }\n const roots = this.getAllowedDirectories();\n if (roots.length === 0) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'No roots configured. Use the roots tool or --allow-cwd.',\n );\n }\n if (roots.length > 1) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n 'Multiple roots configured. Provide an explicit path.',\n );\n }\n const root = roots[0];\n if (!root) {\n throw new FsError(ErrorCode.ACCESS_DENIED, 'Workspace root is unexpectedly undefined');\n }\n return root;\n }\n\n isAllowedRoot(normalizedPath: string): boolean {\n // isSamePath case-folds on case-insensitive filesystems (win + darwin),\n // matching the containment checks used everywhere else in the guard. The\n // previous IS_WINDOWS-only fold left darwin doing exact-case compares here\n // while isPathInsideDirectory folded — a root matched case-insensitively\n // everywhere else was missed.\n return this.getAllowedDirectories().some((dir) => isSamePath(dir, normalizedPath));\n }\n\n // Checks ONLY the sensitive-file denylist. Root containment and symlink\n // resolution must be verified separately (e.g. via validateExistingPath).\n private assertNotSensitiveFile(checkPath: string, requestedPath: string): void {\n if (this.isSensitive(checkPath)) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Sensitive file blocked. Set ALLOW_SENSITIVE=1 to override.',\n requestedPath,\n );\n }\n }\n\n private throwAccessDenied(requestedPath: string, hint?: string): never {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n hint ? `Outside allowed directories. ${hint}` : 'Outside allowed directories.',\n requestedPath,\n );\n }\n private async resolveNearestExistingAncestor(\n requestedPath: string,\n currentPath: string,\n ): Promise<{ realAncestor: string; resolvedTarget: string }> {\n const missingSegments: string[] = [];\n let current = currentPath;\n for (;;) {\n try {\n const realAncestor = normalizePath(await realpath(current));\n const resolvedTarget =\n missingSegments.length === 0\n ? realAncestor\n : normalizePath(join(realAncestor, ...missingSegments.reverse()));\n return { realAncestor, resolvedTarget };\n } catch (error) {\n try {\n const stats = await lstat(current);\n if (stats.isSymbolicLink()) {\n const target = await readlink(current);\n const resolvedTarget = isAbsolute(target) ? target : resolve(dirname(current), target);\n const normalizedTarget = normalizePath(resolvedTarget);\n const allowedDirs = this.getAllowedDirectories();\n if (!isPathWithinDirectories(normalizedTarget, allowedDirs)) {\n this.throwAccessDenied(requestedPath);\n }\n }\n } catch (lstatErr) {\n if (isFsError(lstatErr) && lstatErr.code === ErrorCode.ACCESS_DENIED) {\n throw lstatErr;\n }\n // ENOENT is expected during ancestor walk — the entry simply doesn't exist.\n // Any other error (EACCES, EIO, ELOOP) is unexpected; fail safe.\n if (!isNodeError(lstatErr) || lstatErr.code !== 'ENOENT') {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'Cannot probe symlink ancestor',\n requestedPath,\n lstatErr instanceof Error ? lstatErr : undefined,\n );\n }\n }\n const parent = dirname(current);\n if (parent === current) {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'Cannot resolve path',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n missingSegments.push(basename(current));\n current = parent;\n }\n }\n }\n\n async validatePathForWrite(requestedPath: string): Promise<string> {\n const { normalizedRequested, allowedDirs, accessDeniedHint } =\n this.validateAccessAndSensitivity(requestedPath);\n\n const { realAncestor, resolvedTarget } = await this.resolveNearestExistingAncestor(\n requestedPath,\n normalizedRequested,\n );\n if (\n !isPathWithinDirectories(realAncestor, allowedDirs) ||\n !isPathWithinDirectories(resolvedTarget, allowedDirs)\n ) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n // Re-check the resolved target: a symlink inside an allowed root may point\n // at a sensitive file. Writing through such a link must be blocked too.\n this.assertNotSensitiveFile(resolvedTarget, requestedPath);\n return resolvedTarget;\n }\n\n async validatePathForDelete(requestedPath: string): Promise<string> {\n const { normalizedRequested, allowedDirs, accessDeniedHint } =\n this.validateAccessAndSensitivity(requestedPath);\n\n // A workspace root is refused here, before the parent-containment check\n // below reaches for a parent that is out-of-root by construction and\n // reports \"Outside allowed directories\" for the one directory the caller\n // can see IS allowed. delete-file.ts keeps its own root check for the\n // nested-root case, where the parent is itself an allowed root and this\n // path is never taken.\n if (this.isAllowedRoot(normalizedRequested)) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Deleting a workspace root directory is not allowed',\n requestedPath,\n );\n }\n\n const parent = dirname(normalizedRequested);\n let realParent: string;\n try {\n realParent = await realpath(parent);\n } catch (error) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n 'Parent directory not found',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n const normalizedRealParent = normalizePath(realParent);\n\n if (!isPathWithinDirectories(normalizedRealParent, allowedDirs)) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n\n // Resolve the final component when it exists. For deletion, we ONLY\n // block if the real target is outside the sandbox IF the target is\n // NOT a symlink. Deleting a symlink is safe even if it points outside.\n try {\n const stats = await lstat(normalizedRequested);\n if (stats.isSymbolicLink()) {\n // Symlink: check link sensitivity but don't resolve target.\n // The parent check above ensures the link itself is in an allowed root.\n this.assertNotSensitiveFile(normalizedRequested, requestedPath);\n return normalizedRequested;\n }\n\n // Not a symlink: resolve to catch path escapes (e.g. /allowed/dir/../../etc)\n // and block sensitive files.\n const realTarget = normalizePath(await realpath(normalizedRequested));\n if (!isPathWithinDirectories(realTarget, allowedDirs)) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n this.assertNotSensitiveFile(realTarget, requestedPath);\n return realTarget;\n } catch (error) {\n // A denial raised inside this block (out-of-root real target, or a\n // sensitive file reached through a symlinked parent) must propagate.\n // Only a probe failure — ENOENT and friends — falls through to the\n // parent check, which is sufficient for a path that does not exist.\n if (isFsError(error)) {\n throw error;\n }\n }\n return normalizedRequested;\n }\n\n async recomputeAllowedDirectories(): Promise<void> {\n const cliAllowedDirs = normalizeAllowedDirectories(this.options?.cliAllowedDirs ?? []);\n\n // Parse allowed directories from environment variable\n const allowMissing =\n cli.allowMissingRoots ??\n parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');\n const envAllowedDirs = await resolveConfiguredDirs('FS_ALLOWED_DIRS', { allowMissing });\n\n // Parse FS_ROOT_BOUNDARY (the --root-boundary flag beats the env var)\n const boundaries = await resolveConfiguredDirs('FS_ROOT_BOUNDARY', {\n resolveReal: true,\n ...(cli.rootBoundary !== undefined ? { rawValue: cli.rootBoundary } : {}),\n });\n\n const allowCwd = Boolean(this.options?.allowCwd);\n const allowCwdDirs: string[] = [];\n if (allowCwd) {\n let cwd = normalizePath(process.cwd());\n const walkCwd =\n cli.allowCwdWalk ?? parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');\n if (walkCwd) {\n cwd = await findProjectRoot(cwd, [...boundaries, homedir()]);\n }\n if (isUnsafeCwdPath(cwd)) {\n Logger.emit(\n 'warning',\n `Skipped adding unsafe current working directory to allowed list: ${cwd}`,\n );\n } else {\n allowCwdDirs.push(cwd);\n }\n }\n\n const baseline = [...cliAllowedDirs, ...envAllowedDirs, ...allowCwdDirs];\n\n const signal = AbortSignal.timeout(ROOTS_TIMEOUT_MS);\n // FS_ROOT_BOUNDARY is the only filter grants answer to (see\n // `grantedDirectories`); without one they pass through as accepted.\n const grantsToInclude =\n boundaries.length > 0\n ? await filterRootsWithin(this.grantedDirectories, boundaries, signal)\n : this.grantedDirectories;\n\n const combined = [...baseline, ...grantsToInclude];\n const nextState = await resolveAllowedDirectoriesState(combined, signal);\n // Commit boundaries and the allowed set together, after every await has\n // resolved, so a rejecting recompute leaves the guard's previous,\n // consistent view intact.\n this.rootBoundaries = boundaries;\n this.initialize(nextState);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Stats } from 'node:fs';\nimport { lstat, readlink, realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';\n\nimport { withAbort } from './concurrency.ts';\nimport { cli } from './config.ts';\nimport {\n classifyCauseChain,\n ErrorCode,\n FsError,\n isFsError,\n isNotFoundErrno,\n isSkippableErrno,\n rethrowIfAborted,\n SKIPPABLE_FS_CODES,\n} from './errors.ts';\nimport { Logger } from './observability.ts';\nimport { findProjectRoot, isUnsafeCwdPath, resolveConfiguredDirs } from './path-discovery.ts';\nimport {\n getReservedDeviceNameForPath,\n IS_WINDOWS,\n isPathWithinDirectories,\n isSamePath,\n isWindowsDriveRelativePath,\n normalizeAllowedDirectory,\n normalizePath,\n parseTrueEnvFlag,\n toPosixPath,\n} from './path-utils.ts';\nimport { SensitiveMatcher } from './sensitive.ts';\nimport { ROOTS_TIMEOUT_MS } from './util.ts';\n\n// Allowed-directory assembly and the PathGuard that enforces it. The\n// sensitive-file denylist lives in sensitive.ts, and the character-level\n// primitives (IS_WINDOWS, isAlpha, isSlash, toPosixPath) live in\n// path-utils.ts.\n\nexport interface ValidatedPathDetails {\n requestedPath: string;\n resolvedPath: string;\n isSymlink: boolean;\n}\n\nexport interface ServerOptions {\n allowCwd?: boolean;\n cliAllowedDirs?: string[];\n readOnly?: boolean;\n}\n\n/** True when `normalizedRoot` really resolves inside `bounds` (FS_ROOT_BOUNDARY). */\nasync function isRootWithin(\n normalizedRoot: string,\n bounds: readonly string[],\n signal?: AbortSignal,\n): Promise<boolean> {\n try {\n signal?.throwIfAborted();\n const realPath = await withAbort(realpath(normalizedRoot), signal);\n return isPathWithinDirectories(normalizePath(realPath), bounds);\n } catch (error) {\n rethrowIfAborted(error);\n if (isNotFoundErrno(error)) {\n return false;\n }\n Logger.warn('grantBoundary: realpath failed unexpectedly', {\n root: normalizedRoot,\n error: String(error),\n });\n return false;\n }\n}\n\nasync function filterRootsWithin(\n roots: readonly string[],\n bounds: readonly string[],\n signal?: AbortSignal,\n): Promise<string[]> {\n const normalizedBounds = normalizeAllowedDirectories(bounds);\n const normalizedRoots = roots.map(normalizePath);\n\n const results = await Promise.all(\n normalizedRoots.map((root) => isRootWithin(root, normalizedBounds, signal)),\n );\n\n return normalizedRoots.filter((_, i) => results[i]);\n}\n\n/** `path.relative` with forward slashes, so displayed paths match across platforms. */\nexport function toPosixRelative(from: string, to: string): string {\n return toPosixPath(relative(from, to));\n}\n\n// ---------------------------------------------------------------------------\n// Resolver pipeline\n// ---------------------------------------------------------------------------\n\nexport function normalizeAllowedDirectories(dirs: readonly string[]): string[] {\n return [...new Set(dirs.map(normalizeAllowedDirectory).filter((entry) => entry.length > 0))];\n}\n\nexport async function resolveRealPath(\n normalized: string,\n signal?: AbortSignal,\n): Promise<string | null> {\n try {\n signal?.throwIfAborted();\n const realPath = await withAbort(realpath(normalized), signal);\n return normalizeAllowedDirectory(realPath);\n } catch (error) {\n rethrowIfAborted(error);\n // Only suppress ENOENT — the path genuinely does not exist.\n // EACCES, EIO, and other unexpected errors are rethrown so callers\n // cannot silently operate with a narrowed allowed-directory set.\n if (isNotFoundErrno(error)) {\n return null;\n }\n throw error;\n }\n}\n\nexport async function resolveAllowedDirectoriesState(\n dirs: readonly string[],\n signal?: AbortSignal,\n): Promise<string[]> {\n const primary = normalizeAllowedDirectories(dirs);\n const reals = await Promise.all(primary.map((dir) => resolveRealPath(dir, signal)));\n return [\n ...new Set(\n primary.flatMap((dir, i) => {\n const real = reals[i];\n return real && !isSamePath(real, dir) ? [dir, real] : [dir];\n }),\n ),\n ];\n}\n\n// ---------------------------------------------------------------------------\n// Windows helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Accepted risk: validation methods resolve/verify a path (symlinks, boundaries,\n * sensitivity) and then return a plain string; the actual fs operation happens\n * afterward as a separate syscall (classic TOCTOU). A symlink swapped in that\n * exact window could redirect the follow-up operation. This is mitigated by\n * re-validating the resolved real path (not just the requested path) and by\n * walking ancestors to catch escapes before the target exists, but it is not\n * eliminated — doing so would require fd-based operations (open with\n * O_NOFOLLOW / operate on the resolved fd) throughout core/fs.ts. Acceptable\n * tradeoff for a local, single-user filesystem server today.\n */\nexport class PathGuard {\n private allowedDirectoriesState: string[] | undefined;\n /** One entry per configured or granted root, without the realpath aliases. */\n private rootsState: string[] | undefined;\n private readonly sensitive = new SensitiveMatcher();\n /**\n * Directories added by an accepted access grant (R8) — from the tool\n * executor's grant round-trip, or from a client's declared workspace roots on\n * the legacy stdio leg. Held separately from the configured baseline because\n * `recomputeAllowedDirectories` rebuilds that baseline from CLI args and env\n * on every call: anything merged into it would be recomputed away.\n *\n * These are NOT filtered against the baseline. A granted directory is\n * out-of-baseline by definition, so a baseline filter dropped every one while\n * `applyGrant` still reported success — the whole round-trip prompted the\n * user and then changed nothing. FS_ROOT_BOUNDARY and the unsafe-path denylist\n * remain the limits, both checked in `applyGrant` — the only writer of this\n * field — so the recompute can rebuild from it without re-filtering.\n */\n private grantedDirectories: string[] = [];\n private rootBoundaries: string[] = [];\n\n // ponytail: one mutex per PathGuard. If per-session grant throughput ever\n // matters, split into per-grant-dir locks; a single lock is correct for the\n // stdio + InMemoryEventStore single-process model.\n #mutex = Promise.resolve();\n\n private readonly options: ServerOptions | undefined;\n\n constructor(options?: ServerOptions) {\n this.options = options;\n }\n\n /**\n * `expanded` is the containment set (each root plus its realpath alias);\n * `roots` is what a caller choosing \"the root\" sees, one entry per root.\n */\n initialize(expanded: readonly string[], roots: readonly string[] = expanded): void {\n this.allowedDirectoriesState = normalizeAllowedDirectories(expanded);\n this.rootsState = normalizeAllowedDirectories(roots);\n }\n\n isInitialized(): boolean {\n return this.allowedDirectoriesState !== undefined;\n }\n\n /**\n * Run `fn` as the next holder of the guard's single mutation lock. Every\n * change to the allowed set goes through this so concurrent grants cannot\n * interleave their read-`await`-write and lose a grant (GRANT-1).\n */\n private async runExclusive<T>(fn: () => Promise<T>): Promise<T> {\n const result = this.#mutex.then(fn, fn);\n this.#mutex = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n getAllowedDirectories(): string[] {\n if (!this.allowedDirectoriesState) {\n return [];\n }\n return [...this.allowedDirectoriesState];\n }\n\n /**\n * The roots as configured or granted, one per root. getAllowedDirectories()\n * also holds each root's realpath alias for containment, so an aliased root\n * (symlink, junction, 8.3 short name) appears there twice.\n */\n getRoots(): string[] {\n return this.rootsState ? [...this.rootsState] : [];\n }\n\n isSensitive(filePath: string): boolean {\n return this.sensitive.isSensitive(filePath);\n }\n\n /**\n * True when `entryPath` is both within the guard's allowed directories and\n * not sensitive, checking the requested AND resolved paths. EVERY entry is\n * realpath-resolved via validateExistingPathDetailed: a symlinked ancestor\n * directory pointing outside the sandbox would otherwise pass the lexical\n * containment check (fs.glob follows symlinks and yields external entries as\n * non-symlink dirents). validateExistingPathDetailed re-checks containment on\n * the real path against this.allowedDirectoriesState (the guard's full\n * allowed set — a superset of the single-root `bounds` callers pass) and\n * re-checks sensitivity on the resolved target, throwing ACCESS_DENIED for\n * escapes/sensitive, which the catch below turns into a filter. Skippable\n * errno/fs errors return false (the entry is filtered, not fatal). The\n * accepted TOCTOU window is documented at the class docstring above.\n */\n async isEntryAccessible(entryPath: string): Promise<boolean> {\n const isSensitive = (requestedPath: string, resolvedPath: string): boolean =>\n this.isSensitive(requestedPath) || this.isSensitive(resolvedPath);\n try {\n const validated = await this.validateExistingPathDetailed(entryPath);\n return !isSensitive(validated.requestedPath, validated.resolvedPath);\n } catch (error) {\n if (isFsError(error)) {\n if (SKIPPABLE_FS_CODES.has(error.code)) return false;\n throw error;\n }\n if (isSkippableErrno(error)) return false;\n throw error;\n }\n }\n\n async validateExistingPath(requestedPath: string): Promise<string> {\n const details = await this.validateExistingPathDetailed(requestedPath);\n return details.resolvedPath;\n }\n\n /**\n * True when a grant must never admit `targetDir` — an unsafe path (home,\n * /etc, C:\\Windows, ...). Checked on the lexical path AND on the one it\n * resolves to: `resolveAllowedDirectoriesState` pushes each root's realpath\n * into the allowed set, so a lexical-only check let a symlink or junction\n * aliasing $HOME/C:\\Windows in under an innocuous name — and the confirmation\n * prompt showed the alias, not the target. A target that cannot be resolved\n * is judged lexically; it does not exist to escape into.\n */\n private async isUnsafeGrantTarget(targetDir: string): Promise<boolean> {\n const normalized = normalizePath(targetDir);\n if (isUnsafeCwdPath(normalized)) return true;\n let resolved: string;\n try {\n resolved = normalizePath(await realpath(normalized));\n } catch {\n return false;\n }\n return !isSamePath(resolved, normalized) && isUnsafeCwdPath(resolved);\n }\n\n /** Walk up from a blocked path to the closest existing ancestor directory. */\n private async resolveGrantTargetDir(blockedPath: string): Promise<string> {\n let targetDir = blockedPath;\n for (;;) {\n const parent = dirname(targetDir);\n try {\n // An existing directory is the grant target; an existing file grants\n // its parent directory.\n return (await stat(targetDir)).isDirectory() ? targetDir : parent;\n } catch {\n // Missing — keep walking up.\n }\n if (parent === targetDir) return targetDir;\n targetDir = parent;\n }\n }\n\n /**\n * Pre-check (no mutation, no callback): given the paths a tool is about to\n * operate on, return the sorted, de-duplicated grant-target directories for\n * those that are outside the allowed roots AND grantable (within FS_ROOT_BOUNDARY\n * when it is configured). A path whose nearest existing ancestor escapes the\n * boundary is NOT grantable and is omitted — the operation will fail with\n * ACCESS_DENIED for it rather than prompt. The caller returns an\n * `input_required` result carrying exactly this set (R7), and on retry applies\n * each accepted grant via {@link applyGrant} (R8). `requestState` binds this\n * set so a grant accepted for X cannot authorize Y (R9).\n */\n async precheckAccess(paths: readonly string[]): Promise<string[]> {\n if (!this.allowedDirectoriesState || paths.length === 0) {\n return [];\n }\n const allowedDirs = this.allowedDirectoriesState;\n const grantDirs: string[] = [];\n for (const requested of paths) {\n if (!requested) {\n continue;\n }\n const normalized = normalizePath(requested);\n if (isPathWithinDirectories(normalized, allowedDirs)) {\n continue;\n }\n const targetDir = normalizePath(await this.resolveGrantTargetDir(normalized));\n // Never offer a grant into a bare filesystem root or an unsafe path (home,\n // /etc, C:\\Windows, ...). Without FS_ROOT_BOUNDARY, isWithinBoundary returns\n // true for everything, so this is the one guard that still rejects roots a\n // misleading grant could reach. Mirrors the check gating --allow-cwd.\n if (await this.isUnsafeGrantTarget(targetDir)) {\n continue;\n }\n if (grantDirs.includes(targetDir)) {\n continue;\n }\n if (!(await this.isWithinBoundary(targetDir))) {\n continue;\n }\n grantDirs.push(targetDir);\n }\n return grantDirs.sort();\n }\n\n /**\n * Apply an accepted access grant: enforce FS_ROOT_BOUNDARY again (a TOCTOU\n * re-check against the boundary resolved at config time), then extend the\n * allowed roots for the remainder of the session (R8, A4). Returns false when\n * the boundary or the unsafe-path denylist blocks the grant, AND when the\n * recompute did not actually admit the directory — the return value now\n * reports what the guard's allowed set really holds, not merely that the\n * pre-checks passed. The caller leaves a refused path to fail with\n * ACCESS_DENIED during the operation. Idempotent: re-granting an already\n * allowed directory is a no-op via the dedup in `initialize`.\n */\n async applyGrant(targetDir: string): Promise<boolean> {\n // Defense-in-depth: even a tampered/accepted grant cannot extend roots into\n // an unsafe path. The precheckAccess guard already refuses to offer these;\n // this catches a grant that arrived by another route — the legacy stdio\n // `roots/list` seeding, which has no confirmation round-trip at all.\n if (await this.isUnsafeGrantTarget(targetDir)) {\n return false;\n }\n if (!(await this.isWithinBoundary(targetDir))) {\n return false;\n }\n // Read + write under the mutation lock so a concurrent grant cannot\n // interleave and lose this grant (GRANT-1). runExclusive is not reentrant:\n // nothing in this body may take the lock again.\n return this.runExclusive(async () => {\n const previous = this.grantedDirectories;\n this.grantedDirectories = [...previous, normalizePath(targetDir)];\n try {\n await this.recomputeAllowedDirectories();\n } catch (error) {\n // A failed recompute leaves the guard with its previous, consistent view.\n this.grantedDirectories = previous;\n throw error;\n }\n // Verify rather than assume: a boundary that no longer covers this\n // directory would drop it in the recompute above, and reporting success\n // for a grant that did not land is exactly the failure this replaces.\n if (isPathWithinDirectories(normalizePath(targetDir), this.getAllowedDirectories())) {\n return true;\n }\n this.grantedDirectories = previous;\n await this.recomputeAllowedDirectories();\n return false;\n });\n }\n\n // The guard already realpath-resolved FS_ROOT_BOUNDARY into rootBoundaries\n // during recomputeAllowedDirectories. Reuse that single source of truth so\n // grant paths check the same boundary the rest of the guard enforces,\n // instead of re-reading the env and re-resolving each entry.\n private async isWithinBoundary(targetDir: string): Promise<boolean> {\n if (this.rootBoundaries.length === 0) {\n return true;\n }\n let resolved: string;\n try {\n resolved = normalizePath(await realpath(targetDir));\n } catch {\n resolved = normalizePath(targetDir);\n }\n return isPathWithinDirectories(resolved, this.rootBoundaries);\n }\n\n private validateAccessAndSensitivity(requestedPath: string): {\n normalizedRequested: string;\n allowedDirs: string[];\n accessDeniedHint: string;\n } {\n const result = this.validateAccess(requestedPath);\n this.assertNotSensitiveFile(requestedPath, requestedPath);\n this.assertNotSensitiveFile(result.normalizedRequested, requestedPath);\n return result;\n }\n\n // Synchronous since the access-grant round-trip moved to the executor's\n // pre-check: validateAccess only does lexical containment math and throws,\n // no async I/O remains. Callers still `await` it for uniform control-flow;\n // awaiting a non-thenable returns it unchanged.\n private validateAccess(requestedPath: string): {\n normalizedRequested: string;\n allowedDirs: string[];\n accessDeniedHint: string;\n } {\n if (!this.allowedDirectoriesState) {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'PathGuard not initialized. Call initialize() first.',\n requestedPath,\n );\n }\n if (isWindowsDriveRelativePath(requestedPath)) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n 'Drive-relative paths are not allowed. Use C:\\\\path or C:/path instead of C:path.',\n requestedPath,\n );\n }\n // A device name is only a device on Windows; on POSIX `aux.ts` is a file.\n const reservedDevice = IS_WINDOWS ? getReservedDeviceNameForPath(requestedPath) : undefined;\n if (reservedDevice) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n `Reserved Windows device name not allowed: ${reservedDevice}.`,\n requestedPath,\n );\n }\n\n const normalizedRequested = normalizePath(requestedPath);\n const allowedDirs = this.allowedDirectoriesState;\n\n const accessDeniedHint =\n allowedDirs.length > 0\n ? `Allowed: ${allowedDirs.join(', ')}`\n : 'No allowed directories configured.';\n\n if (!isPathWithinDirectories(normalizedRequested, allowedDirs)) {\n // Out of root. The access-grant `input_required` round-trip is driven by\n // the executor's pre-check (precheckAccess) BEFORE the operation runs, so\n // by the time validation reaches here any grantable out-of-root path has\n // already been accepted and added to the allowed set. A path still out of\n // root here was either declined, ungrantable (outside FS_ROOT_BOUNDARY), or\n // never pre-checked — fail closed.\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n\n return {\n normalizedRequested,\n allowedDirs: this.allowedDirectoriesState,\n accessDeniedHint,\n };\n }\n\n private async handleRealpathError(\n error: unknown,\n normalizedRequested: string,\n allowedDirs: string[],\n accessDeniedHint: string,\n requestedPath: string,\n ): Promise<never> {\n if (isNotFoundErrno(error)) {\n // Resolve the nearest existing ancestor to detect out-of-sandbox symlinks.\n // e.g. if `link -> C:\\external` and path is `link\\nonexistent.txt`, the\n // ancestor resolves outside allowed dirs → ACCESS_DENIED, not NOT_FOUND.\n try {\n const { realAncestor, resolvedTarget } = await this.resolveNearestExistingAncestor(\n requestedPath,\n normalizedRequested,\n );\n if (\n !isPathWithinDirectories(realAncestor, allowedDirs) ||\n !isPathWithinDirectories(resolvedTarget, allowedDirs)\n ) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n } catch (ancestorErr) {\n // Rethrow any FsError — collapsing e.g. UNKNOWN to NOT_FOUND would mask\n // incomplete sandbox checks and make bugs invisible to callers.\n if (isFsError(ancestorErr)) {\n throw ancestorErr;\n }\n }\n\n throw new FsError(ErrorCode.NOT_FOUND, 'Path not found', requestedPath, error);\n }\n\n const mapped = classifyCauseChain(error);\n throw new FsError(\n mapped.code,\n 'Cannot access path',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n\n async validateExistingPathDetailed(requestedPath: string): Promise<ValidatedPathDetails> {\n const { normalizedRequested, allowedDirs, accessDeniedHint } =\n this.validateAccessAndSensitivity(requestedPath);\n\n let realPath: string;\n try {\n realPath = await realpath(normalizedRequested);\n } catch (error) {\n realPath = await this.handleRealpathError(\n error,\n normalizedRequested,\n allowedDirs,\n accessDeniedHint,\n requestedPath,\n );\n }\n\n const normalizedReal = normalizePath(realPath);\n\n if (!isPathWithinDirectories(normalizedReal, allowedDirs)) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n\n // Re-check the resolved real path: a symlink inside an allowed root may\n // point at a sensitive file (e.g. link -> .env). The early check above only\n // sees the requested/normalized path, not the symlink target.\n this.assertNotSensitiveFile(normalizedReal, requestedPath);\n\n return {\n requestedPath: normalizedRequested,\n resolvedPath: normalizedReal,\n isSymlink: !isSamePath(normalizedRequested, normalizedReal),\n };\n }\n\n async validateExistingDirectory(requestedPath: string): Promise<string> {\n const details = await this.validateExistingPathDetailed(requestedPath);\n\n let stats: Stats;\n try {\n stats = await stat(details.resolvedPath);\n } catch (error) {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'Cannot access directory',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n\n if (!stats.isDirectory()) {\n throw new FsError(ErrorCode.NOT_DIRECTORY, 'Not a directory', requestedPath);\n }\n\n return details.resolvedPath;\n }\n\n resolvePathOrRoot(pathValue: string | undefined): string {\n if (pathValue && pathValue.trim().length > 0) {\n return pathValue;\n }\n const roots = this.getRoots();\n if (roots.length === 0) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'No roots configured. Use the roots tool or --allow-cwd.',\n );\n }\n if (roots.length > 1) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n 'Multiple roots configured. Provide an explicit path.',\n );\n }\n const root = roots[0];\n if (!root) {\n throw new FsError(ErrorCode.ACCESS_DENIED, 'Workspace root is unexpectedly undefined');\n }\n return root;\n }\n\n isAllowedRoot(normalizedPath: string): boolean {\n // isSamePath case-folds on case-insensitive filesystems (win + darwin),\n // matching the containment checks used everywhere else in the guard. The\n // previous IS_WINDOWS-only fold left darwin doing exact-case compares here\n // while isPathInsideDirectory folded — a root matched case-insensitively\n // everywhere else was missed.\n return this.getAllowedDirectories().some((dir) => isSamePath(dir, normalizedPath));\n }\n\n // Checks ONLY the sensitive-file denylist. Root containment and symlink\n // resolution must be verified separately (e.g. via validateExistingPath).\n private assertNotSensitiveFile(checkPath: string, requestedPath: string): void {\n if (this.isSensitive(checkPath)) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Sensitive file blocked. Start the server with --allow-sensitive (or FS_ALLOW_SENSITIVE=1) to override.',\n requestedPath,\n );\n }\n }\n\n private throwAccessDenied(requestedPath: string, hint?: string): never {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n hint ? `Outside allowed directories. ${hint}` : 'Outside allowed directories.',\n requestedPath,\n );\n }\n private async resolveNearestExistingAncestor(\n requestedPath: string,\n currentPath: string,\n ): Promise<{ realAncestor: string; resolvedTarget: string }> {\n const missingSegments: string[] = [];\n let current = currentPath;\n for (;;) {\n try {\n const realAncestor = normalizePath(await realpath(current));\n const resolvedTarget =\n missingSegments.length === 0\n ? realAncestor\n : normalizePath(join(realAncestor, ...missingSegments.reverse()));\n return { realAncestor, resolvedTarget };\n } catch (error) {\n try {\n const stats = await lstat(current);\n if (stats.isSymbolicLink()) {\n const target = await readlink(current);\n const resolvedTarget = isAbsolute(target) ? target : resolve(dirname(current), target);\n const normalizedTarget = normalizePath(resolvedTarget);\n const allowedDirs = this.getAllowedDirectories();\n if (!isPathWithinDirectories(normalizedTarget, allowedDirs)) {\n this.throwAccessDenied(requestedPath);\n }\n }\n } catch (lstatErr) {\n if (isFsError(lstatErr) && lstatErr.code === ErrorCode.ACCESS_DENIED) {\n throw lstatErr;\n }\n // ENOENT is expected during ancestor walk — the entry simply doesn't exist.\n // Any other error (EACCES, EIO, ELOOP) is unexpected; fail safe.\n if (!isNotFoundErrno(lstatErr)) {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'Cannot probe symlink ancestor',\n requestedPath,\n lstatErr instanceof Error ? lstatErr : undefined,\n );\n }\n }\n const parent = dirname(current);\n if (parent === current) {\n throw new FsError(\n ErrorCode.UNKNOWN,\n 'Cannot resolve path',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n missingSegments.push(basename(current));\n current = parent;\n }\n }\n }\n\n async validatePathForWrite(requestedPath: string): Promise<string> {\n const { normalizedRequested, allowedDirs, accessDeniedHint } =\n this.validateAccessAndSensitivity(requestedPath);\n\n const { realAncestor, resolvedTarget } = await this.resolveNearestExistingAncestor(\n requestedPath,\n normalizedRequested,\n );\n if (\n !isPathWithinDirectories(realAncestor, allowedDirs) ||\n !isPathWithinDirectories(resolvedTarget, allowedDirs)\n ) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n // Re-check the resolved target: a symlink inside an allowed root may point\n // at a sensitive file. Writing through such a link must be blocked too.\n this.assertNotSensitiveFile(resolvedTarget, requestedPath);\n return resolvedTarget;\n }\n\n async validatePathForDelete(requestedPath: string): Promise<string> {\n const { normalizedRequested, allowedDirs, accessDeniedHint } =\n this.validateAccessAndSensitivity(requestedPath);\n\n // A workspace root is refused here, before the parent-containment check\n // below reaches for a parent that is out-of-root by construction and\n // reports \"Outside allowed directories\" for the one directory the caller\n // can see IS allowed. delete-file.ts keeps its own root check for the\n // nested-root case, where the parent is itself an allowed root and this\n // path is never taken.\n if (this.isAllowedRoot(normalizedRequested)) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Deleting a workspace root directory is not allowed',\n requestedPath,\n );\n }\n\n const parent = dirname(normalizedRequested);\n let realParent: string;\n try {\n realParent = await realpath(parent);\n } catch (error) {\n throw new FsError(\n ErrorCode.NOT_FOUND,\n 'Parent directory not found',\n requestedPath,\n error instanceof Error ? error : undefined,\n );\n }\n const normalizedRealParent = normalizePath(realParent);\n\n if (!isPathWithinDirectories(normalizedRealParent, allowedDirs)) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n\n // Resolve the final component when it exists. For deletion, we ONLY\n // block if the real target is outside the sandbox IF the target is\n // NOT a symlink. Deleting a symlink is safe even if it points outside.\n try {\n const stats = await lstat(normalizedRequested);\n if (stats.isSymbolicLink()) {\n // Symlink: check link sensitivity but don't resolve target.\n // The parent check above ensures the link itself is in an allowed root.\n this.assertNotSensitiveFile(normalizedRequested, requestedPath);\n return normalizedRequested;\n }\n\n // Not a symlink: resolve to catch path escapes (e.g. /allowed/dir/../../etc)\n // and block sensitive files.\n const realTarget = normalizePath(await realpath(normalizedRequested));\n if (!isPathWithinDirectories(realTarget, allowedDirs)) {\n this.throwAccessDenied(requestedPath, accessDeniedHint);\n }\n this.assertNotSensitiveFile(realTarget, requestedPath);\n return realTarget;\n } catch (error) {\n // A denial raised inside this block (out-of-root real target, or a\n // sensitive file reached through a symlinked parent) must propagate.\n // Only a probe failure — ENOENT and friends — falls through to the\n // parent check, which is sufficient for a path that does not exist.\n if (isFsError(error)) {\n throw error;\n }\n }\n return normalizedRequested;\n }\n\n async recomputeAllowedDirectories(): Promise<void> {\n const cliAllowedDirs = normalizeAllowedDirectories(this.options?.cliAllowedDirs ?? []);\n\n // Parse allowed directories from environment variable\n const allowMissing =\n cli.allowMissingRoots ??\n parseTrueEnvFlag(process.env['FS_ALLOW_MISSING_ROOTS'], 'FS_ALLOW_MISSING_ROOTS');\n const envAllowedDirs = await resolveConfiguredDirs('FS_ALLOWED_DIRS', { allowMissing });\n\n // Parse FS_ROOT_BOUNDARY (the --root-boundary flag beats the env var)\n const boundaries = await resolveConfiguredDirs('FS_ROOT_BOUNDARY', {\n resolveReal: true,\n ...(cli.rootBoundary !== undefined ? { rawValue: cli.rootBoundary } : {}),\n });\n\n const allowCwd = Boolean(this.options?.allowCwd);\n const allowCwdDirs: string[] = [];\n if (allowCwd) {\n let cwd = normalizePath(process.cwd());\n const walkCwd =\n cli.allowCwdWalk ?? parseTrueEnvFlag(process.env['FS_ALLOW_CWD_WALK'], 'FS_ALLOW_CWD_WALK');\n if (walkCwd) {\n cwd = await findProjectRoot(cwd, [...boundaries, homedir()]);\n }\n if (isUnsafeCwdPath(cwd)) {\n Logger.emit(\n 'warning',\n `Skipped adding unsafe current working directory to allowed list: ${cwd}`,\n );\n } else {\n allowCwdDirs.push(cwd);\n }\n }\n\n const baseline = [...cliAllowedDirs, ...envAllowedDirs, ...allowCwdDirs];\n\n const signal = AbortSignal.timeout(ROOTS_TIMEOUT_MS);\n // FS_ROOT_BOUNDARY is the only filter grants answer to (see\n // `grantedDirectories`); without one they pass through as accepted.\n const grantsToInclude =\n boundaries.length > 0\n ? await filterRootsWithin(this.grantedDirectories, boundaries, signal)\n : this.grantedDirectories;\n\n const combined = [...baseline, ...grantsToInclude];\n const nextState = await resolveAllowedDirectoriesState(combined, signal);\n // Commit boundaries and the allowed set together, after every await has\n // resolved, so a rejecting recompute leaves the guard's previous,\n // consistent view intact.\n this.rootBoundaries = boundaries;\n this.initialize(nextState, combined);\n }\n}\n"]}
|
package/dist/core/read.d.ts
CHANGED
package/dist/core/read.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/core/read.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/core/read.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAwC,OAAO,EAAa,MAAM,aAAa,CAAC;AAyCvF,MAAM,MAAM,QAAQ,GAChB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAazE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,GACpB,OAAO,CAMT;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,MAAM,CAAC,CAkCjB;AAED,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,KAAG,MAI5C,CAAC;AAiXF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAIpE;AAED,iEAAiE;AACjE,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,cAAc,CAAC,CAczB"}
|
package/dist/core/read.js
CHANGED
|
@@ -50,11 +50,13 @@ var __disposeResources = (this && this.__disposeResources) || (function (Suppres
|
|
|
50
50
|
var e = new Error(message);
|
|
51
51
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
52
|
});
|
|
53
|
+
// File reading pipeline split out of fs.ts. fs.ts imports from here; never the reverse.
|
|
54
|
+
import { isUtf8 } from 'node:buffer';
|
|
53
55
|
import { open as fsOpen } from 'node:fs/promises';
|
|
54
56
|
import { StringDecoder } from 'node:string_decoder';
|
|
55
57
|
import { withAbort } from "./concurrency.js";
|
|
56
58
|
import { ErrorCode, formatUnknownErrorMessage, FsError, isFsError } from "./errors.js";
|
|
57
|
-
import { isBinarySample, isKnownBinaryExtension, MIME_SAMPLE_SIZE } from "./mime.js";
|
|
59
|
+
import { hasUtf16Bom, isBinarySample, isKnownBinaryExtension, MIME_SAMPLE_SIZE } from "./mime.js";
|
|
58
60
|
import { Logger } from "./observability.js";
|
|
59
61
|
import { getMaxTextFileSize } from "./util.js";
|
|
60
62
|
const STREAM_CHUNK_SIZE = 64 * 1024;
|
|
@@ -87,11 +89,6 @@ async function readProbe(handle, signal) {
|
|
|
87
89
|
}
|
|
88
90
|
return buffer.subarray(0, bytesRead);
|
|
89
91
|
}
|
|
90
|
-
async function isProbablyBinary(filePath, handle, signal) {
|
|
91
|
-
if (isKnownBinaryExtension(filePath))
|
|
92
|
-
return true;
|
|
93
|
-
return isBinarySample(await readProbe(handle, signal));
|
|
94
|
-
}
|
|
95
92
|
export function createTooLargeError(bytesRead, maxSize, requestedPath) {
|
|
96
93
|
return new FsError(ErrorCode.TOO_LARGE, `File exceeds size limit (${bytesRead} > ${maxSize} bytes)`, requestedPath);
|
|
97
94
|
}
|
|
@@ -348,17 +345,31 @@ async function readTailContent(handle, tail, options, filePath) {
|
|
|
348
345
|
hasMoreLines,
|
|
349
346
|
};
|
|
350
347
|
}
|
|
351
|
-
async function readFullContent(handle, maxSize, requestedPath, signal) {
|
|
348
|
+
async function readFullContent(handle, maxSize, requestedPath, signal, strictUtf8) {
|
|
352
349
|
const buffer = await readFileBufferWithLimit(handle, maxSize, requestedPath, signal);
|
|
350
|
+
// The binary probe samples only the first 512 bytes. A caller that writes the
|
|
351
|
+
// text back must refuse any byte the decode would replace with U+FFFD.
|
|
352
|
+
if (strictUtf8 && (!isUtf8(buffer) || buffer.includes(0))) {
|
|
353
|
+
throw new FsError(ErrorCode.INVALID_INPUT, 'Binary or non-UTF-8 file detected.', requestedPath);
|
|
354
|
+
}
|
|
353
355
|
const content = buffer.toString('utf-8');
|
|
354
356
|
return { content, totalLines: countLines(content) };
|
|
355
357
|
}
|
|
356
358
|
async function assertNotBinary(validPath, filePath, handle, normalized) {
|
|
357
359
|
normalized.signal?.throwIfAborted();
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
360
|
+
if (isKnownBinaryExtension(validPath)) {
|
|
361
|
+
throw new FsError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);
|
|
362
|
+
}
|
|
363
|
+
const probe = await readProbe(handle, normalized.signal);
|
|
364
|
+
// A UTF-16 BOM passes the binary probe (it is text), but every reader
|
|
365
|
+
// here (read and edit) decodes UTF-8 and would hand back U+FFFD and NULs
|
|
366
|
+
// as content.
|
|
367
|
+
if (hasUtf16Bom(probe)) {
|
|
368
|
+
throw new FsError(ErrorCode.INVALID_INPUT, 'UTF-16 text file detected; convert it to UTF-8 first.', filePath);
|
|
369
|
+
}
|
|
370
|
+
if (isBinarySample(probe)) {
|
|
371
|
+
throw new FsError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);
|
|
372
|
+
}
|
|
362
373
|
}
|
|
363
374
|
function assertSizeWithinLimit(size, maxSize, filePath) {
|
|
364
375
|
if (size <= maxSize)
|
|
@@ -369,7 +380,7 @@ async function readByMode(handle, validPath, filePath, stats, spec, options) {
|
|
|
369
380
|
switch (spec.kind) {
|
|
370
381
|
case 'full': {
|
|
371
382
|
assertSizeWithinLimit(stats.size, options.maxSize, filePath);
|
|
372
|
-
const { content, totalLines } = await readFullContent(handle, options.maxSize, filePath, options.signal);
|
|
383
|
+
const { content, totalLines } = await readFullContent(handle, options.maxSize, filePath, options.signal, spec.strictUtf8 === true);
|
|
373
384
|
return {
|
|
374
385
|
path: validPath,
|
|
375
386
|
readMode: 'full',
|
package/dist/core/read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/core/read.ts"],"names":[],"mappings":"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","sourcesContent":["// File reading pipeline split out of fs.ts. fs.ts imports from here; never the reverse.\nimport type { Stats } from 'node:fs';\nimport { open as fsOpen } from 'node:fs/promises';\nimport type { FileHandle } from 'node:fs/promises';\nimport { StringDecoder } from 'node:string_decoder';\n\nimport { withAbort } from './concurrency.ts';\nimport { ErrorCode, formatUnknownErrorMessage, FsError, isFsError } from './errors.ts';\nimport { isBinarySample, isKnownBinaryExtension, MIME_SAMPLE_SIZE } from './mime.ts';\nimport { Logger } from './observability.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\nconst STREAM_CHUNK_SIZE = 64 * 1024;\n\nconst READ_ONLY_FILE_FLAG = 'r';\n\nasync function openReadableFileHandle(filePath: string, signal?: AbortSignal): Promise<FileHandle> {\n const handlePromise = fsOpen(filePath, READ_ONLY_FILE_FLAG);\n if (!signal) return handlePromise;\n try {\n return await withAbort(handlePromise, signal);\n } catch (error) {\n void handlePromise\n .then((handle) => {\n void handle.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close file handle for ${filePath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n })\n .catch(() => {\n /* ignore open error */\n });\n throw error;\n }\n}\n\nasync function readProbe(handle: FileHandle, signal?: AbortSignal): Promise<Buffer> {\n const buffer = Buffer.allocUnsafe(MIME_SAMPLE_SIZE);\n const { bytesRead } = await withAbort(handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0), signal);\n\n if (bytesRead === 0) {\n return Buffer.alloc(0);\n }\n\n return buffer.subarray(0, bytesRead);\n}\n\nasync function isProbablyBinary(\n filePath: string,\n handle: FileHandle,\n signal?: AbortSignal,\n): Promise<boolean> {\n if (isKnownBinaryExtension(filePath)) return true;\n return isBinarySample(await readProbe(handle, signal));\n}\n\nexport type ReadSpec =\n | { kind: 'full'; signal?: AbortSignal }\n | { kind: 'head'; lines: number; signal?: AbortSignal }\n | { kind: 'tail'; lines: number; signal?: AbortSignal }\n | { kind: 'range'; start: number; end?: number; signal?: AbortSignal };\n\ninterface ReadOptions {\n maxSize: number;\n signal?: AbortSignal;\n}\n\ninterface PartialReadResult {\n content: string;\n linesRead: number;\n hasMoreLines: boolean;\n}\n\nexport interface ReadFileResult {\n path: string;\n content: string;\n totalLines?: number;\n readMode: ReadSpec['kind'];\n head?: number;\n tail?: number;\n startLine?: number;\n endLine?: number;\n linesRead?: number;\n hasMoreLines?: boolean;\n}\n\nexport function createTooLargeError(\n bytesRead: number,\n maxSize: number,\n requestedPath: string,\n): FsError {\n return new FsError(\n ErrorCode.TOO_LARGE,\n `File exceeds size limit (${bytesRead} > ${maxSize} bytes)`,\n requestedPath,\n );\n}\n\nexport async function readFileBufferWithLimit(\n handle: FileHandle,\n maxSize: number,\n requestedPath: string,\n signal?: AbortSignal,\n): Promise<Buffer> {\n const stream = handle.createReadStream({\n start: 0,\n highWaterMark: STREAM_CHUNK_SIZE,\n autoClose: false,\n emitClose: false,\n signal,\n });\n\n const chunks: Buffer[] = [];\n let totalSize = 0;\n\n try {\n for await (const chunk of stream) {\n const buffer = chunk as Buffer;\n totalSize += buffer.length;\n\n if (totalSize > maxSize) {\n stream.destroy();\n throw createTooLargeError(totalSize, maxSize, requestedPath);\n }\n\n chunks.push(buffer);\n }\n } finally {\n if (!stream.destroyed) {\n stream.on('error', (_err: unknown) => {\n /* suppress post-destroy error event */\n });\n stream.destroy();\n }\n }\n\n return Buffer.concat(chunks, totalSize);\n}\n\nexport const countLines = (content: string): number => {\n if (content.length === 0) return 0;\n const trimmed = content.endsWith('\\n') ? content.slice(0, -1) : content;\n return trimmed.split('\\n').length;\n};\n\nasync function peekHasMore(iterator: AsyncIterator<string>): Promise<boolean> {\n try {\n const { done } = await iterator.next();\n return !done;\n } catch (error) {\n // The peek only asks \"is there another line\", and the caller keeps nothing\n // it returns. An over-long line past the requested range still answers yes\n // — failing the whole read over a line outside it would reject e.g. lines\n // 1-2 of a file whose line 3 is one 50 KB blob.\n if (isFsError(error) && error.code === ErrorCode.TOO_LARGE) return true;\n throw error;\n }\n}\n\nconst LF = 0x0a;\n\n/**\n * Line iterator bounded by bytes rather than by the decoded string, so a file\n * that is one enormous line cannot be materialized before the size check runs.\n * `handle.readLines` decodes the whole line first and can OOM on such a file.\n *\n * Yields lines from `startLine` onward, 1-indexed. Lines before `startLine` are\n * counted and discarded without accumulating, so an over-long line the caller\n * skipped past does not fail the read. Line endings match `readLines`: split on\n * `\\n`, with a preceding `\\r` stripped, so CRLF input decodes identically.\n */\nasync function* readLinesBounded(\n handle: FileHandle,\n options: ReadOptions,\n filePath: string,\n startLine: number,\n): AsyncGenerator<string, void, undefined> {\n const stream = handle.createReadStream({\n start: 0,\n highWaterMark: STREAM_CHUNK_SIZE,\n autoClose: false,\n emitClose: false,\n ...(options.signal ? { signal: options.signal } : {}),\n });\n\n const decoder = new StringDecoder('utf-8');\n let pending = '';\n let pendingBytes = 0;\n let lineNumber = 0;\n\n const tooLong = (bytes: number): FsError =>\n new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (single line ${bytes} > ${options.maxSize} bytes). Use a narrower range or head.`,\n filePath,\n );\n\n try {\n for await (const chunk of stream) {\n const buffer = chunk as Buffer;\n let offset = 0;\n\n // Search loop: each iteration finds the next LF and advances offset past\n // it. Written as while-with-assignment so the re-search is visible, rather\n // than buried in a for \"increment\" slot that re-runs indexOf.\n let nl = buffer.indexOf(LF, offset);\n while (nl !== -1) {\n const segment = buffer.subarray(offset, nl);\n const lineBytes = pendingBytes + segment.length;\n lineNumber++;\n // Decode even when skipping: the decoder carries multi-byte\n // continuation state into the first line the caller keeps.\n const decoded = decoder.write(segment);\n if (lineNumber >= startLine) {\n if (lineBytes > options.maxSize) throw tooLong(lineBytes);\n yield (pending + decoded).replace(/\\r$/, '');\n }\n pending = '';\n pendingBytes = 0;\n offset = nl + 1;\n nl = buffer.indexOf(LF, offset);\n }\n\n const rest = buffer.subarray(offset);\n pendingBytes += rest.length;\n if (lineNumber + 1 < startLine) {\n decoder.write(rest); // still skipping; drop the text, keep decoder state\n } else {\n if (pendingBytes > options.maxSize) throw tooLong(pendingBytes);\n pending += decoder.write(rest);\n }\n }\n\n // Trailing line with no final newline. A file ending in `\\n` leaves\n // pendingBytes at 0 and emits nothing, matching readLines.\n if (pendingBytes > 0) {\n lineNumber++;\n if (lineNumber >= startLine) {\n if (pendingBytes > options.maxSize) throw tooLong(pendingBytes);\n yield pending + decoder.end();\n }\n }\n } finally {\n if (!stream.destroyed) {\n stream.on('error', (_err: unknown) => {\n /* suppress post-destroy error event */\n });\n stream.destroy();\n }\n }\n}\n\nasync function readRangeContent(\n handle: FileHandle,\n startLine: number,\n endLine: number | undefined,\n options: ReadOptions,\n filePath: string,\n): Promise<PartialReadResult> {\n options.signal?.throwIfAborted();\n\n const lines: string[] = [];\n let lineNumber = startLine - 1;\n let estimatedBytes = 0;\n const newlineBytes = Buffer.byteLength('\\n', 'utf-8');\n const stopAt = endLine ?? Number.POSITIVE_INFINITY;\n\n let hasMoreLines = false;\n\n // Byte-bounded: an over-long line throws TOO_LARGE while still a partial\n // buffer, before it can be decoded into one huge string.\n const iterator = readLinesBounded(handle, options, filePath, startLine)[Symbol.asyncIterator]();\n\n try {\n for (;;) {\n const { value: line, done } = await iterator.next();\n if (done) {\n break;\n }\n lineNumber++;\n\n if (lineNumber > stopAt) {\n hasMoreLines = true;\n break;\n }\n\n lines.push(line);\n\n estimatedBytes += Buffer.byteLength(line, 'utf-8') + newlineBytes;\n if (estimatedBytes > options.maxSize) {\n hasMoreLines = await peekHasMore(iterator);\n break;\n }\n\n if (lineNumber === stopAt) {\n hasMoreLines = await peekHasMore(iterator);\n break;\n }\n }\n } finally {\n await iterator.return();\n }\n\n return {\n content: lines.join('\\n'),\n linesRead: lines.length,\n hasMoreLines,\n };\n}\n\nasync function readTailContent(\n handle: FileHandle,\n tail: number,\n options: ReadOptions,\n filePath: string,\n): Promise<PartialReadResult> {\n options.signal?.throwIfAborted();\n\n const stats = await handle.stat();\n const fileSize = stats.size;\n if (fileSize === 0) {\n return {\n content: '',\n linesRead: 0,\n hasMoreLines: false,\n };\n }\n\n const encoding = 'utf-8';\n // Accumulate raw bytes from the end: decoding a chunk ending mid-codepoint\n // corrupts the trailing UTF-8 sequence into U+FFFD. 0x0A is single-byte and\n // never part of a multibyte sequence, so count newlines on the raw buffer and\n // decode the whole bounded buffer once at the end.\n let position = fileSize;\n const chunks: Buffer[] = [];\n let totalLen = 0;\n let newlines = 0;\n let stoppedByLimit = false;\n\n while (position > 0) {\n options.signal?.throwIfAborted();\n const chunkSize = Math.min(position, STREAM_CHUNK_SIZE);\n const buffer = Buffer.allocUnsafe(chunkSize);\n const { bytesRead } = await handle.read(buffer, 0, chunkSize, position - chunkSize);\n if (bytesRead < chunkSize) {\n // The file shrank under us. Bytes from `position` on are still a\n // contiguous suffix, so keep those and stop — splicing this short chunk in\n // would leave a hole (and, with an alloc'd buffer, NULs) mid-content.\n // `position` stays put, so the result is reported as having more lines.\n break;\n }\n position -= chunkSize;\n\n for (const byte of buffer) {\n if (byte === 0x0a) newlines++;\n }\n\n chunks.unshift(buffer);\n totalLen += chunkSize;\n\n // One newline more than `tail`: the oldest segment in the buffer starts\n // mid-line whenever we stop before the file start, and gets dropped below.\n if (newlines > tail) break;\n if (totalLen > options.maxSize) {\n stoppedByLimit = true;\n break;\n }\n }\n\n const allBytes = Buffer.concat(chunks, totalLen);\n let lines = allBytes.toString(encoding).split('\\n');\n\n // A trailing newline produces a spurious empty final segment; drop it.\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines = lines.slice(0, -1);\n }\n // Normalize CRLF.\n lines = lines.map((l) => (l.endsWith('\\r') ? l.slice(0, -1) : l));\n\n // We stopped before the file start, so the first segment is the tail of a\n // line whose beginning was never read. Drop it instead of reporting a\n // fragment as a complete line.\n if (position > 0 && lines.length > 0) {\n lines = lines.slice(1);\n }\n\n if (stoppedByLimit && lines.length < tail) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (${totalLen} > ${options.maxSize} bytes, could not collect ${tail} lines). Use a narrower tail or head.`,\n filePath,\n );\n }\n\n let hasMoreLines = stoppedByLimit || position > 0;\n if (lines.length > tail) {\n hasMoreLines = true;\n lines = lines.slice(lines.length - tail);\n }\n\n const content = lines.join('\\n');\n return {\n content,\n linesRead: lines.length,\n hasMoreLines,\n };\n}\n\nasync function readFullContent(\n handle: FileHandle,\n maxSize: number,\n requestedPath: string,\n signal?: AbortSignal,\n): Promise<{ content: string; totalLines: number }> {\n const buffer = await readFileBufferWithLimit(handle, maxSize, requestedPath, signal);\n const content = buffer.toString('utf-8');\n return { content, totalLines: countLines(content) };\n}\n\nasync function assertNotBinary(\n validPath: string,\n filePath: string,\n handle: FileHandle,\n normalized: ReadOptions,\n): Promise<void> {\n normalized.signal?.throwIfAborted();\n const isBinary = await isProbablyBinary(validPath, handle, normalized.signal);\n if (!isBinary) return;\n throw new FsError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);\n}\n\nfunction assertSizeWithinLimit(size: number, maxSize: number, filePath: string): void {\n if (size <= maxSize) return;\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (${size} > ${maxSize} bytes). Use head to preview.`,\n filePath,\n );\n}\n\nasync function readByMode(\n handle: FileHandle,\n validPath: string,\n filePath: string,\n stats: Stats,\n spec: ReadSpec,\n options: ReadOptions,\n): Promise<ReadFileResult> {\n switch (spec.kind) {\n case 'full': {\n assertSizeWithinLimit(stats.size, options.maxSize, filePath);\n const { content, totalLines } = await readFullContent(\n handle,\n options.maxSize,\n filePath,\n options.signal,\n );\n return {\n path: validPath,\n readMode: 'full',\n content,\n totalLines,\n linesRead: totalLines,\n hasMoreLines: false,\n };\n }\n case 'head':\n return {\n path: validPath,\n readMode: 'head',\n head: spec.lines,\n ...(await readRangeContent(handle, 1, spec.lines, options, filePath)),\n };\n case 'range':\n return {\n path: validPath,\n readMode: 'range',\n startLine: spec.start,\n ...(spec.end !== undefined ? { endLine: spec.end } : {}),\n ...(await readRangeContent(handle, spec.start, spec.end, options, filePath)),\n };\n case 'tail':\n return {\n path: validPath,\n readMode: 'tail',\n tail: spec.lines,\n ...(await readTailContent(handle, spec.lines, options, validPath)),\n };\n }\n}\n\nexport function assertFileStats(filePath: string, stats: Stats): void {\n if (!stats.isFile()) {\n throw new FsError(ErrorCode.NOT_FILE, 'Not a regular file', filePath);\n }\n}\n\n/** Read an already-validated, already-stat'd file per `spec`. */\nexport async function readFileWithStats(\n filePath: string,\n validPath: string,\n stats: Stats,\n spec: ReadSpec,\n): Promise<ReadFileResult> {\n const options: ReadOptions = {\n maxSize: getMaxTextFileSize(),\n ...(spec.signal ? { signal: spec.signal } : {}),\n };\n spec.signal?.throwIfAborted();\n assertFileStats(filePath, stats);\n\n await using handle = await openReadableFileHandle(validPath, spec.signal);\n\n await assertNotBinary(validPath, filePath, handle, options);\n spec.signal?.throwIfAborted();\n\n return await readByMode(handle, validPath, filePath, stats, spec, options);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/core/read.ts"],"names":[],"mappings":"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","sourcesContent":["// File reading pipeline split out of fs.ts. fs.ts imports from here; never the reverse.\nimport { isUtf8 } from 'node:buffer';\nimport type { Stats } from 'node:fs';\nimport { open as fsOpen } from 'node:fs/promises';\nimport type { FileHandle } from 'node:fs/promises';\nimport { StringDecoder } from 'node:string_decoder';\n\nimport { withAbort } from './concurrency.ts';\nimport { ErrorCode, formatUnknownErrorMessage, FsError, isFsError } from './errors.ts';\nimport { hasUtf16Bom, isBinarySample, isKnownBinaryExtension, MIME_SAMPLE_SIZE } from './mime.ts';\nimport { Logger } from './observability.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\nconst STREAM_CHUNK_SIZE = 64 * 1024;\n\nconst READ_ONLY_FILE_FLAG = 'r';\n\nasync function openReadableFileHandle(filePath: string, signal?: AbortSignal): Promise<FileHandle> {\n const handlePromise = fsOpen(filePath, READ_ONLY_FILE_FLAG);\n if (!signal) return handlePromise;\n try {\n return await withAbort(handlePromise, signal);\n } catch (error) {\n void handlePromise\n .then((handle) => {\n void handle.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close file handle for ${filePath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n })\n .catch(() => {\n /* ignore open error */\n });\n throw error;\n }\n}\n\nasync function readProbe(handle: FileHandle, signal?: AbortSignal): Promise<Buffer> {\n const buffer = Buffer.allocUnsafe(MIME_SAMPLE_SIZE);\n const { bytesRead } = await withAbort(handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0), signal);\n\n if (bytesRead === 0) {\n return Buffer.alloc(0);\n }\n\n return buffer.subarray(0, bytesRead);\n}\n\nexport type ReadSpec =\n | { kind: 'full'; signal?: AbortSignal; strictUtf8?: boolean }\n | { kind: 'head'; lines: number; signal?: AbortSignal }\n | { kind: 'tail'; lines: number; signal?: AbortSignal }\n | { kind: 'range'; start: number; end?: number; signal?: AbortSignal };\n\ninterface ReadOptions {\n maxSize: number;\n signal?: AbortSignal;\n}\n\ninterface PartialReadResult {\n content: string;\n linesRead: number;\n hasMoreLines: boolean;\n}\n\nexport interface ReadFileResult {\n path: string;\n content: string;\n totalLines?: number;\n readMode: ReadSpec['kind'];\n head?: number;\n tail?: number;\n startLine?: number;\n endLine?: number;\n linesRead?: number;\n hasMoreLines?: boolean;\n}\n\nexport function createTooLargeError(\n bytesRead: number,\n maxSize: number,\n requestedPath: string,\n): FsError {\n return new FsError(\n ErrorCode.TOO_LARGE,\n `File exceeds size limit (${bytesRead} > ${maxSize} bytes)`,\n requestedPath,\n );\n}\n\nexport async function readFileBufferWithLimit(\n handle: FileHandle,\n maxSize: number,\n requestedPath: string,\n signal?: AbortSignal,\n): Promise<Buffer> {\n const stream = handle.createReadStream({\n start: 0,\n highWaterMark: STREAM_CHUNK_SIZE,\n autoClose: false,\n emitClose: false,\n signal,\n });\n\n const chunks: Buffer[] = [];\n let totalSize = 0;\n\n try {\n for await (const chunk of stream) {\n const buffer = chunk as Buffer;\n totalSize += buffer.length;\n\n if (totalSize > maxSize) {\n stream.destroy();\n throw createTooLargeError(totalSize, maxSize, requestedPath);\n }\n\n chunks.push(buffer);\n }\n } finally {\n if (!stream.destroyed) {\n stream.on('error', (_err: unknown) => {\n /* suppress post-destroy error event */\n });\n stream.destroy();\n }\n }\n\n return Buffer.concat(chunks, totalSize);\n}\n\nexport const countLines = (content: string): number => {\n if (content.length === 0) return 0;\n const trimmed = content.endsWith('\\n') ? content.slice(0, -1) : content;\n return trimmed.split('\\n').length;\n};\n\nasync function peekHasMore(iterator: AsyncIterator<string>): Promise<boolean> {\n try {\n const { done } = await iterator.next();\n return !done;\n } catch (error) {\n // The peek only asks \"is there another line\", and the caller keeps nothing\n // it returns. An over-long line past the requested range still answers yes\n // — failing the whole read over a line outside it would reject e.g. lines\n // 1-2 of a file whose line 3 is one 50 KB blob.\n if (isFsError(error) && error.code === ErrorCode.TOO_LARGE) return true;\n throw error;\n }\n}\n\nconst LF = 0x0a;\n\n/**\n * Line iterator bounded by bytes rather than by the decoded string, so a file\n * that is one enormous line cannot be materialized before the size check runs.\n * `handle.readLines` decodes the whole line first and can OOM on such a file.\n *\n * Yields lines from `startLine` onward, 1-indexed. Lines before `startLine` are\n * counted and discarded without accumulating, so an over-long line the caller\n * skipped past does not fail the read. Line endings match `readLines`: split on\n * `\\n`, with a preceding `\\r` stripped, so CRLF input decodes identically.\n */\nasync function* readLinesBounded(\n handle: FileHandle,\n options: ReadOptions,\n filePath: string,\n startLine: number,\n): AsyncGenerator<string, void, undefined> {\n const stream = handle.createReadStream({\n start: 0,\n highWaterMark: STREAM_CHUNK_SIZE,\n autoClose: false,\n emitClose: false,\n ...(options.signal ? { signal: options.signal } : {}),\n });\n\n const decoder = new StringDecoder('utf-8');\n let pending = '';\n let pendingBytes = 0;\n let lineNumber = 0;\n\n const tooLong = (bytes: number): FsError =>\n new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (single line ${bytes} > ${options.maxSize} bytes). Use a narrower range or head.`,\n filePath,\n );\n\n try {\n for await (const chunk of stream) {\n const buffer = chunk as Buffer;\n let offset = 0;\n\n // Search loop: each iteration finds the next LF and advances offset past\n // it. Written as while-with-assignment so the re-search is visible, rather\n // than buried in a for \"increment\" slot that re-runs indexOf.\n let nl = buffer.indexOf(LF, offset);\n while (nl !== -1) {\n const segment = buffer.subarray(offset, nl);\n const lineBytes = pendingBytes + segment.length;\n lineNumber++;\n // Decode even when skipping: the decoder carries multi-byte\n // continuation state into the first line the caller keeps.\n const decoded = decoder.write(segment);\n if (lineNumber >= startLine) {\n if (lineBytes > options.maxSize) throw tooLong(lineBytes);\n yield (pending + decoded).replace(/\\r$/, '');\n }\n pending = '';\n pendingBytes = 0;\n offset = nl + 1;\n nl = buffer.indexOf(LF, offset);\n }\n\n const rest = buffer.subarray(offset);\n pendingBytes += rest.length;\n if (lineNumber + 1 < startLine) {\n decoder.write(rest); // still skipping; drop the text, keep decoder state\n } else {\n if (pendingBytes > options.maxSize) throw tooLong(pendingBytes);\n pending += decoder.write(rest);\n }\n }\n\n // Trailing line with no final newline. A file ending in `\\n` leaves\n // pendingBytes at 0 and emits nothing, matching readLines.\n if (pendingBytes > 0) {\n lineNumber++;\n if (lineNumber >= startLine) {\n if (pendingBytes > options.maxSize) throw tooLong(pendingBytes);\n yield pending + decoder.end();\n }\n }\n } finally {\n if (!stream.destroyed) {\n stream.on('error', (_err: unknown) => {\n /* suppress post-destroy error event */\n });\n stream.destroy();\n }\n }\n}\n\nasync function readRangeContent(\n handle: FileHandle,\n startLine: number,\n endLine: number | undefined,\n options: ReadOptions,\n filePath: string,\n): Promise<PartialReadResult> {\n options.signal?.throwIfAborted();\n\n const lines: string[] = [];\n let lineNumber = startLine - 1;\n let estimatedBytes = 0;\n const newlineBytes = Buffer.byteLength('\\n', 'utf-8');\n const stopAt = endLine ?? Number.POSITIVE_INFINITY;\n\n let hasMoreLines = false;\n\n // Byte-bounded: an over-long line throws TOO_LARGE while still a partial\n // buffer, before it can be decoded into one huge string.\n const iterator = readLinesBounded(handle, options, filePath, startLine)[Symbol.asyncIterator]();\n\n try {\n for (;;) {\n const { value: line, done } = await iterator.next();\n if (done) {\n break;\n }\n lineNumber++;\n\n if (lineNumber > stopAt) {\n hasMoreLines = true;\n break;\n }\n\n lines.push(line);\n\n estimatedBytes += Buffer.byteLength(line, 'utf-8') + newlineBytes;\n if (estimatedBytes > options.maxSize) {\n hasMoreLines = await peekHasMore(iterator);\n break;\n }\n\n if (lineNumber === stopAt) {\n hasMoreLines = await peekHasMore(iterator);\n break;\n }\n }\n } finally {\n await iterator.return();\n }\n\n return {\n content: lines.join('\\n'),\n linesRead: lines.length,\n hasMoreLines,\n };\n}\n\nasync function readTailContent(\n handle: FileHandle,\n tail: number,\n options: ReadOptions,\n filePath: string,\n): Promise<PartialReadResult> {\n options.signal?.throwIfAborted();\n\n const stats = await handle.stat();\n const fileSize = stats.size;\n if (fileSize === 0) {\n return {\n content: '',\n linesRead: 0,\n hasMoreLines: false,\n };\n }\n\n const encoding = 'utf-8';\n // Accumulate raw bytes from the end: decoding a chunk ending mid-codepoint\n // corrupts the trailing UTF-8 sequence into U+FFFD. 0x0A is single-byte and\n // never part of a multibyte sequence, so count newlines on the raw buffer and\n // decode the whole bounded buffer once at the end.\n let position = fileSize;\n const chunks: Buffer[] = [];\n let totalLen = 0;\n let newlines = 0;\n let stoppedByLimit = false;\n\n while (position > 0) {\n options.signal?.throwIfAborted();\n const chunkSize = Math.min(position, STREAM_CHUNK_SIZE);\n const buffer = Buffer.allocUnsafe(chunkSize);\n const { bytesRead } = await handle.read(buffer, 0, chunkSize, position - chunkSize);\n if (bytesRead < chunkSize) {\n // The file shrank under us. Bytes from `position` on are still a\n // contiguous suffix, so keep those and stop — splicing this short chunk in\n // would leave a hole (and, with an alloc'd buffer, NULs) mid-content.\n // `position` stays put, so the result is reported as having more lines.\n break;\n }\n position -= chunkSize;\n\n for (const byte of buffer) {\n if (byte === 0x0a) newlines++;\n }\n\n chunks.unshift(buffer);\n totalLen += chunkSize;\n\n // One newline more than `tail`: the oldest segment in the buffer starts\n // mid-line whenever we stop before the file start, and gets dropped below.\n if (newlines > tail) break;\n if (totalLen > options.maxSize) {\n stoppedByLimit = true;\n break;\n }\n }\n\n const allBytes = Buffer.concat(chunks, totalLen);\n let lines = allBytes.toString(encoding).split('\\n');\n\n // A trailing newline produces a spurious empty final segment; drop it.\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines = lines.slice(0, -1);\n }\n // Normalize CRLF.\n lines = lines.map((l) => (l.endsWith('\\r') ? l.slice(0, -1) : l));\n\n // We stopped before the file start, so the first segment is the tail of a\n // line whose beginning was never read. Drop it instead of reporting a\n // fragment as a complete line.\n if (position > 0 && lines.length > 0) {\n lines = lines.slice(1);\n }\n\n if (stoppedByLimit && lines.length < tail) {\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (${totalLen} > ${options.maxSize} bytes, could not collect ${tail} lines). Use a narrower tail or head.`,\n filePath,\n );\n }\n\n let hasMoreLines = stoppedByLimit || position > 0;\n if (lines.length > tail) {\n hasMoreLines = true;\n lines = lines.slice(lines.length - tail);\n }\n\n const content = lines.join('\\n');\n return {\n content,\n linesRead: lines.length,\n hasMoreLines,\n };\n}\n\nasync function readFullContent(\n handle: FileHandle,\n maxSize: number,\n requestedPath: string,\n signal: AbortSignal | undefined,\n strictUtf8: boolean,\n): Promise<{ content: string; totalLines: number }> {\n const buffer = await readFileBufferWithLimit(handle, maxSize, requestedPath, signal);\n // The binary probe samples only the first 512 bytes. A caller that writes the\n // text back must refuse any byte the decode would replace with U+FFFD.\n if (strictUtf8 && (!isUtf8(buffer) || buffer.includes(0))) {\n throw new FsError(ErrorCode.INVALID_INPUT, 'Binary or non-UTF-8 file detected.', requestedPath);\n }\n const content = buffer.toString('utf-8');\n return { content, totalLines: countLines(content) };\n}\n\nasync function assertNotBinary(\n validPath: string,\n filePath: string,\n handle: FileHandle,\n normalized: ReadOptions,\n): Promise<void> {\n normalized.signal?.throwIfAborted();\n if (isKnownBinaryExtension(validPath)) {\n throw new FsError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);\n }\n const probe = await readProbe(handle, normalized.signal);\n // A UTF-16 BOM passes the binary probe (it is text), but every reader\n // here (read and edit) decodes UTF-8 and would hand back U+FFFD and NULs\n // as content.\n if (hasUtf16Bom(probe)) {\n throw new FsError(\n ErrorCode.INVALID_INPUT,\n 'UTF-16 text file detected; convert it to UTF-8 first.',\n filePath,\n );\n }\n if (isBinarySample(probe)) {\n throw new FsError(ErrorCode.INVALID_INPUT, 'Binary file detected.', filePath);\n }\n}\n\nfunction assertSizeWithinLimit(size: number, maxSize: number, filePath: string): void {\n if (size <= maxSize) return;\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large (${size} > ${maxSize} bytes). Use head to preview.`,\n filePath,\n );\n}\n\nasync function readByMode(\n handle: FileHandle,\n validPath: string,\n filePath: string,\n stats: Stats,\n spec: ReadSpec,\n options: ReadOptions,\n): Promise<ReadFileResult> {\n switch (spec.kind) {\n case 'full': {\n assertSizeWithinLimit(stats.size, options.maxSize, filePath);\n const { content, totalLines } = await readFullContent(\n handle,\n options.maxSize,\n filePath,\n options.signal,\n spec.strictUtf8 === true,\n );\n return {\n path: validPath,\n readMode: 'full',\n content,\n totalLines,\n linesRead: totalLines,\n hasMoreLines: false,\n };\n }\n case 'head':\n return {\n path: validPath,\n readMode: 'head',\n head: spec.lines,\n ...(await readRangeContent(handle, 1, spec.lines, options, filePath)),\n };\n case 'range':\n return {\n path: validPath,\n readMode: 'range',\n startLine: spec.start,\n ...(spec.end !== undefined ? { endLine: spec.end } : {}),\n ...(await readRangeContent(handle, spec.start, spec.end, options, filePath)),\n };\n case 'tail':\n return {\n path: validPath,\n readMode: 'tail',\n tail: spec.lines,\n ...(await readTailContent(handle, spec.lines, options, validPath)),\n };\n }\n}\n\nexport function assertFileStats(filePath: string, stats: Stats): void {\n if (!stats.isFile()) {\n throw new FsError(ErrorCode.NOT_FILE, 'Not a regular file', filePath);\n }\n}\n\n/** Read an already-validated, already-stat'd file per `spec`. */\nexport async function readFileWithStats(\n filePath: string,\n validPath: string,\n stats: Stats,\n spec: ReadSpec,\n): Promise<ReadFileResult> {\n const options: ReadOptions = {\n maxSize: getMaxTextFileSize(),\n ...(spec.signal ? { signal: spec.signal } : {}),\n };\n spec.signal?.throwIfAborted();\n assertFileStats(filePath, stats);\n\n await using handle = await openReadableFileHandle(validPath, spec.signal);\n\n await assertNotBinary(validPath, filePath, handle, options);\n spec.signal?.throwIfAborted();\n\n return await readByMode(handle, validPath, filePath, stats, spec, options);\n}\n"]}
|
package/dist/core/schema.d.ts
CHANGED
|
@@ -54,6 +54,16 @@ export declare const PerFileErrorSchema: z.ZodObject<{
|
|
|
54
54
|
path: z.ZodOptional<z.ZodString>;
|
|
55
55
|
suggestion: z.ZodOptional<z.ZodString>;
|
|
56
56
|
}, z.core.$strict>;
|
|
57
|
+
/**
|
|
58
|
+
* The `{ path, value, error }` envelope every per-path tool (`delete`, `edit`,
|
|
59
|
+
* `read`, `stat`, `replace_text`) wraps its results in: the requested path, the
|
|
60
|
+
* tool-specific value on success, the error detail on failure.
|
|
61
|
+
*/
|
|
62
|
+
export declare function perPathEnvelope<TValue extends z.ZodType>(value: TValue, valueDescription: string): z.ZodObject<{
|
|
63
|
+
path: z.ZodString;
|
|
64
|
+
value: z.ZodOptional<TValue>;
|
|
65
|
+
error: z.ZodOptional<typeof PerFileErrorSchema>;
|
|
66
|
+
}, z.core.$strict>;
|
|
57
67
|
/** One entry of a tool's `failures[]`: the path that failed and why. */
|
|
58
68
|
export declare const PathFailureSchema: z.ZodObject<{
|
|
59
69
|
path: z.ZodString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAG5B,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAQ9C,eAAO,MAAM,WAAW,kBAMtB,CAAC;AAIH,eAAO,MAAM,SAAS,aAG2D,CAAC;AAElF,eAAO,MAAM,SAAS,UAEoB,CAAC;AAE3C,eAAO,MAAM,WAAW,UAEe,CAAC;AAExC,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AACpD,eAAO,MAAM,QAAQ;;;;;EAAsB,CAAC;AAE5C,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AACnD,eAAO,MAAM,QAAQ;;;;;;EAAqB,CAAC;AAM3C,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,OAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"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"}
|
package/dist/core/schema.js
CHANGED
|
@@ -7,7 +7,7 @@ import { MAX_SEARCH_DEPTH } from "./util.js";
|
|
|
7
7
|
// (no AJV warning — it is a known format, unlike sha256_hex / base64url).
|
|
8
8
|
// No `id` on this or any other shared schema below: an `id` hoists the schema
|
|
9
9
|
// into `$defs` and leaves a `$ref` at every use site, which is exactly the wire
|
|
10
|
-
// weight
|
|
10
|
+
// weight the published JSON schema (tools/define.ts) leaves out.
|
|
11
11
|
export const IsoDateTime = z.iso.datetime().meta({
|
|
12
12
|
description: 'ISO 8601 UTC date-time string (e.g. 2024-01-15T12:00:00.000Z)',
|
|
13
13
|
// `format: "date-time"` already pins the value; zod's ~330-char calendar
|
|
@@ -30,7 +30,7 @@ export const PositiveInt = z
|
|
|
30
30
|
export const FileType = z.enum(ENTRY_TYPES);
|
|
31
31
|
export const FileKind = z.enum(MIME_KINDS);
|
|
32
32
|
const MAX_PATH_LENGTH = 4096;
|
|
33
|
-
const
|
|
33
|
+
const LINE_BREAK_RE = /[\n\r]/;
|
|
34
34
|
export const isBlank = (val) => val.trim().length === 0;
|
|
35
35
|
function refineSafeText(label, specific) {
|
|
36
36
|
return (val, ctx) => {
|
|
@@ -41,19 +41,21 @@ function refineSafeText(label, specific) {
|
|
|
41
41
|
: val.includes('\0')
|
|
42
42
|
? `${label} cannot contain null bytes`
|
|
43
43
|
: (specific(val) ??
|
|
44
|
-
(
|
|
45
|
-
? `${label} contains prohibited characters (newlines or shell metacharacters)`
|
|
46
|
-
: undefined));
|
|
44
|
+
(LINE_BREAK_RE.test(val) ? `${label} cannot contain line breaks` : undefined));
|
|
47
45
|
if (issue !== undefined) {
|
|
48
46
|
ctx.addIssue({ code: 'custom', message: issue, fatal: true });
|
|
49
47
|
}
|
|
50
48
|
};
|
|
51
49
|
}
|
|
50
|
+
// Only a whole `..` segment is traversal. `..` inside a segment
|
|
51
|
+
// (`[...slug]`, `v1..2.md`) is a plain file name; PathGuard re-checks
|
|
52
|
+
// containment on the resolved path regardless.
|
|
53
|
+
const hasTraversalSegment = (val) => val.split(/[\\/]/u).includes('..');
|
|
52
54
|
export const RequiredPath = z
|
|
53
55
|
.string()
|
|
54
56
|
.min(1, { message: 'Path required' })
|
|
55
57
|
.max(MAX_PATH_LENGTH, { message: `Path too long (max ${MAX_PATH_LENGTH} chars)` })
|
|
56
|
-
.superRefine(refineSafeText('Path', (val) => val
|
|
58
|
+
.superRefine(refineSafeText('Path', (val) => hasTraversalSegment(val) ? 'Directory traversal sequences ("..") are forbidden' : undefined))
|
|
57
59
|
.describe('File or directory path inside an allowed workspace root.');
|
|
58
60
|
export const OptionalPath = RequiredPath.optional();
|
|
59
61
|
export const SafeGlobPattern = z
|
|
@@ -94,6 +96,18 @@ export const PerFileErrorSchema = z.strictObject({
|
|
|
94
96
|
path: z.string().optional(),
|
|
95
97
|
suggestion: z.string().optional(),
|
|
96
98
|
});
|
|
99
|
+
/**
|
|
100
|
+
* The `{ path, value, error }` envelope every per-path tool (`delete`, `edit`,
|
|
101
|
+
* `read`, `stat`, `replace_text`) wraps its results in: the requested path, the
|
|
102
|
+
* tool-specific value on success, the error detail on failure.
|
|
103
|
+
*/
|
|
104
|
+
export function perPathEnvelope(value, valueDescription) {
|
|
105
|
+
return z.strictObject({
|
|
106
|
+
path: z.string().describe('Requested path'),
|
|
107
|
+
value: value.optional().describe(valueDescription),
|
|
108
|
+
error: PerFileErrorSchema.optional().describe('Error details; present on failure'),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
97
111
|
/** One entry of a tool's `failures[]`: the path that failed and why. */
|
|
98
112
|
export const PathFailureSchema = z.strictObject({
|
|
99
113
|
path: z.string(),
|