file-entry-cache 11.0.0-beta.1 → 11.0.0-beta.3

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 CHANGED
@@ -15,7 +15,7 @@
15
15
  - Ideal for processes that work on a specific set of files
16
16
  - Persists cache to Disk via `reconcile()` or `persistInterval` on `cache` options.
17
17
  - Uses `checksum` to determine if a file has changed
18
- - Supports `relative` and `absolute` paths - paths are stored exactly as provided
18
+ - Supports `relative` and `absolute` paths with configurable current working directory
19
19
  - Portable cache files when using relative paths
20
20
  - ESM and CommonJS support with Typescript
21
21
 
@@ -23,11 +23,17 @@
23
23
 
24
24
  - [Installation](#installation)
25
25
  - [Getting Started](#getting-started)
26
+ - [Changes from v10 to v11](#changes-from-v10-to-v11)
26
27
  - [Changes from v9 to v10](#changes-from-v9-to-v10)
27
28
  - [Global Default Functions](#global-default-functions)
28
29
  - [FileEntryCache Options (FileEntryCacheOptions)](#fileentrycache-options-fileentrycacheoptions)
29
30
  - [API](#api)
30
31
  - [Get File Descriptor](#get-file-descriptor)
32
+ - [Path Handling and Current Working Directory](#path-handling-and-current-working-directory)
33
+ - [Cache Portability](#cache-portability)
34
+ - [Maximum Portability with Checksums](#maximum-portability-with-checksums)
35
+ - [Handling Project Relocations](#handling-project-relocations)
36
+ - [Path Security and Traversal Prevention](#path-security-and-traversal-prevention)
31
37
  - [Using Checksums to Determine if a File has Changed (useCheckSum)](#using-checksums-to-determine-if-a-file-has-changed-usechecksum)
32
38
  - [Setting Additional Meta Data](#setting-additional-meta-data)
33
39
  - [How to Contribute](#how-to-contribute)
@@ -78,41 +84,16 @@ let fileDescriptor = cache.getFileDescriptor('./src/file.txt');
78
84
  console.log(fileDescriptor.changed); // false as it has not changed from the saved cache.
79
85
  ```
80
86
 
81
- ## Migration Guide from v10 to v11
82
-
83
- The main breaking change is the removal of `currentWorkingDirectory`. Here's how to update your code:
84
-
85
- **Before (v10):**
86
- ```javascript
87
- const cache = new FileEntryCache({
88
- currentWorkingDirectory: '/project/root'
89
- });
90
- // This would store the key as 'src/file.js'
91
- cache.getFileDescriptor('/project/root/src/file.js');
92
- ```
93
-
94
- **After (v11):**
95
- ```javascript
96
- const cache = new FileEntryCache();
97
- // Now stores the key exactly as provided
98
- cache.getFileDescriptor('./src/file.js'); // Key: './src/file.js'
99
- cache.getFileDescriptor('/project/root/src/file.js'); // Key: '/project/root/src/file.js'
100
- ```
101
-
102
- If you were using absolute paths with `currentWorkingDirectory`, you'll need to update your code to use relative paths if you want portable cache files.
103
87
 
104
88
  # Changes from v10 to v11
105
89
 
106
90
  **BREAKING CHANGES:**
107
- - **Removed `currentWorkingDirectory`** - This option has been completely removed from the API. Paths are now stored exactly as provided (relative or absolute).
108
- - **Path handling changes** - The cache now stores paths exactly as they are provided:
109
- - Relative paths remain relative in the cache
110
- - Absolute paths remain absolute in the cache
111
- - The same file accessed with different path formats will create separate cache entries
112
- - **Renamed method** - `renameAbsolutePathKeys()` is now `renameCacheKeys()` to reflect that it works with any path format
113
- - **Simplified API** - Removed `currentWorkingDirectory` parameter from all methods including `getFileDescriptor()`, `removeEntry()`, and factory functions
91
+ - **`strictPaths` now defaults to `true`** - Path traversal protection is enabled by default for security. To restore v10 behavior, explicitly set `strictPaths: false`
114
92
 
115
- These changes make cache files portable when using relative paths, and simplify the API by removing path manipulation logic.
93
+ **NEW FEATURES:**
94
+ - **Added `cwd` option** - You can now specify a custom current working directory for resolving relative paths
95
+ - **Added `strictPaths` option** - Provides protection against path traversal attacks (enabled by default)
96
+ - **Improved cache portability** - When using relative paths with the same `cwd`, cache files are portable across different environments
116
97
 
117
98
  # Changes from v9 to v10
118
99
 
@@ -127,13 +108,15 @@ There have been many features added and changes made to the `file-entry-cache` c
127
108
  - On `FileEntryDescriptor.meta` if using typescript you need to use the `meta.data` to set additional information. This is to allow for better type checking and to avoid conflicts with the `meta` object which was `any`.
128
109
 
129
110
  # Global Default Functions
130
- - `create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean)` - Creates a new instance of the `FileEntryCache` class
131
- - `createFromFile(cachePath: string, useCheckSum?: boolean)` - Creates a new instance of the `FileEntryCache` class and loads the cache from a file.
111
+ - `create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean, cwd?: string)` - Creates a new instance of the `FileEntryCache` class
112
+ - `createFromFile(cachePath: string, useCheckSum?: boolean, cwd?: string)` - Creates a new instance of the `FileEntryCache` class and loads the cache from a file.
132
113
 
133
114
  # FileEntryCache Options (FileEntryCacheOptions)
134
115
  - `useModifiedTime?` - If `true` it will use the modified time to determine if the file has changed. Default is `true`
135
116
  - `useCheckSum?` - If `true` it will use a checksum to determine if the file has changed. Default is `false`
136
117
  - `hashAlgorithm?` - The algorithm to use for the checksum. Default is `md5` but can be any algorithm supported by `crypto.createHash`
118
+ - `cwd?` - The current working directory for resolving relative paths. Default is `process.cwd()`
119
+ - `strictPaths?` - If `true` restricts file access to within `cwd` boundaries, preventing path traversal attacks. Default is `true`
137
120
  - `cache.ttl?` - The time to live for the cache in milliseconds. Default is `0` which means no expiration
138
121
  - `cache.lruSize?` - The number of items to keep in the cache. Default is `0` which means no limit
139
122
  - `cache.useClone?` - If `true` it will clone the data before returning it. Default is `false`
@@ -150,17 +133,21 @@ There have been many features added and changes made to the `file-entry-cache` c
150
133
  - `useCheckSum: boolean` - If `true` it will use a checksum to determine if the file has changed. Default is `false`
151
134
  - `hashAlgorithm: string` - The algorithm to use for the checksum. Default is `md5` but can be any algorithm supported by `crypto.createHash`
152
135
  - `getHash(buffer: Buffer): string` - Gets the hash of a buffer used for checksums
136
+ - `cwd: string` - The current working directory for resolving relative paths. Default is `process.cwd()`
137
+ - `strictPaths: boolean` - If `true` restricts file access to within `cwd` boundaries. Default is `true`
153
138
  - `createFileKey(filePath: string): string` - Returns the cache key for the file path (returns the path exactly as provided).
154
139
  - `deleteCacheFile(): boolean` - Deletes the cache file from disk
155
140
  - `destroy(): void` - Destroys the cache. This will clear the cache in memory. If using cache persistence it will stop the interval.
156
- - `removeEntry(filePath: string): void` - Removes an entry from the cache using the exact path provided.
141
+ - `removeEntry(filePath: string): void` - Removes an entry from the cache.
157
142
  - `reconcile(): void` - Saves the cache to disk and removes any files that are no longer found.
158
143
  - `hasFileChanged(filePath: string): boolean` - Checks if the file has changed. This will return `true` if the file has changed.
159
144
  - `getFileDescriptor(filePath: string, options?: { useModifiedTime?: boolean, useCheckSum?: boolean }): FileEntryDescriptor` - Gets the file descriptor for the file. Please refer to the entire section on `Get File Descriptor` for more information.
160
- - `normalizeEntries(entries: FileEntryDescriptor[]): FileEntryDescriptor[]` - Normalizes the entries to have the correct paths. This is used when loading the cache from disk.
145
+ - `normalizeEntries(files?: string[]): FileDescriptor[]` - Normalizes the entries. If no files are provided, it will return all cached entries.
161
146
  - `analyzeFiles(files: string[])` will return `AnalyzedFiles` object with `changedFiles`, `notFoundFiles`, and `notChangedFiles` as FileDescriptor arrays.
162
147
  - `getUpdatedFiles(files: string[])` will return an array of `FileEntryDescriptor` objects that have changed.
163
148
  - `getFileDescriptorsByPath(filePath: string): FileEntryDescriptor[]` will return an array of `FileEntryDescriptor` objects that starts with the path prefix specified.
149
+ - `getAbsolutePath(filePath: string): string` - Resolves a relative path to absolute using the configured `cwd`. Returns absolute paths unchanged. When `strictPaths` is enabled, throws an error if the path resolves outside `cwd`.
150
+ - `getAbsolutePathWithCwd(filePath: string, cwd: string): string` - Resolves a relative path to absolute using a custom working directory. When `strictPaths` is enabled, throws an error if the path resolves outside the provided `cwd`.
164
151
 
165
152
  # Get File Descriptor
166
153
 
@@ -172,32 +159,94 @@ The `getFileDescriptor(filePath: string, options?: { useCheckSum?: boolean, useM
172
159
  - `meta: FileEntryMeta` - The meta data for the file. This has the following properties: `size`, `mtime`, `hash`, `data`. Note that `data` is an object that can be used to store additional information.
173
160
  - `err` - If there was an error analyzing the file.
174
161
 
175
- ## Path Handling
162
+ ## Path Handling and Current Working Directory
163
+
164
+ The cache stores paths exactly as they are provided (relative or absolute). When checking if files have changed, relative paths are resolved using the configured `cwd` (current working directory):
165
+
166
+ ```javascript
167
+ // Default: uses process.cwd()
168
+ const cache1 = fileEntryCache.create('cache1');
169
+
170
+ // Custom working directory
171
+ const cache2 = fileEntryCache.create('cache2', './cache', false, '/project/root');
172
+ // Or with options object
173
+ const cache3 = new FileEntryCache({ cwd: '/project/root' });
174
+
175
+ // The cache key is always the provided path
176
+ const descriptor = cache2.getFileDescriptor('./src/file.txt');
177
+ console.log(descriptor.key); // './src/file.txt'
178
+ // But file operations resolve from: '/project/root/src/file.txt'
179
+ ```
176
180
 
177
- The cache stores paths exactly as they are provided:
181
+ ### Cache Portability
178
182
 
179
- - **Relative paths** remain relative in the cache
180
- - **Absolute paths** remain absolute in the cache
181
- - The same file accessed with different path formats creates **separate cache entries**
183
+ Using relative paths with a consistent `cwd` (defaults to `process.cwd()`) makes cache files portable across different machines and environments. This is especially useful for CI/CD pipelines and team development.
182
184
 
183
185
  ```javascript
184
- const fileEntryCache = new FileEntryCache();
186
+ // On machine A (project at /home/user/project)
187
+ const cacheA = fileEntryCache.create('build-cache', './cache', false, '/home/user/project');
188
+ cacheA.getFileDescriptor('./src/index.js'); // Resolves to /home/user/project/src/index.js
189
+ cacheA.reconcile();
190
+
191
+ // On machine B (project at /workspace/project)
192
+ const cacheB = fileEntryCache.create('build-cache', './cache', false, '/workspace/project');
193
+ cacheB.getFileDescriptor('./src/index.js'); // Resolves to /workspace/project/src/index.js
194
+ // Cache hit! File hasn't changed since machine A
195
+ ```
185
196
 
186
- // Using a relative path
187
- const relativeDescriptor = fileEntryCache.getFileDescriptor('./file.txt');
188
- console.log(relativeDescriptor.key); // './file.txt'
197
+ ### Maximum Portability with Checksums
189
198
 
190
- // Using an absolute path
191
- const absolutePath = path.resolve('./file.txt');
192
- const absoluteDescriptor = fileEntryCache.getFileDescriptor(absolutePath);
193
- console.log(absoluteDescriptor.key); // '/full/path/to/file.txt'
199
+ For maximum cache portability across different environments, use checksums (`useCheckSum: true`) along with relative paths and `cwd` which defaults to `process.cwd()`. This ensures that cache validity is determined by file content rather than modification times, which can vary across systems:
194
200
 
195
- // These create two separate cache entries even though they refer to the same file
201
+ ```javascript
202
+ // Development machine
203
+ const devCache = fileEntryCache.create(
204
+ '.buildcache',
205
+ './cache', // cache directory
206
+ true // Use checksums for content-based comparison
207
+ );
208
+
209
+ // Process files using relative paths
210
+ const descriptor = devCache.getFileDescriptor('./src/index.js');
211
+ if (descriptor.changed) {
212
+ console.log('Building ./src/index.js...');
213
+ // Build process here
214
+ }
215
+ devCache.reconcile(); // Save cache
216
+
217
+ // CI/CD Pipeline or another developer's machine
218
+ const ciCache = fileEntryCache.create(
219
+ '.buildcache',
220
+ './node_modules/.cache',
221
+ true, // Same checksum setting
222
+ process.cwd() // Different absolute path, same relative structure
223
+ );
224
+
225
+ // Same relative path works across environments
226
+ const descriptor2 = ciCache.getFileDescriptor('./src/index.js');
227
+ if (!descriptor2.changed) {
228
+ console.log('Using cached result for ./src/index.js');
229
+ // Skip rebuild - file content unchanged
230
+ }
196
231
  ```
197
232
 
198
- This behavior makes cache files portable when using relative paths, as they will work correctly when the project is moved to a different location.
233
+ ### Handling Project Relocations
234
+
235
+ Cache remains valid even when projects are moved or renamed:
236
+
237
+ ```javascript
238
+ // Original location: /projects/my-app
239
+ const cache1 = fileEntryCache.create('.cache', './cache', true, '/projects/my-app');
240
+ cache1.getFileDescriptor('./src/app.js');
241
+ cache1.reconcile();
242
+
243
+ // After moving project to: /archived/2024/my-app
244
+ const cache2 = fileEntryCache.create('.cache', './cache', true, '/archived/2024/my-app');
245
+ cache2.getFileDescriptor('./src/app.js'); // Still finds cached entry!
246
+ // Cache valid as long as relative structure unchanged
247
+ ```
199
248
 
200
- If there is an error when trying to get the file descriptor it will return an ``notFound` and `err` property with the error.
249
+ If there is an error when trying to get the file descriptor it will return a `notFound` and `err` property with the error.
201
250
 
202
251
  ```javascript
203
252
  const fileEntryCache = new FileEntryCache();
@@ -211,6 +260,124 @@ if (fileDescriptor.notFound) {
211
260
  }
212
261
  ```
213
262
 
263
+ # Path Security and Traversal Prevention
264
+
265
+ The `strictPaths` option provides security against path traversal attacks by restricting file access to within the configured `cwd` boundaries. **This is enabled by default (since v11)** to ensure secure defaults when processing untrusted input or when running in security-sensitive environments.
266
+
267
+ ## Basic Usage
268
+
269
+ ```javascript
270
+ // strictPaths is enabled by default for security
271
+ const cache = new FileEntryCache({
272
+ cwd: '/project/root'
273
+ });
274
+
275
+ // This will work - file is within cwd
276
+ const descriptor = cache.getFileDescriptor('./src/index.js');
277
+
278
+ // This will throw an error - attempts to access parent directory
279
+ try {
280
+ cache.getFileDescriptor('../../../etc/passwd');
281
+ } catch (error) {
282
+ console.error(error); // Path traversal attempt blocked
283
+ }
284
+
285
+ // To allow parent directory access (not recommended for untrusted input)
286
+ const unsafeCache = new FileEntryCache({
287
+ cwd: '/project/root',
288
+ strictPaths: false // Explicitly disable protection
289
+ });
290
+ ```
291
+
292
+ ## Security Features
293
+
294
+ When `strictPaths` is enabled:
295
+ - **Path Traversal Prevention**: Blocks attempts to access files outside the working directory using `../` sequences
296
+ - **Null Byte Protection**: Automatically removes null bytes from paths to prevent injection attacks
297
+ - **Path Normalization**: Cleans and normalizes paths to prevent bypass attempts
298
+
299
+ ## Use Cases
300
+
301
+ ### Build Tools with Untrusted Input
302
+ ```javascript
303
+ // Secure build tool configuration
304
+ const cache = fileEntryCache.create(
305
+ '.buildcache',
306
+ './cache',
307
+ true, // useCheckSum
308
+ process.cwd()
309
+ );
310
+
311
+ // Enable strict path checking for security
312
+ cache.strictPaths = true;
313
+
314
+ // Process user-provided file paths safely
315
+ function processUserFile(userProvidedPath) {
316
+ try {
317
+ const descriptor = cache.getFileDescriptor(userProvidedPath);
318
+ // Safe to process - file is within boundaries
319
+ return descriptor;
320
+ } catch (error) {
321
+ if (error.message.includes('Path traversal attempt blocked')) {
322
+ console.warn('Security: Blocked access to:', userProvidedPath);
323
+ return null;
324
+ }
325
+ throw error;
326
+ }
327
+ }
328
+ ```
329
+
330
+ ### CI/CD Environments
331
+ ```javascript
332
+ // Strict security for CI/CD pipelines
333
+ const cache = new FileEntryCache({
334
+ cwd: process.env.GITHUB_WORKSPACE || process.cwd(),
335
+ strictPaths: true, // Prevent access outside workspace
336
+ useCheckSum: true // Content-based validation
337
+ });
338
+
339
+ // All file operations are now restricted to the workspace
340
+ cache.getFileDescriptor('./src/app.js'); // ✓ Allowed
341
+ cache.getFileDescriptor('/etc/passwd'); // ✗ Blocked (absolute path outside cwd)
342
+ cache.getFileDescriptor('../../../root'); // ✗ Blocked (path traversal)
343
+ ```
344
+
345
+ ### Dynamic Security Control
346
+ ```javascript
347
+ const cache = new FileEntryCache({ cwd: '/safe/directory' });
348
+
349
+ // Start with relaxed mode for trusted operations
350
+ cache.strictPaths = false;
351
+ processInternalFiles();
352
+
353
+ // Enable strict mode for untrusted input
354
+ cache.strictPaths = true;
355
+ processUserUploadedPaths();
356
+
357
+ // Return to relaxed mode if needed
358
+ cache.strictPaths = false;
359
+ ```
360
+
361
+ ## Default Behavior
362
+
363
+ **As of v11, `strictPaths` is enabled by default** to provide secure defaults. This means:
364
+ - Path traversal attempts using `../` are blocked
365
+ - File access is restricted to within the configured `cwd`
366
+ - Null bytes in paths are automatically sanitized
367
+
368
+ ### Migrating from v10 or Earlier
369
+
370
+ If you're upgrading from v10 or earlier and need to maintain the previous behavior (for example, if your code legitimately accesses parent directories), you can explicitly disable strict paths:
371
+
372
+ ```javascript
373
+ const cache = new FileEntryCache({
374
+ cwd: process.cwd(),
375
+ strictPaths: false // Restore v10 behavior
376
+ });
377
+ ```
378
+
379
+ However, we strongly recommend keeping `strictPaths: true` and adjusting your code to work within the security boundaries, especially when processing any untrusted input.
380
+
214
381
  # Using Checksums to Determine if a File has Changed (useCheckSum)
215
382
 
216
383
  By default the `useCheckSum` is `false`. This means that the `FileEntryCache` will use the `mtime` and `ctime` to determine if the file has changed. If you set `useCheckSum` to `true` it will use a checksum to determine if the file has changed. This is useful when you want to make sure that the file has not changed at all.
package/dist/index.cjs CHANGED
@@ -40,14 +40,15 @@ var import_node_crypto = __toESM(require("crypto"), 1);
40
40
  var import_node_fs = __toESM(require("fs"), 1);
41
41
  var import_node_path = __toESM(require("path"), 1);
42
42
  var import_flat_cache = require("flat-cache");
43
- function createFromFile(filePath, useCheckSum) {
43
+ function createFromFile(filePath, useCheckSum, cwd) {
44
44
  const fname = import_node_path.default.basename(filePath);
45
45
  const directory = import_node_path.default.dirname(filePath);
46
- return create(fname, directory, useCheckSum);
46
+ return create(fname, directory, useCheckSum, cwd);
47
47
  }
48
- function create(cacheId, cacheDirectory, useCheckSum) {
48
+ function create(cacheId, cacheDirectory, useCheckSum, cwd) {
49
49
  const options = {
50
50
  useCheckSum,
51
+ cwd,
51
52
  cache: {
52
53
  cacheId,
53
54
  cacheDir: cacheDirectory
@@ -69,25 +70,29 @@ var FileEntryDefault = class {
69
70
  var FileEntryCache = class {
70
71
  _cache = new import_flat_cache.FlatCache({ useClone: false });
71
72
  _useCheckSum = false;
72
- _useModifiedTime = true;
73
73
  _hashAlgorithm = "md5";
74
+ _cwd = process.cwd();
75
+ _strictPaths = true;
74
76
  /**
75
77
  * Create a new FileEntryCache instance
76
- * @param options - The options for the FileEntryCache
78
+ * @param options - The options for the FileEntryCache (all properties are optional with defaults)
77
79
  */
78
80
  constructor(options) {
79
81
  if (options?.cache) {
80
82
  this._cache = new import_flat_cache.FlatCache(options.cache);
81
83
  }
82
- if (options?.useModifiedTime) {
83
- this._useModifiedTime = options.useModifiedTime;
84
- }
85
84
  if (options?.useCheckSum) {
86
85
  this._useCheckSum = options.useCheckSum;
87
86
  }
88
87
  if (options?.hashAlgorithm) {
89
88
  this._hashAlgorithm = options.hashAlgorithm;
90
89
  }
90
+ if (options?.cwd) {
91
+ this._cwd = options.cwd;
92
+ }
93
+ if (options?.strictPaths !== void 0) {
94
+ this._strictPaths = options.strictPaths;
95
+ }
91
96
  }
92
97
  /**
93
98
  * Get the cache
@@ -105,7 +110,7 @@ var FileEntryCache = class {
105
110
  }
106
111
  /**
107
112
  * Use the hash to check if the file has changed
108
- * @returns {boolean} if the hash is used to check if the file has changed
113
+ * @returns {boolean} if the hash is used to check if the file has changed (default: false)
109
114
  */
110
115
  get useCheckSum() {
111
116
  return this._useCheckSum;
@@ -117,23 +122,9 @@ var FileEntryCache = class {
117
122
  set useCheckSum(value) {
118
123
  this._useCheckSum = value;
119
124
  }
120
- /**
121
- * Use the modified time to check if the file has changed
122
- * @returns {boolean} if the modified time is used to check if the file has changed
123
- */
124
- get useModifiedTime() {
125
- return this._useModifiedTime;
126
- }
127
- /**
128
- * Set the useModifiedTime value
129
- * @param {boolean} value - The value to set
130
- */
131
- set useModifiedTime(value) {
132
- this._useModifiedTime = value;
133
- }
134
125
  /**
135
126
  * Get the hash algorithm
136
- * @returns {string} The hash algorithm
127
+ * @returns {string} The hash algorithm (default: 'md5')
137
128
  */
138
129
  get hashAlgorithm() {
139
130
  return this._hashAlgorithm;
@@ -145,6 +136,34 @@ var FileEntryCache = class {
145
136
  set hashAlgorithm(value) {
146
137
  this._hashAlgorithm = value;
147
138
  }
139
+ /**
140
+ * Get the current working directory
141
+ * @returns {string} The current working directory (default: process.cwd())
142
+ */
143
+ get cwd() {
144
+ return this._cwd;
145
+ }
146
+ /**
147
+ * Set the current working directory
148
+ * @param {string} value - The value to set
149
+ */
150
+ set cwd(value) {
151
+ this._cwd = value;
152
+ }
153
+ /**
154
+ * Get whether to restrict paths to cwd boundaries
155
+ * @returns {boolean} Whether strict path checking is enabled (default: true)
156
+ */
157
+ get strictPaths() {
158
+ return this._strictPaths;
159
+ }
160
+ /**
161
+ * Set whether to restrict paths to cwd boundaries
162
+ * @param {boolean} value - The value to set
163
+ */
164
+ set strictPaths(value) {
165
+ this._strictPaths = value;
166
+ }
148
167
  /**
149
168
  * Given a buffer, calculate md5 hash of its content.
150
169
  * @method getHash
@@ -241,7 +260,6 @@ var FileEntryCache = class {
241
260
  result.meta = this._cache.getKey(result.key) ?? {};
242
261
  const absolutePath = this.getAbsolutePath(filePath);
243
262
  const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
244
- const useModifiedTimeValue = options?.useModifiedTime ?? this._useModifiedTime;
245
263
  try {
246
264
  fstat = import_node_fs.default.statSync(absolutePath);
247
265
  result.meta = {
@@ -274,7 +292,7 @@ var FileEntryCache = class {
274
292
  if (result.meta.data === void 0) {
275
293
  result.meta.data = metaCache.data;
276
294
  }
277
- if (useModifiedTimeValue && metaCache?.mtime !== result.meta?.mtime) {
295
+ if (useCheckSumValue === false && metaCache?.mtime !== result.meta?.mtime) {
278
296
  result.changed = true;
279
297
  }
280
298
  if (metaCache?.size !== result.meta?.size) {
@@ -369,13 +387,54 @@ var FileEntryCache = class {
369
387
  }
370
388
  /**
371
389
  * Get the Absolute Path. If it is already absolute it will return the path as is.
390
+ * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
372
391
  * @method getAbsolutePath
373
392
  * @param filePath - The file path to get the absolute path for
374
393
  * @returns {string}
394
+ * @throws {Error} When strictPaths is true and path would resolve outside cwd
375
395
  */
376
396
  getAbsolutePath(filePath) {
377
397
  if (this.isRelativePath(filePath)) {
378
- return import_node_path.default.resolve(process.cwd(), filePath);
398
+ const sanitizedPath = filePath.replace(/\0/g, "");
399
+ const resolved = import_node_path.default.resolve(this._cwd, sanitizedPath);
400
+ if (this._strictPaths) {
401
+ const normalizedResolved = import_node_path.default.normalize(resolved);
402
+ const normalizedCwd = import_node_path.default.normalize(this._cwd);
403
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
404
+ if (!isWithinCwd) {
405
+ throw new Error(
406
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
407
+ );
408
+ }
409
+ }
410
+ return resolved;
411
+ }
412
+ return filePath;
413
+ }
414
+ /**
415
+ * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
416
+ * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
417
+ * @method getAbsolutePathWithCwd
418
+ * @param filePath - The file path to get the absolute path for
419
+ * @param cwd - The custom working directory to resolve relative paths from
420
+ * @returns {string}
421
+ * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
422
+ */
423
+ getAbsolutePathWithCwd(filePath, cwd) {
424
+ if (this.isRelativePath(filePath)) {
425
+ const sanitizedPath = filePath.replace(/\0/g, "");
426
+ const resolved = import_node_path.default.resolve(cwd, sanitizedPath);
427
+ if (this._strictPaths) {
428
+ const normalizedResolved = import_node_path.default.normalize(resolved);
429
+ const normalizedCwd = import_node_path.default.normalize(cwd);
430
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + import_node_path.default.sep);
431
+ if (!isWithinCwd) {
432
+ throw new Error(
433
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
434
+ );
435
+ }
436
+ }
437
+ return resolved;
379
438
  }
380
439
  return filePath;
381
440
  }
package/dist/index.d.cts CHANGED
@@ -2,35 +2,70 @@ import { Buffer } from 'node:buffer';
2
2
  import { FlatCacheOptions, FlatCache } from 'flat-cache';
3
3
 
4
4
  type FileEntryCacheOptions = {
5
+ /** Whether to use file modified time for change detection (default: true) */
5
6
  useModifiedTime?: boolean;
7
+ /** Whether to use checksum for change detection (default: false) */
6
8
  useCheckSum?: boolean;
9
+ /** Hash algorithm to use for checksum (default: 'md5') */
7
10
  hashAlgorithm?: string;
11
+ /** Current working directory for resolving relative paths (default: process.cwd()) */
12
+ cwd?: string;
13
+ /** Restrict file access to within cwd boundaries (default: true) */
14
+ strictPaths?: boolean;
15
+ /** Options for the underlying flat cache */
8
16
  cache?: FlatCacheOptions;
9
17
  };
10
18
  type GetFileDescriptorOptions = {
19
+ /** Whether to use checksum for this specific file check instead of modified time (mtime) (overrides instance setting) */
11
20
  useCheckSum?: boolean;
12
- useModifiedTime?: boolean;
13
21
  };
14
22
  type FileDescriptor = {
23
+ /** The cache key for this file (typically the file path) */
15
24
  key: string;
25
+ /** Whether the file has changed since last cache check */
16
26
  changed?: boolean;
27
+ /** Metadata about the file */
17
28
  meta: FileDescriptorMeta;
29
+ /** Whether the file was not found */
18
30
  notFound?: boolean;
31
+ /** Error encountered when accessing the file */
19
32
  err?: Error;
20
33
  };
21
34
  type FileDescriptorMeta = {
35
+ /** File size in bytes */
22
36
  size?: number;
37
+ /** File modification time (timestamp in milliseconds) */
23
38
  mtime?: number;
39
+ /** File content hash (when useCheckSum is enabled) */
24
40
  hash?: string;
41
+ /** Custom data associated with the file (e.g., lint results, metadata) */
25
42
  data?: unknown;
26
43
  };
27
44
  type AnalyzedFiles = {
45
+ /** Array of file paths that have changed since last cache */
28
46
  changedFiles: string[];
47
+ /** Array of file paths that were not found */
29
48
  notFoundFiles: string[];
49
+ /** Array of file paths that have not changed since last cache */
30
50
  notChangedFiles: string[];
31
51
  };
32
- declare function createFromFile(filePath: string, useCheckSum?: boolean): FileEntryCache;
33
- declare function create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean): FileEntryCache;
52
+ /**
53
+ * Create a new FileEntryCache instance from a file path
54
+ * @param filePath - The path to the cache file
55
+ * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
56
+ * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
57
+ * @returns A new FileEntryCache instance
58
+ */
59
+ declare function createFromFile(filePath: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
60
+ /**
61
+ * Create a new FileEntryCache instance
62
+ * @param cacheId - The cache file name
63
+ * @param cacheDirectory - The directory to store the cache file (default: undefined, cache won't be persisted)
64
+ * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
65
+ * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
66
+ * @returns A new FileEntryCache instance
67
+ */
68
+ declare function create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
34
69
  declare class FileEntryDefault {
35
70
  static create: typeof create;
36
71
  static createFromFile: typeof createFromFile;
@@ -38,11 +73,12 @@ declare class FileEntryDefault {
38
73
  declare class FileEntryCache {
39
74
  private _cache;
40
75
  private _useCheckSum;
41
- private _useModifiedTime;
42
76
  private _hashAlgorithm;
77
+ private _cwd;
78
+ private _strictPaths;
43
79
  /**
44
80
  * Create a new FileEntryCache instance
45
- * @param options - The options for the FileEntryCache
81
+ * @param options - The options for the FileEntryCache (all properties are optional with defaults)
46
82
  */
47
83
  constructor(options?: FileEntryCacheOptions);
48
84
  /**
@@ -57,7 +93,7 @@ declare class FileEntryCache {
57
93
  set cache(cache: FlatCache);
58
94
  /**
59
95
  * Use the hash to check if the file has changed
60
- * @returns {boolean} if the hash is used to check if the file has changed
96
+ * @returns {boolean} if the hash is used to check if the file has changed (default: false)
61
97
  */
62
98
  get useCheckSum(): boolean;
63
99
  /**
@@ -65,19 +101,9 @@ declare class FileEntryCache {
65
101
  * @param {boolean} value - The value to set
66
102
  */
67
103
  set useCheckSum(value: boolean);
68
- /**
69
- * Use the modified time to check if the file has changed
70
- * @returns {boolean} if the modified time is used to check if the file has changed
71
- */
72
- get useModifiedTime(): boolean;
73
- /**
74
- * Set the useModifiedTime value
75
- * @param {boolean} value - The value to set
76
- */
77
- set useModifiedTime(value: boolean);
78
104
  /**
79
105
  * Get the hash algorithm
80
- * @returns {string} The hash algorithm
106
+ * @returns {string} The hash algorithm (default: 'md5')
81
107
  */
82
108
  get hashAlgorithm(): string;
83
109
  /**
@@ -85,6 +111,26 @@ declare class FileEntryCache {
85
111
  * @param {string} value - The value to set
86
112
  */
87
113
  set hashAlgorithm(value: string);
114
+ /**
115
+ * Get the current working directory
116
+ * @returns {string} The current working directory (default: process.cwd())
117
+ */
118
+ get cwd(): string;
119
+ /**
120
+ * Set the current working directory
121
+ * @param {string} value - The value to set
122
+ */
123
+ set cwd(value: string);
124
+ /**
125
+ * Get whether to restrict paths to cwd boundaries
126
+ * @returns {boolean} Whether strict path checking is enabled (default: true)
127
+ */
128
+ get strictPaths(): boolean;
129
+ /**
130
+ * Set whether to restrict paths to cwd boundaries
131
+ * @param {boolean} value - The value to set
132
+ */
133
+ set strictPaths(value: boolean);
88
134
  /**
89
135
  * Given a buffer, calculate md5 hash of its content.
90
136
  * @method getHash
@@ -173,11 +219,23 @@ declare class FileEntryCache {
173
219
  getFileDescriptorsByPath(filePath: string): FileDescriptor[];
174
220
  /**
175
221
  * Get the Absolute Path. If it is already absolute it will return the path as is.
222
+ * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
176
223
  * @method getAbsolutePath
177
224
  * @param filePath - The file path to get the absolute path for
178
225
  * @returns {string}
226
+ * @throws {Error} When strictPaths is true and path would resolve outside cwd
179
227
  */
180
228
  getAbsolutePath(filePath: string): string;
229
+ /**
230
+ * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
231
+ * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
232
+ * @method getAbsolutePathWithCwd
233
+ * @param filePath - The file path to get the absolute path for
234
+ * @param cwd - The custom working directory to resolve relative paths from
235
+ * @returns {string}
236
+ * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
237
+ */
238
+ getAbsolutePathWithCwd(filePath: string, cwd: string): string;
181
239
  /**
182
240
  * Rename cache keys that start with a given path prefix.
183
241
  * @method renameCacheKeys
package/dist/index.d.ts CHANGED
@@ -2,35 +2,70 @@ import { Buffer } from 'node:buffer';
2
2
  import { FlatCacheOptions, FlatCache } from 'flat-cache';
3
3
 
4
4
  type FileEntryCacheOptions = {
5
+ /** Whether to use file modified time for change detection (default: true) */
5
6
  useModifiedTime?: boolean;
7
+ /** Whether to use checksum for change detection (default: false) */
6
8
  useCheckSum?: boolean;
9
+ /** Hash algorithm to use for checksum (default: 'md5') */
7
10
  hashAlgorithm?: string;
11
+ /** Current working directory for resolving relative paths (default: process.cwd()) */
12
+ cwd?: string;
13
+ /** Restrict file access to within cwd boundaries (default: true) */
14
+ strictPaths?: boolean;
15
+ /** Options for the underlying flat cache */
8
16
  cache?: FlatCacheOptions;
9
17
  };
10
18
  type GetFileDescriptorOptions = {
19
+ /** Whether to use checksum for this specific file check instead of modified time (mtime) (overrides instance setting) */
11
20
  useCheckSum?: boolean;
12
- useModifiedTime?: boolean;
13
21
  };
14
22
  type FileDescriptor = {
23
+ /** The cache key for this file (typically the file path) */
15
24
  key: string;
25
+ /** Whether the file has changed since last cache check */
16
26
  changed?: boolean;
27
+ /** Metadata about the file */
17
28
  meta: FileDescriptorMeta;
29
+ /** Whether the file was not found */
18
30
  notFound?: boolean;
31
+ /** Error encountered when accessing the file */
19
32
  err?: Error;
20
33
  };
21
34
  type FileDescriptorMeta = {
35
+ /** File size in bytes */
22
36
  size?: number;
37
+ /** File modification time (timestamp in milliseconds) */
23
38
  mtime?: number;
39
+ /** File content hash (when useCheckSum is enabled) */
24
40
  hash?: string;
41
+ /** Custom data associated with the file (e.g., lint results, metadata) */
25
42
  data?: unknown;
26
43
  };
27
44
  type AnalyzedFiles = {
45
+ /** Array of file paths that have changed since last cache */
28
46
  changedFiles: string[];
47
+ /** Array of file paths that were not found */
29
48
  notFoundFiles: string[];
49
+ /** Array of file paths that have not changed since last cache */
30
50
  notChangedFiles: string[];
31
51
  };
32
- declare function createFromFile(filePath: string, useCheckSum?: boolean): FileEntryCache;
33
- declare function create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean): FileEntryCache;
52
+ /**
53
+ * Create a new FileEntryCache instance from a file path
54
+ * @param filePath - The path to the cache file
55
+ * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
56
+ * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
57
+ * @returns A new FileEntryCache instance
58
+ */
59
+ declare function createFromFile(filePath: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
60
+ /**
61
+ * Create a new FileEntryCache instance
62
+ * @param cacheId - The cache file name
63
+ * @param cacheDirectory - The directory to store the cache file (default: undefined, cache won't be persisted)
64
+ * @param useCheckSum - Whether to use checksum to detect file changes (default: false)
65
+ * @param cwd - The current working directory for resolving relative paths (default: process.cwd())
66
+ * @returns A new FileEntryCache instance
67
+ */
68
+ declare function create(cacheId: string, cacheDirectory?: string, useCheckSum?: boolean, cwd?: string): FileEntryCache;
34
69
  declare class FileEntryDefault {
35
70
  static create: typeof create;
36
71
  static createFromFile: typeof createFromFile;
@@ -38,11 +73,12 @@ declare class FileEntryDefault {
38
73
  declare class FileEntryCache {
39
74
  private _cache;
40
75
  private _useCheckSum;
41
- private _useModifiedTime;
42
76
  private _hashAlgorithm;
77
+ private _cwd;
78
+ private _strictPaths;
43
79
  /**
44
80
  * Create a new FileEntryCache instance
45
- * @param options - The options for the FileEntryCache
81
+ * @param options - The options for the FileEntryCache (all properties are optional with defaults)
46
82
  */
47
83
  constructor(options?: FileEntryCacheOptions);
48
84
  /**
@@ -57,7 +93,7 @@ declare class FileEntryCache {
57
93
  set cache(cache: FlatCache);
58
94
  /**
59
95
  * Use the hash to check if the file has changed
60
- * @returns {boolean} if the hash is used to check if the file has changed
96
+ * @returns {boolean} if the hash is used to check if the file has changed (default: false)
61
97
  */
62
98
  get useCheckSum(): boolean;
63
99
  /**
@@ -65,19 +101,9 @@ declare class FileEntryCache {
65
101
  * @param {boolean} value - The value to set
66
102
  */
67
103
  set useCheckSum(value: boolean);
68
- /**
69
- * Use the modified time to check if the file has changed
70
- * @returns {boolean} if the modified time is used to check if the file has changed
71
- */
72
- get useModifiedTime(): boolean;
73
- /**
74
- * Set the useModifiedTime value
75
- * @param {boolean} value - The value to set
76
- */
77
- set useModifiedTime(value: boolean);
78
104
  /**
79
105
  * Get the hash algorithm
80
- * @returns {string} The hash algorithm
106
+ * @returns {string} The hash algorithm (default: 'md5')
81
107
  */
82
108
  get hashAlgorithm(): string;
83
109
  /**
@@ -85,6 +111,26 @@ declare class FileEntryCache {
85
111
  * @param {string} value - The value to set
86
112
  */
87
113
  set hashAlgorithm(value: string);
114
+ /**
115
+ * Get the current working directory
116
+ * @returns {string} The current working directory (default: process.cwd())
117
+ */
118
+ get cwd(): string;
119
+ /**
120
+ * Set the current working directory
121
+ * @param {string} value - The value to set
122
+ */
123
+ set cwd(value: string);
124
+ /**
125
+ * Get whether to restrict paths to cwd boundaries
126
+ * @returns {boolean} Whether strict path checking is enabled (default: true)
127
+ */
128
+ get strictPaths(): boolean;
129
+ /**
130
+ * Set whether to restrict paths to cwd boundaries
131
+ * @param {boolean} value - The value to set
132
+ */
133
+ set strictPaths(value: boolean);
88
134
  /**
89
135
  * Given a buffer, calculate md5 hash of its content.
90
136
  * @method getHash
@@ -173,11 +219,23 @@ declare class FileEntryCache {
173
219
  getFileDescriptorsByPath(filePath: string): FileDescriptor[];
174
220
  /**
175
221
  * Get the Absolute Path. If it is already absolute it will return the path as is.
222
+ * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
176
223
  * @method getAbsolutePath
177
224
  * @param filePath - The file path to get the absolute path for
178
225
  * @returns {string}
226
+ * @throws {Error} When strictPaths is true and path would resolve outside cwd
179
227
  */
180
228
  getAbsolutePath(filePath: string): string;
229
+ /**
230
+ * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
231
+ * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
232
+ * @method getAbsolutePathWithCwd
233
+ * @param filePath - The file path to get the absolute path for
234
+ * @param cwd - The custom working directory to resolve relative paths from
235
+ * @returns {string}
236
+ * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
237
+ */
238
+ getAbsolutePathWithCwd(filePath: string, cwd: string): string;
181
239
  /**
182
240
  * Rename cache keys that start with a given path prefix.
183
241
  * @method renameCacheKeys
package/dist/index.js CHANGED
@@ -6,14 +6,15 @@ import {
6
6
  createFromFile as createFlatCacheFile,
7
7
  FlatCache
8
8
  } from "flat-cache";
9
- function createFromFile(filePath, useCheckSum) {
9
+ function createFromFile(filePath, useCheckSum, cwd) {
10
10
  const fname = path.basename(filePath);
11
11
  const directory = path.dirname(filePath);
12
- return create(fname, directory, useCheckSum);
12
+ return create(fname, directory, useCheckSum, cwd);
13
13
  }
14
- function create(cacheId, cacheDirectory, useCheckSum) {
14
+ function create(cacheId, cacheDirectory, useCheckSum, cwd) {
15
15
  const options = {
16
16
  useCheckSum,
17
+ cwd,
17
18
  cache: {
18
19
  cacheId,
19
20
  cacheDir: cacheDirectory
@@ -35,25 +36,29 @@ var FileEntryDefault = class {
35
36
  var FileEntryCache = class {
36
37
  _cache = new FlatCache({ useClone: false });
37
38
  _useCheckSum = false;
38
- _useModifiedTime = true;
39
39
  _hashAlgorithm = "md5";
40
+ _cwd = process.cwd();
41
+ _strictPaths = true;
40
42
  /**
41
43
  * Create a new FileEntryCache instance
42
- * @param options - The options for the FileEntryCache
44
+ * @param options - The options for the FileEntryCache (all properties are optional with defaults)
43
45
  */
44
46
  constructor(options) {
45
47
  if (options?.cache) {
46
48
  this._cache = new FlatCache(options.cache);
47
49
  }
48
- if (options?.useModifiedTime) {
49
- this._useModifiedTime = options.useModifiedTime;
50
- }
51
50
  if (options?.useCheckSum) {
52
51
  this._useCheckSum = options.useCheckSum;
53
52
  }
54
53
  if (options?.hashAlgorithm) {
55
54
  this._hashAlgorithm = options.hashAlgorithm;
56
55
  }
56
+ if (options?.cwd) {
57
+ this._cwd = options.cwd;
58
+ }
59
+ if (options?.strictPaths !== void 0) {
60
+ this._strictPaths = options.strictPaths;
61
+ }
57
62
  }
58
63
  /**
59
64
  * Get the cache
@@ -71,7 +76,7 @@ var FileEntryCache = class {
71
76
  }
72
77
  /**
73
78
  * Use the hash to check if the file has changed
74
- * @returns {boolean} if the hash is used to check if the file has changed
79
+ * @returns {boolean} if the hash is used to check if the file has changed (default: false)
75
80
  */
76
81
  get useCheckSum() {
77
82
  return this._useCheckSum;
@@ -83,23 +88,9 @@ var FileEntryCache = class {
83
88
  set useCheckSum(value) {
84
89
  this._useCheckSum = value;
85
90
  }
86
- /**
87
- * Use the modified time to check if the file has changed
88
- * @returns {boolean} if the modified time is used to check if the file has changed
89
- */
90
- get useModifiedTime() {
91
- return this._useModifiedTime;
92
- }
93
- /**
94
- * Set the useModifiedTime value
95
- * @param {boolean} value - The value to set
96
- */
97
- set useModifiedTime(value) {
98
- this._useModifiedTime = value;
99
- }
100
91
  /**
101
92
  * Get the hash algorithm
102
- * @returns {string} The hash algorithm
93
+ * @returns {string} The hash algorithm (default: 'md5')
103
94
  */
104
95
  get hashAlgorithm() {
105
96
  return this._hashAlgorithm;
@@ -111,6 +102,34 @@ var FileEntryCache = class {
111
102
  set hashAlgorithm(value) {
112
103
  this._hashAlgorithm = value;
113
104
  }
105
+ /**
106
+ * Get the current working directory
107
+ * @returns {string} The current working directory (default: process.cwd())
108
+ */
109
+ get cwd() {
110
+ return this._cwd;
111
+ }
112
+ /**
113
+ * Set the current working directory
114
+ * @param {string} value - The value to set
115
+ */
116
+ set cwd(value) {
117
+ this._cwd = value;
118
+ }
119
+ /**
120
+ * Get whether to restrict paths to cwd boundaries
121
+ * @returns {boolean} Whether strict path checking is enabled (default: true)
122
+ */
123
+ get strictPaths() {
124
+ return this._strictPaths;
125
+ }
126
+ /**
127
+ * Set whether to restrict paths to cwd boundaries
128
+ * @param {boolean} value - The value to set
129
+ */
130
+ set strictPaths(value) {
131
+ this._strictPaths = value;
132
+ }
114
133
  /**
115
134
  * Given a buffer, calculate md5 hash of its content.
116
135
  * @method getHash
@@ -207,7 +226,6 @@ var FileEntryCache = class {
207
226
  result.meta = this._cache.getKey(result.key) ?? {};
208
227
  const absolutePath = this.getAbsolutePath(filePath);
209
228
  const useCheckSumValue = options?.useCheckSum ?? this._useCheckSum;
210
- const useModifiedTimeValue = options?.useModifiedTime ?? this._useModifiedTime;
211
229
  try {
212
230
  fstat = fs.statSync(absolutePath);
213
231
  result.meta = {
@@ -240,7 +258,7 @@ var FileEntryCache = class {
240
258
  if (result.meta.data === void 0) {
241
259
  result.meta.data = metaCache.data;
242
260
  }
243
- if (useModifiedTimeValue && metaCache?.mtime !== result.meta?.mtime) {
261
+ if (useCheckSumValue === false && metaCache?.mtime !== result.meta?.mtime) {
244
262
  result.changed = true;
245
263
  }
246
264
  if (metaCache?.size !== result.meta?.size) {
@@ -335,13 +353,54 @@ var FileEntryCache = class {
335
353
  }
336
354
  /**
337
355
  * Get the Absolute Path. If it is already absolute it will return the path as is.
356
+ * When strictPaths is enabled, ensures the resolved path stays within cwd boundaries.
338
357
  * @method getAbsolutePath
339
358
  * @param filePath - The file path to get the absolute path for
340
359
  * @returns {string}
360
+ * @throws {Error} When strictPaths is true and path would resolve outside cwd
341
361
  */
342
362
  getAbsolutePath(filePath) {
343
363
  if (this.isRelativePath(filePath)) {
344
- return path.resolve(process.cwd(), filePath);
364
+ const sanitizedPath = filePath.replace(/\0/g, "");
365
+ const resolved = path.resolve(this._cwd, sanitizedPath);
366
+ if (this._strictPaths) {
367
+ const normalizedResolved = path.normalize(resolved);
368
+ const normalizedCwd = path.normalize(this._cwd);
369
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
370
+ if (!isWithinCwd) {
371
+ throw new Error(
372
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${this._cwd}"`
373
+ );
374
+ }
375
+ }
376
+ return resolved;
377
+ }
378
+ return filePath;
379
+ }
380
+ /**
381
+ * Get the Absolute Path with a custom working directory. If it is already absolute it will return the path as is.
382
+ * When strictPaths is enabled, ensures the resolved path stays within the provided cwd boundaries.
383
+ * @method getAbsolutePathWithCwd
384
+ * @param filePath - The file path to get the absolute path for
385
+ * @param cwd - The custom working directory to resolve relative paths from
386
+ * @returns {string}
387
+ * @throws {Error} When strictPaths is true and path would resolve outside the provided cwd
388
+ */
389
+ getAbsolutePathWithCwd(filePath, cwd) {
390
+ if (this.isRelativePath(filePath)) {
391
+ const sanitizedPath = filePath.replace(/\0/g, "");
392
+ const resolved = path.resolve(cwd, sanitizedPath);
393
+ if (this._strictPaths) {
394
+ const normalizedResolved = path.normalize(resolved);
395
+ const normalizedCwd = path.normalize(cwd);
396
+ const isWithinCwd = normalizedResolved === normalizedCwd || normalizedResolved.startsWith(normalizedCwd + path.sep);
397
+ if (!isWithinCwd) {
398
+ throw new Error(
399
+ `Path traversal attempt blocked: "${filePath}" resolves outside of working directory "${cwd}"`
400
+ );
401
+ }
402
+ }
403
+ return resolved;
345
404
  }
346
405
  return filePath;
347
406
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "file-entry-cache",
3
- "version": "11.0.0-beta.1",
3
+ "version": "11.0.0-beta.3",
4
4
  "description": "A lightweight cache for file metadata, ideal for processes that work on a specific set of files and only need to reprocess files that have changed since the last run",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -29,16 +29,16 @@
29
29
  "cache"
30
30
  ],
31
31
  "devDependencies": {
32
- "@biomejs/biome": "^2.2.4",
33
- "@types/node": "^24.5.0",
32
+ "@biomejs/biome": "^2.2.5",
33
+ "@types/node": "^24.6.2",
34
34
  "@vitest/coverage-v8": "^3.2.4",
35
35
  "rimraf": "^6.0.1",
36
36
  "tsup": "^8.5.0",
37
- "typescript": "^5.9.2",
37
+ "typescript": "^5.9.3",
38
38
  "vitest": "^3.2.4"
39
39
  },
40
40
  "dependencies": {
41
- "flat-cache": "^6.1.14"
41
+ "flat-cache": "^6.1.17"
42
42
  },
43
43
  "files": [
44
44
  "dist",