@openclaw/fs-safe 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/dist/absolute-path.d.ts.map +1 -1
  4. package/dist/absolute-path.js +8 -7
  5. package/dist/advanced.d.ts +2 -2
  6. package/dist/advanced.d.ts.map +1 -1
  7. package/dist/advanced.js +2 -2
  8. package/dist/archive-kind.d.ts +1 -0
  9. package/dist/archive-kind.d.ts.map +1 -1
  10. package/dist/archive-kind.js +6 -0
  11. package/dist/archive-limits.d.ts +0 -1
  12. package/dist/archive-limits.d.ts.map +1 -1
  13. package/dist/archive-limits.js +0 -3
  14. package/dist/archive-native.d.ts +1 -0
  15. package/dist/archive-native.d.ts.map +1 -1
  16. package/dist/archive-native.js +20 -76
  17. package/dist/archive-parser-errors.d.ts +2 -0
  18. package/dist/archive-parser-errors.d.ts.map +1 -0
  19. package/dist/archive-parser-errors.js +12 -0
  20. package/dist/archive-plan.d.ts +20 -0
  21. package/dist/archive-plan.d.ts.map +1 -0
  22. package/dist/archive-plan.js +55 -0
  23. package/dist/archive-read.d.ts.map +1 -1
  24. package/dist/archive-read.js +7 -13
  25. package/dist/archive-tar-inspect.d.ts +7 -0
  26. package/dist/archive-tar-inspect.d.ts.map +1 -0
  27. package/dist/archive-tar-inspect.js +59 -0
  28. package/dist/archive-tar-wasm.d.ts +1 -1
  29. package/dist/archive-tar-wasm.d.ts.map +1 -1
  30. package/dist/archive-tar-wasm.js +5 -8
  31. package/dist/archive-tar.d.ts +3 -1
  32. package/dist/archive-tar.d.ts.map +1 -1
  33. package/dist/archive-tar.js +12 -49
  34. package/dist/archive.d.ts +1 -0
  35. package/dist/archive.d.ts.map +1 -1
  36. package/dist/archive.js +54 -33
  37. package/dist/atomic.d.ts +1 -1
  38. package/dist/atomic.d.ts.map +1 -1
  39. package/dist/atomic.js +1 -1
  40. package/dist/bounded-read-stream.d.ts +1 -5
  41. package/dist/bounded-read-stream.d.ts.map +1 -1
  42. package/dist/bounded-read-stream.js +5 -13
  43. package/dist/bounded-read.d.ts.map +1 -1
  44. package/dist/bounded-read.js +31 -4
  45. package/dist/byte-budget.d.ts +0 -1
  46. package/dist/byte-budget.d.ts.map +1 -1
  47. package/dist/byte-budget.js +1 -1
  48. package/dist/directory-durability.js +5 -5
  49. package/dist/directory-guard.d.ts.map +1 -1
  50. package/dist/directory-guard.js +5 -6
  51. package/dist/directory-mode-owner.d.ts.map +1 -1
  52. package/dist/directory-mode-owner.js +2 -0
  53. package/dist/error-detail.d.ts +1 -0
  54. package/dist/error-detail.d.ts.map +1 -1
  55. package/dist/error-detail.js +6 -0
  56. package/dist/file-store-boundary.d.ts +1 -1
  57. package/dist/file-store-boundary.d.ts.map +1 -1
  58. package/dist/file-store-boundary.js +3 -14
  59. package/dist/file-store-prune.js +17 -8
  60. package/dist/file-store.js +3 -3
  61. package/dist/file-sync.d.ts +5 -0
  62. package/dist/file-sync.d.ts.map +1 -0
  63. package/dist/file-sync.js +19 -0
  64. package/dist/guarded-mkdir.d.ts.map +1 -1
  65. package/dist/guarded-mkdir.js +5 -4
  66. package/dist/guarded-mutation.d.ts +3 -0
  67. package/dist/guarded-mutation.d.ts.map +1 -1
  68. package/dist/guarded-mutation.js +5 -1
  69. package/dist/home-dir.d.ts +0 -10
  70. package/dist/home-dir.d.ts.map +1 -1
  71. package/dist/home-dir.js +0 -28
  72. package/dist/json-durable-queue-ownership.d.ts +1 -0
  73. package/dist/json-durable-queue-ownership.d.ts.map +1 -1
  74. package/dist/json-durable-queue-ownership.js +139 -18
  75. package/dist/json-durable-queue.d.ts.map +1 -1
  76. package/dist/json-durable-queue.js +5 -10
  77. package/dist/json-store.d.ts.map +1 -1
  78. package/dist/json-store.js +1 -1
  79. package/dist/local-roots.d.ts.map +1 -1
  80. package/dist/local-roots.js +2 -3
  81. package/dist/move-path-cleanup.d.ts +1 -1
  82. package/dist/move-path-cleanup.d.ts.map +1 -1
  83. package/dist/move-path-cleanup.js +4 -2
  84. package/dist/move-path.d.ts +9 -0
  85. package/dist/move-path.d.ts.map +1 -1
  86. package/dist/move-path.js +53 -16
  87. package/dist/native-binding.d.ts +2 -1
  88. package/dist/native-binding.d.ts.map +1 -1
  89. package/dist/native-operations.d.ts +0 -1
  90. package/dist/native-operations.d.ts.map +1 -1
  91. package/dist/native-operations.js +0 -10
  92. package/dist/native-pinned-write-windows.d.ts.map +1 -1
  93. package/dist/native-pinned-write-windows.js +8 -4
  94. package/dist/native-pinned-write.js +3 -3
  95. package/dist/native-staged-file.d.ts +10 -3
  96. package/dist/native-staged-file.d.ts.map +1 -1
  97. package/dist/native-staged-file.js +38 -13
  98. package/dist/opened-realpath.d.ts +12 -1
  99. package/dist/opened-realpath.d.ts.map +1 -1
  100. package/dist/opened-realpath.js +16 -14
  101. package/dist/output.d.ts.map +1 -1
  102. package/dist/output.js +26 -2
  103. package/dist/path-policy.js +3 -3
  104. package/dist/permissions.d.ts +1 -0
  105. package/dist/permissions.d.ts.map +1 -1
  106. package/dist/permissions.js +3 -0
  107. package/dist/pinned-write.d.ts +2 -2
  108. package/dist/pinned-write.d.ts.map +1 -1
  109. package/dist/pinned-write.js +17 -22
  110. package/dist/private-temp-workspace.js +1 -1
  111. package/dist/publish-file.js +2 -2
  112. package/dist/regular-file.js +8 -8
  113. package/dist/replace-file-copy-source.d.ts.map +1 -1
  114. package/dist/replace-file-copy-source.js +2 -7
  115. package/dist/replace-file-descriptor.d.ts.map +1 -1
  116. package/dist/replace-file-descriptor.js +11 -20
  117. package/dist/replace-file-temp-owner.d.ts.map +1 -1
  118. package/dist/replace-file-temp-owner.js +15 -14
  119. package/dist/replace-file.d.ts.map +1 -1
  120. package/dist/replace-file.js +21 -8
  121. package/dist/root-context.js +5 -5
  122. package/dist/root-errors.d.ts +1 -0
  123. package/dist/root-errors.d.ts.map +1 -1
  124. package/dist/root-errors.js +9 -0
  125. package/dist/root-file.d.ts.map +1 -1
  126. package/dist/root-file.js +17 -28
  127. package/dist/root-impl.d.ts +2 -1
  128. package/dist/root-impl.d.ts.map +1 -1
  129. package/dist/root-impl.js +46 -58
  130. package/dist/root-path-existing.d.ts.map +1 -1
  131. package/dist/root-path-existing.js +2 -3
  132. package/dist/root-path-symlink.d.ts.map +1 -1
  133. package/dist/root-path-symlink.js +2 -3
  134. package/dist/root-path.d.ts.map +1 -1
  135. package/dist/root-path.js +51 -97
  136. package/dist/root-paths.d.ts.map +1 -1
  137. package/dist/root-paths.js +16 -15
  138. package/dist/root-write-mode.d.ts +6 -0
  139. package/dist/root-write-mode.d.ts.map +1 -0
  140. package/dist/root-write-mode.js +61 -0
  141. package/dist/root-write-verification.js +8 -8
  142. package/dist/safe-path-segment.d.ts +1 -0
  143. package/dist/safe-path-segment.d.ts.map +1 -1
  144. package/dist/safe-path-segment.js +1 -1
  145. package/dist/secret-file.d.ts.map +1 -1
  146. package/dist/secret-file.js +4 -11
  147. package/dist/secret-read-async.d.ts.map +1 -1
  148. package/dist/secret-read-async.js +2 -10
  149. package/dist/secret-read-policy.d.ts +4 -0
  150. package/dist/secret-read-policy.d.ts.map +1 -1
  151. package/dist/secret-read-policy.js +12 -0
  152. package/dist/sibling-staged-file.d.ts.map +1 -1
  153. package/dist/sibling-staged-file.js +2 -7
  154. package/dist/sidecar-lock-policy.d.ts +0 -1
  155. package/dist/sidecar-lock-policy.d.ts.map +1 -1
  156. package/dist/sidecar-lock-policy.js +0 -4
  157. package/dist/strict-file-identity.d.ts +1 -1
  158. package/dist/strict-file-identity.d.ts.map +1 -1
  159. package/dist/string-coerce.d.ts +0 -9
  160. package/dist/string-coerce.d.ts.map +1 -1
  161. package/dist/string-coerce.js +0 -56
  162. package/dist/symlink-parents.d.ts.map +1 -1
  163. package/dist/symlink-parents.js +1 -2
  164. package/dist/temp-target.d.ts.map +1 -1
  165. package/dist/temp-target.js +1 -12
  166. package/docs/archive.md +57 -0
  167. package/docs/atomic.md +53 -19
  168. package/docs/reading.md +2 -0
  169. package/docs/root.md +10 -1
  170. package/docs/staged-file.md +6 -2
  171. package/docs/types.md +2 -1
  172. package/docs/writing.md +30 -2
  173. package/package.json +8 -8
  174. package/dist/archive-tar-extract.d.ts +0 -11
  175. package/dist/archive-tar-extract.d.ts.map +0 -1
  176. package/dist/archive-tar-extract.js +0 -49
  177. package/dist/fsync.d.ts +0 -2
  178. package/dist/fsync.d.ts.map +0 -1
  179. package/dist/fsync.js +0 -1
  180. package/dist/json-durable-queue-retirement.d.ts +0 -9
  181. package/dist/json-durable-queue-retirement.d.ts.map +0 -1
  182. package/dist/json-durable-queue-retirement.js +0 -126
  183. package/dist/json-durable-queue-transfer-lock.d.ts +0 -2
  184. package/dist/json-durable-queue-transfer-lock.d.ts.map +0 -1
  185. package/dist/json-durable-queue-transfer-lock.js +0 -19
  186. package/dist/mode.d.ts +0 -2
  187. package/dist/mode.d.ts.map +0 -1
  188. package/dist/mode.js +0 -3
  189. package/dist/output-sibling.d.ts +0 -8
  190. package/dist/output-sibling.d.ts.map +0 -1
  191. package/dist/output-sibling.js +0 -30
  192. package/dist/read-error.d.ts +0 -2
  193. package/dist/read-error.d.ts.map +0 -1
  194. package/dist/read-error.js +0 -11
  195. package/dist/short-path.d.ts +0 -2
  196. package/dist/short-path.d.ts.map +0 -1
  197. package/dist/short-path.js +0 -7
  198. package/dist/staged-file.d.ts +0 -10
  199. package/dist/staged-file.d.ts.map +0 -1
  200. package/dist/staged-file.js +0 -15
package/dist/root-path.js CHANGED
@@ -1,13 +1,11 @@
1
1
  import fs from "node:fs";
2
- import fsp from "node:fs/promises";
3
2
  import path from "node:path";
4
- import { formatErrorDetail } from "./error-detail.js";
3
+ import { formatErrorDetail, shortPath } from "./error-detail.js";
5
4
  import { FsSafeError } from "./errors.js";
6
5
  import { assertNoNulPathInput, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
7
6
  import { resolvePathViaExistingAncestor, resolvePathViaExistingAncestorSync, } from "./root-path-existing.js";
8
7
  import { resolveSymlinkHopPath, resolveSymlinkHopPathSync } from "./root-path-symlink.js";
9
8
  import { assertNoDriveRelativePathSegments } from "./safe-path-segment.js";
10
- import { shortPath } from "./short-path.js";
11
9
  export { resolvePathViaExistingAncestorSync } from "./root-path-existing.js";
12
10
  export const ROOT_PATH_ALIAS_POLICIES = {
13
11
  strict: Object.freeze({
@@ -120,12 +118,6 @@ function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
120
118
  const root = path.parse(filePath).root;
121
119
  assertNoDriveRelativePathSegments(filePath.slice(root.length).replaceAll("\\", "/"), label);
122
120
  }
123
- function isPromiseLike(value) {
124
- return Boolean(value &&
125
- (typeof value === "object" || typeof value === "function") &&
126
- "then" in value &&
127
- typeof value.then === "function");
128
- }
129
121
  function createLexicalTraversalState(params) {
130
122
  const rawAbsolutePath = params.params.absolutePath;
131
123
  const rawRelativePath = rawPathRelativeToRoot(params.rootPath, rawAbsolutePath);
@@ -205,26 +197,13 @@ function handleLexicalLstatFailure(context, error, missingFromIndex) {
205
197
  applyMissingSuffixToCanonicalCursor(context, missingFromIndex);
206
198
  return true;
207
199
  }
208
- function handleLexicalStatReadFailure(context, error, missingFromIndex) {
209
- if (handleLexicalLstatFailure(context, error, missingFromIndex)) {
210
- return null;
211
- }
212
- throw error;
213
- }
214
- function handleLexicalStatDisposition(context, params) {
215
- if (!params.isSymbolicLink) {
216
- advanceCanonicalCursorForSegment(context, params.segment);
200
+ function lexicalStatDisposition(params) {
201
+ if (!params.isSymbolicLink)
217
202
  return "continue";
218
- }
219
- if (context.resolveParams.rejectSymlinks === true && params.isLast) {
203
+ if (params.rejectSymlinks === true && params.isLast) {
220
204
  throw new FsSafeError("symlink", "symlink path component not allowed");
221
205
  }
222
- if (context.state.allowFinalSymlink && params.isLast) {
223
- context.state.preserveFinalSymlink = true;
224
- advanceCanonicalCursorForSegment(context, params.segment);
225
- return "break";
226
- }
227
- return "resolve-link";
206
+ return params.allowFinalSymlink && params.isLast ? "break" : "resolve-link";
228
207
  }
229
208
  function applyResolvedSymlinkHop(context, linkCanonical) {
230
209
  if (!isPathInside(context.rootCanonicalPath, linkCanonical)) {
@@ -237,70 +216,48 @@ function applyResolvedSymlinkHop(context, linkCanonical) {
237
216
  context.state.canonicalCursor = linkCanonical;
238
217
  context.state.lexicalCursor = linkCanonical;
239
218
  }
240
- function readLexicalStat(context, params) {
241
- try {
242
- const stat = params.read(context.state.lexicalCursor);
243
- if (isPromiseLike(stat)) {
244
- return Promise.resolve(stat).catch((error) => handleLexicalStatReadFailure(context, error, params.missingFromIndex));
245
- }
246
- return stat;
247
- }
248
- catch (error) {
249
- return handleLexicalStatReadFailure(context, error, params.missingFromIndex);
250
- }
251
- }
252
- function resolveAndApplySymlinkHop(context, params) {
253
- const linkCanonical = params.resolveLinkCanonical(context.state.lexicalCursor);
254
- if (isPromiseLike(linkCanonical)) {
255
- return Promise.resolve(linkCanonical).then((value) => {
256
- applyResolvedSymlinkHop(context, value);
257
- });
258
- }
259
- applyResolvedSymlinkHop(context, linkCanonical);
260
- }
261
219
  function applyParentTraversalStep(context) {
262
220
  context.state.lexicalCursor = path.resolve(context.state.lexicalCursor, "..");
263
221
  advanceCanonicalCursorForSegment(context, "..");
264
222
  }
265
- function* iterateLexicalTraversal(state) {
266
- for (let idx = 0; idx < state.segments.length; idx += 1) {
267
- const segment = state.segments[idx] ?? "";
268
- const isLast = idx === state.segments.length - 1;
269
- yield { idx, segment, isLast };
270
- }
271
- }
272
223
  async function resolveRootPathLexicalAsync(params) {
273
224
  const context = createLexicalTraversalContext(params);
274
225
  const { state } = context;
275
- for (const { idx, segment, isLast } of iterateLexicalTraversal(state)) {
226
+ for (let idx = 0; idx < state.segments.length; idx += 1) {
227
+ const segment = state.segments[idx] ?? "";
228
+ const isLast = idx === state.segments.length - 1;
276
229
  if (segment === "..") {
277
230
  applyParentTraversalStep(context);
278
231
  continue;
279
232
  }
280
233
  state.lexicalCursor = path.join(state.lexicalCursor, segment);
281
- const stat = await readLexicalStat(context, {
282
- missingFromIndex: idx,
283
- read: (cursor) => fsp.lstat(cursor),
284
- });
285
- if (!stat) {
286
- break;
234
+ let stat;
235
+ try {
236
+ stat = fs.lstatSync(state.lexicalCursor);
287
237
  }
288
- const disposition = handleLexicalStatDisposition(context, {
289
- isSymbolicLink: stat.isSymbolicLink(),
290
- segment,
238
+ catch (error) {
239
+ if (handleLexicalLstatFailure(context, error, idx))
240
+ break;
241
+ throw error;
242
+ }
243
+ const isSymbolicLink = stat.isSymbolicLink();
244
+ const disposition = lexicalStatDisposition({
245
+ isSymbolicLink,
291
246
  isLast,
247
+ rejectSymlinks: isSymbolicLink ? context.resolveParams.rejectSymlinks : undefined,
248
+ allowFinalSymlink: state.allowFinalSymlink,
292
249
  });
293
- if (disposition === "continue") {
250
+ if (disposition !== "resolve-link") {
251
+ state.preserveFinalSymlink = disposition === "break";
252
+ advanceCanonicalCursorForSegment(context, segment);
253
+ if (state.preserveFinalSymlink)
254
+ break;
294
255
  continue;
295
256
  }
296
- if (disposition === "break") {
297
- break;
298
- }
299
- await resolveAndApplySymlinkHop(context, {
300
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPath(cursor, {
301
- rejectUnresolved: context.resolveParams.rejectUnresolvedSymlinks,
302
- }),
257
+ const linkCanonical = await resolveSymlinkHopPath(state.lexicalCursor, {
258
+ rejectUnresolved: context.resolveParams.rejectUnresolvedSymlinks,
303
259
  });
260
+ applyResolvedSymlinkHop(context, linkCanonical);
304
261
  if (context.resolveParams.rejectSymlinks === true) {
305
262
  throw new FsSafeError("symlink", "symlink path component not allowed");
306
263
  }
@@ -319,36 +276,33 @@ function resolveRootPathLexicalSync(params) {
319
276
  continue;
320
277
  }
321
278
  state.lexicalCursor = path.join(state.lexicalCursor, segment);
322
- const maybeStat = readLexicalStat(context, {
323
- missingFromIndex: idx,
324
- read: (cursor) => fs.lstatSync(cursor),
325
- });
326
- if (isPromiseLike(maybeStat)) {
327
- throw new Error("Unexpected async lexical stat");
279
+ let stat;
280
+ try {
281
+ stat = fs.lstatSync(state.lexicalCursor);
328
282
  }
329
- const stat = maybeStat;
330
- if (!stat) {
331
- break;
283
+ catch (error) {
284
+ if (handleLexicalLstatFailure(context, error, idx))
285
+ break;
286
+ throw error;
332
287
  }
333
- const disposition = handleLexicalStatDisposition(context, {
334
- isSymbolicLink: stat.isSymbolicLink(),
335
- segment,
288
+ const isSymbolicLink = stat.isSymbolicLink();
289
+ const disposition = lexicalStatDisposition({
290
+ isSymbolicLink,
336
291
  isLast,
292
+ rejectSymlinks: isSymbolicLink ? context.resolveParams.rejectSymlinks : undefined,
293
+ allowFinalSymlink: state.allowFinalSymlink,
337
294
  });
338
- if (disposition === "continue") {
295
+ if (disposition !== "resolve-link") {
296
+ state.preserveFinalSymlink = disposition === "break";
297
+ advanceCanonicalCursorForSegment(context, segment);
298
+ if (state.preserveFinalSymlink)
299
+ break;
339
300
  continue;
340
301
  }
341
- if (disposition === "break") {
342
- break;
343
- }
344
- const maybeApplied = resolveAndApplySymlinkHop(context, {
345
- resolveLinkCanonical: (cursor) => resolveSymlinkHopPathSync(cursor, {
346
- rejectUnresolved: context.resolveParams.rejectUnresolvedSymlinks,
347
- }),
302
+ const linkCanonical = resolveSymlinkHopPathSync(state.lexicalCursor, {
303
+ rejectUnresolved: context.resolveParams.rejectUnresolvedSymlinks,
348
304
  });
349
- if (isPromiseLike(maybeApplied)) {
350
- throw new Error("Unexpected async symlink resolution");
351
- }
305
+ applyResolvedSymlinkHop(context, linkCanonical);
352
306
  if (context.resolveParams.rejectSymlinks === true) {
353
307
  throw new FsSafeError("symlink", "symlink path component not allowed");
354
308
  }
@@ -468,8 +422,8 @@ function buildResolvedRootPath(params) {
468
422
  async function getPathKind(absolutePath, preserveFinalSymlink) {
469
423
  try {
470
424
  const stat = preserveFinalSymlink
471
- ? await fsp.lstat(absolutePath)
472
- : await fsp.stat(absolutePath);
425
+ ? fs.lstatSync(absolutePath)
426
+ : fs.statSync(absolutePath);
473
427
  return { exists: true, kind: toResolvedKind(stat) };
474
428
  }
475
429
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import fsSync from "node:fs";
1
2
  import fs from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { formatErrorDetail } from "./error-detail.js";
@@ -16,7 +17,7 @@ function pathStaysWithinRoot(rootDir, candidatePath) {
16
17
  }
17
18
  async function resolveRealPathIfExists(targetPath) {
18
19
  try {
19
- return await fs.realpath(targetPath);
20
+ return fsSync.realpathSync.native(targetPath);
20
21
  }
21
22
  catch {
22
23
  return undefined;
@@ -24,11 +25,11 @@ async function resolveRealPathIfExists(targetPath) {
24
25
  }
25
26
  async function resolveTrustedRootRealPath(rootDir) {
26
27
  try {
27
- const rootLstat = await fs.lstat(rootDir);
28
+ const rootLstat = fsSync.lstatSync(rootDir);
28
29
  if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
29
30
  return undefined;
30
31
  }
31
- return await fs.realpath(rootDir);
32
+ return fsSync.realpathSync.native(rootDir);
32
33
  }
33
34
  catch {
34
35
  return undefined;
@@ -36,7 +37,7 @@ async function resolveTrustedRootRealPath(rootDir) {
36
37
  }
37
38
  async function validateCanonicalPathWithinRoot(params) {
38
39
  try {
39
- const candidateLstat = await fs.lstat(params.candidatePath);
40
+ const candidateLstat = fsSync.lstatSync(params.candidatePath);
40
41
  if (candidateLstat.isSymbolicLink()) {
41
42
  return "invalid";
42
43
  }
@@ -49,7 +50,7 @@ async function validateCanonicalPathWithinRoot(params) {
49
50
  if (params.expect === "file" && candidateLstat.nlink > 1) {
50
51
  return "invalid";
51
52
  }
52
- const candidateRealPath = await fs.realpath(params.candidatePath);
53
+ const candidateRealPath = fsSync.realpathSync.native(params.candidatePath);
53
54
  return isPathInside(params.rootRealPath, candidateRealPath) ? "ok" : "invalid";
54
55
  }
55
56
  catch (err) {
@@ -109,7 +110,7 @@ async function resolveNearestExistingPath(targetPath) {
109
110
  let current = path.resolve(targetPath);
110
111
  while (true) {
111
112
  try {
112
- await fs.lstat(current);
113
+ fsSync.lstatSync(current);
113
114
  return current;
114
115
  }
115
116
  catch (err) {
@@ -133,7 +134,7 @@ async function assertNoSymlinkSegments(params) {
133
134
  for (const segment of relative.split(path.sep).filter(Boolean)) {
134
135
  current = path.join(current, segment);
135
136
  try {
136
- const stat = await fs.lstat(current);
137
+ const stat = fsSync.lstatSync(current);
137
138
  if (stat.isSymbolicLink()) {
138
139
  throw new FsSafeError("symlink", `Invalid path: must not traverse symlinks within ${params.scopeLabel}`);
139
140
  }
@@ -163,14 +164,14 @@ export async function ensureDirectoryWithinRoot(params) {
163
164
  return lexical;
164
165
  const rootDir = path.resolve(params.rootDir);
165
166
  const targetPath = lexical.path;
166
- const rootStat = await fs.lstat(rootDir);
167
+ const rootStat = fsSync.lstatSync(rootDir);
167
168
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
168
169
  return invalidPath(scopeLabel);
169
170
  }
170
171
  await assertNoSymlinkSegments({ rootDir, targetPath, scopeLabel });
171
- const rootReal = await fs.realpath(rootDir);
172
+ const rootReal = fsSync.realpathSync.native(rootDir);
172
173
  const nearestExistingPath = await resolveNearestExistingPath(targetPath);
173
- const nearestExistingReal = await fs.realpath(nearestExistingPath);
174
+ const nearestExistingReal = fsSync.realpathSync.native(nearestExistingPath);
174
175
  if (!isPathInside(rootReal, nearestExistingReal)) {
175
176
  return invalidPath(scopeLabel);
176
177
  }
@@ -180,7 +181,7 @@ export async function ensureDirectoryWithinRoot(params) {
180
181
  current = path.join(current, segment);
181
182
  while (true) {
182
183
  try {
183
- const stat = await fs.lstat(current);
184
+ const stat = fsSync.lstatSync(current);
184
185
  if (stat.isSymbolicLink() || !stat.isDirectory()) {
185
186
  return invalidPath(scopeLabel);
186
187
  }
@@ -201,12 +202,12 @@ export async function ensureDirectoryWithinRoot(params) {
201
202
  }
202
203
  }
203
204
  }
204
- const currentReal = await fs.realpath(current);
205
+ const currentReal = fsSync.realpathSync.native(current);
205
206
  if (!isPathInside(rootReal, currentReal)) {
206
207
  return invalidPath(scopeLabel);
207
208
  }
208
209
  }
209
- const targetReal = await fs.realpath(targetPath);
210
+ const targetReal = fsSync.realpathSync.native(targetPath);
210
211
  if (!isPathInside(rootReal, targetReal)) {
211
212
  return invalidPath(scopeLabel);
212
213
  }
@@ -308,7 +309,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
308
309
  return lexicalPathResult;
309
310
  }
310
311
  try {
311
- const resolvedExistingPath = await fs.realpath(raw);
312
+ const resolvedExistingPath = fsSync.realpathSync.native(raw);
312
313
  const relativePath = path.relative(rootRealPath, resolvedExistingPath);
313
314
  if (!isInRoot(relativePath)) {
314
315
  return lexicalPathResult;
@@ -350,7 +351,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
350
351
  scopeLabel: params.scopeLabel,
351
352
  });
352
353
  const existingPath = await resolveNearestExistingPath(pathResult.fallbackPath);
353
- const existingRealPath = await fs.realpath(existingPath);
354
+ const existingRealPath = fsSync.realpathSync.native(existingPath);
354
355
  if (!isPathInside(rootRealPath, existingRealPath)) {
355
356
  return invalidPath(params.scopeLabel);
356
357
  }
@@ -0,0 +1,6 @@
1
+ export declare function inheritWriteTargetMode(params: {
2
+ targetPath: string;
3
+ rootWithSep: string;
4
+ requestedMode?: number;
5
+ }): Promise<number>;
6
+ //# sourceMappingURL=root-write-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AAUA,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwClB"}
@@ -0,0 +1,61 @@
1
+ import fsSync from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { FsSafeError } from "./errors.js";
4
+ import { sameFileIdentity } from "./file-identity.js";
5
+ import { isNotFoundPathError, isPathInside } from "./path.js";
6
+ import { resolveReadOpenFlags } from "./read-open-flags.js";
7
+ import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
8
+ import { inspectFileIdentity } from "./strict-file-identity.js";
9
+ // The caller has already resolved and guarded this write target.
10
+ export async function inheritWriteTargetMode(params) {
11
+ try {
12
+ const existing = await inspectFileIdentity(() => fsSync.lstatSync(params.targetPath, { bigint: true }));
13
+ if (existing.isSymbolicLink())
14
+ throw new FsSafeError("path-alias", "path alias escape blocked");
15
+ if (!existing.isFile())
16
+ throw new FsSafeError("not-file", "not a file");
17
+ if (existing.nlink > 1n)
18
+ throw hardlinkedPathNotAllowedError();
19
+ if (!isPathInside(params.rootWithSep, params.targetPath))
20
+ throw outsideWorkspaceError();
21
+ // Preserve read-open admission of the pre-existing destination. access(2)
22
+ // is not equivalent: it ignores ACLs on Windows and capabilities on Linux.
23
+ const handle = await fs.open(params.targetPath, resolveReadOpenFlags());
24
+ try {
25
+ // Bind admission to the inode whose metadata is inherited.
26
+ if (!sameFileIdentity(fsSync.fstatSync(handle.fd, { bigint: true }), existing)) {
27
+ throw new FsSafeError("path-mismatch", "write target changed during mode inheritance");
28
+ }
29
+ }
30
+ finally {
31
+ await handle.close().catch(() => undefined);
32
+ }
33
+ try {
34
+ // A parent can change after guarded resolution. Do not inherit metadata
35
+ // from an outside inode, even if the parent is restored before publication.
36
+ const realPath = fsSync.realpathSync.native(params.targetPath);
37
+ if (!isPathInside(params.rootWithSep, realPath))
38
+ throw outsideWorkspaceError();
39
+ await inspectFileIdentity(async () => {
40
+ const current = fsSync.statSync(realPath, { bigint: true });
41
+ if (!current.isFile())
42
+ throw new FsSafeError("not-file", "not a file");
43
+ if (current.nlink > 1n)
44
+ throw hardlinkedPathNotAllowedError();
45
+ return current;
46
+ }, existing);
47
+ }
48
+ catch (error) {
49
+ if (isNotFoundPathError(error)) {
50
+ throw new FsSafeError("path-mismatch", "write target changed during mode inheritance", { cause: error });
51
+ }
52
+ throw error;
53
+ }
54
+ return params.requestedMode ?? Number(existing.mode & 511n);
55
+ }
56
+ catch (error) {
57
+ if (!isNotFoundPathError(error))
58
+ throw error;
59
+ return params.requestedMode ?? 0o600;
60
+ }
61
+ }
@@ -46,16 +46,16 @@ export async function verifyAtomicWriteResult(params) {
46
46
  try {
47
47
  // This descriptor remains owned by the writer, even when final mode forbids opens.
48
48
  const stat = assertDescriptor();
49
- assertPath(await fs.lstat(params.targetPath, { bigint: true }));
50
- const realPath = await resolveOpenedFileRealPathForFd(params.fd, stat, params.targetPath);
51
- assertPath(await fs.stat(realPath, { bigint: true }));
49
+ assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
50
+ const { realPath } = await resolveOpenedFileRealPathForFd(params.fd, stat, params.targetPath);
51
+ assertPath(fsSync.statSync(realPath, { bigint: true }));
52
52
  if (!isPathInside(params.root.rootWithSep, realPath)) {
53
53
  throw outsideWorkspaceError();
54
54
  }
55
55
  await assertAsyncDirectoryGuard(params.parentGuard);
56
56
  await assertRootIdentityCurrent(params.root);
57
57
  // Recheck after canonical resolution and directory checks, including late links.
58
- assertPath(await fs.lstat(params.targetPath, { bigint: true }));
58
+ assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
59
59
  assertDescriptor();
60
60
  if (needsPathOpen) {
61
61
  // A retained fd cannot prove that an opaque Windows pathname still names it.
@@ -73,15 +73,15 @@ export async function verifyAtomicWriteResult(params) {
73
73
  });
74
74
  try {
75
75
  const reopenedStat = assertDescriptor(opened.fd);
76
- assertPath(await fs.lstat(params.targetPath, { bigint: true }));
77
- const reopenedPath = await resolveOpenedFileRealPathForFd(opened.fd, reopenedStat, params.targetPath);
78
- assertPath(await fs.stat(reopenedPath, { bigint: true }));
76
+ assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
77
+ const { realPath: reopenedPath } = await resolveOpenedFileRealPathForFd(opened.fd, reopenedStat, params.targetPath);
78
+ assertPath(fsSync.statSync(reopenedPath, { bigint: true }));
79
79
  if (!isPathInside(params.root.rootWithSep, reopenedPath)) {
80
80
  throw outsideWorkspaceError();
81
81
  }
82
82
  await assertAsyncDirectoryGuard(params.parentGuard);
83
83
  await assertRootIdentityCurrent(params.root);
84
- assertPath(await fs.lstat(params.targetPath, { bigint: true }));
84
+ assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
85
85
  assertDescriptor(opened.fd);
86
86
  }
87
87
  finally {
@@ -4,6 +4,7 @@ export type SafePathSegmentOptions = {
4
4
  };
5
5
  export declare function isDriveRelativePath(value: string): boolean;
6
6
  export declare function assertNoDriveRelativePathSegments(value: string, label: string): string;
7
+ export declare function trimHyphenEdges(value: string): string;
7
8
  export declare function isSafePathSegment(segment: string, options?: SafePathSegmentOptions): boolean;
8
9
  export declare function assertSafePathSegment(segment: string, options?: SafePathSegmentOptions): string;
9
10
  export declare function sanitizeSafePathSegment(value: string, fallback: string, options?: SafePathSegmentOptions): string;
@@ -1 +1 @@
1
- {"version":3,"file":"safe-path-segment.d.ts","sourceRoot":"","sources":["../src/safe-path-segment.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtF;AAcD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAaT;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAUR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAWR;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAeR"}
1
+ {"version":3,"file":"safe-path-segment.d.ts","sourceRoot":"","sources":["../src/safe-path-segment.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,sBAAsB,GAAG;IACnC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKtF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUrD;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAaT;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAUR;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAWR;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAeR"}
@@ -14,7 +14,7 @@ export function assertNoDriveRelativePathSegments(value, label) {
14
14
  }
15
15
  return value;
16
16
  }
17
- function trimHyphenEdges(value) {
17
+ export function trimHyphenEdges(value) {
18
18
  let start = 0;
19
19
  let end = value.length;
20
20
  while (start < end && value.charCodeAt(start) === HYPHEN_CHAR_CODE) {
@@ -1 +1 @@
1
- {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAM/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAUhJ,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAyER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAYF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAuBD;AAuDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
1
+ {"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAK/C,OAAO,EAAkF,KAAK,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAUhJ,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAIjC,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAkER;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAUpB;AA8JD,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAYF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,IAAI,CAAC,qBAAqB,EAAE,SAAS,CAAC,GAC7C,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC5C,WAAW,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC,CAuBD;AAuDD,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
@@ -3,7 +3,6 @@ import fsp from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
5
5
  import { readFileDescriptorBoundedSync } from "./bounded-read.js";
6
- import { normalizeMaxBytes } from "./byte-budget.js";
7
6
  import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
8
7
  import { pinNodeDirectoryForMode } from "./directory-mode-node.js";
9
8
  import { assertOwnedDirectory } from "./directory-mode-owner.js";
@@ -14,19 +13,13 @@ import { openPinnedFileSync } from "./pinned-open.js";
14
13
  import { runPinnedWriteHelper } from "./pinned-write.js";
15
14
  import { ensureTrailingSep } from "./root-context.js";
16
15
  import { verifyAtomicWriteResult } from "./root-write-verification.js";
17
- import { assertSecretFilePreview, DEFAULT_SECRET_FILE_MAX_BYTES, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
16
+ import { assertSecretFilePreview, resolveSecretReadPolicy, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
18
17
  import { inspectFileIdentity, inspectFileIdentitySync } from "./strict-file-identity.js";
19
18
  import { serializePathWrite } from "./write-queue.js";
20
19
  export const PRIVATE_SECRET_DIR_MODE = 0o700;
21
20
  export const PRIVATE_SECRET_FILE_MODE = 0o600;
22
21
  export function readSecretFileSync(filePath, label, options = {}) {
23
- const resolvedPath = resolveHomeRelativePath(filePath.trim());
24
- if (!resolvedPath) {
25
- throw new FsSafeError("invalid-path", `${label} file path is empty.`, { cause: undefined });
26
- }
27
- const maxBytes = normalizeMaxBytes(options.maxBytes, {
28
- defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
29
- });
22
+ const { resolvedPath, maxBytes } = resolveSecretReadPolicy(filePath, label, options);
30
23
  function inspectInput(symlinkMessage) {
31
24
  const stat = options.rejectSymlink
32
25
  ? fs.lstatSync(resolvedPath, { bigint: true })
@@ -147,7 +140,7 @@ async function enforcePrivateDirectoryMode(params) {
147
140
  }
148
141
  async function inspectPrivateDirectory(directory, kind) {
149
142
  return await inspectFileIdentity(async () => {
150
- const stat = await fsp.lstat(directory, { bigint: true });
143
+ const stat = fs.lstatSync(directory, { bigint: true });
151
144
  if (stat.isSymbolicLink()) {
152
145
  throw new Error(`Private secret ${kind} ${directory} must not be a symlink.`);
153
146
  }
@@ -267,7 +260,7 @@ export async function prepareSecretFileWrite(params) {
267
260
  async function materializeSecretFileAtomic(params, createOnly) {
268
261
  const { mode, rootGuard, parentGuard, fileName, finalFilePath } = await prepareSecretFileWrite(params);
269
262
  try {
270
- const stat = await fsp.lstat(finalFilePath);
263
+ const stat = fs.lstatSync(finalFilePath);
271
264
  if (createOnly) {
272
265
  throw new FsSafeError("secret-exists", `Private secret file ${finalFilePath} already exists.`);
273
266
  }
@@ -1 +1 @@
1
- {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AASA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAgEjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
1
+ {"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAOA,OAAO,EAML,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CA0DjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B"}
@@ -1,21 +1,13 @@
1
1
  import fsSync from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import { readFileHandleBounded } from "./bounded-read.js";
4
- import { normalizeMaxBytes } from "./byte-budget.js";
5
4
  import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
6
5
  import { FsSafeError } from "./errors.js";
7
- import { resolveHomeRelativePath } from "./home-dir.js";
8
6
  import { resolveReadOpenFlags } from "./read-open-flags.js";
9
7
  import { inspectFileIdentity } from "./strict-file-identity.js";
10
- import { assertSecretFilePreview, DEFAULT_SECRET_FILE_MAX_BYTES, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
8
+ import { assertSecretFilePreview, resolveSecretReadPolicy, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
11
9
  export async function readSecretFile(filePath, label, options = {}) {
12
- const resolvedPath = resolveHomeRelativePath(filePath.trim());
13
- if (!resolvedPath) {
14
- throw new FsSafeError("invalid-path", `${label} file path is empty.`, { cause: undefined });
15
- }
16
- const maxBytes = normalizeMaxBytes(options.maxBytes, {
17
- defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
18
- });
10
+ const { resolvedPath, maxBytes } = resolveSecretReadPolicy(filePath, label, options);
19
11
  async function inspectInput(symlinkMessage) {
20
12
  const stat = options.rejectSymlink
21
13
  ? await fs.lstat(resolvedPath, { bigint: true })
@@ -10,4 +10,8 @@ export declare function secretPathErrorCode(error: unknown): FsSafeErrorCode;
10
10
  export declare function secretReadError(code: FsSafeErrorCode, action: "inspect" | "read", label: string, resolvedPath: string, error: unknown): FsSafeError;
11
11
  export declare function assertSecretFilePreview(stat: BigIntStats, label: string, resolvedPath: string, maxBytes: number, rejectHardlinks: boolean): void;
12
12
  export declare function trimSecretFileContent(raw: string, label: string, resolvedPath: string): string;
13
+ export declare function resolveSecretReadPolicy(filePath: string, label: string, options: SecretFileReadOptions): {
14
+ resolvedPath: string;
15
+ maxBytes: number;
16
+ };
13
17
  //# sourceMappingURL=secret-read-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"secret-read-policy.d.ts","sourceRoot":"","sources":["../src/secret-read-policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhE,eAAO,MAAM,6BAA6B,QAAY,CAAC;AAEvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAGnE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,OAAO,GACb,WAAW,CAGb;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,OAAO,GACvB,IAAI,CAUN;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAM9F"}
1
+ {"version":3,"file":"secret-read-policy.d.ts","sourceRoot":"","sources":["../src/secret-read-policy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAEhE,eAAO,MAAM,6BAA6B,QAAY,CAAC;AAEvD,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAGnE;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,SAAS,GAAG,MAAM,EAC1B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,OAAO,GACb,WAAW,CAGb;AAED,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,OAAO,GACvB,IAAI,CAUN;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAM9F;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG;IACxG,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB,CASA"}
@@ -1,3 +1,5 @@
1
+ import { normalizeMaxBytes } from "./byte-budget.js";
2
+ import { resolveHomeRelativePath } from "./home-dir.js";
1
3
  import { FsSafeError } from "./errors.js";
2
4
  export const DEFAULT_SECRET_FILE_MAX_BYTES = 16 * 1024;
3
5
  export function secretPathErrorCode(error) {
@@ -26,3 +28,13 @@ export function trimSecretFileContent(raw, label, resolvedPath) {
26
28
  }
27
29
  return secret;
28
30
  }
31
+ export function resolveSecretReadPolicy(filePath, label, options) {
32
+ const resolvedPath = resolveHomeRelativePath(filePath.trim());
33
+ if (!resolvedPath) {
34
+ throw new FsSafeError("invalid-path", `${label} file path is empty.`, { cause: undefined });
35
+ }
36
+ const maxBytes = normalizeMaxBytes(options.maxBytes, {
37
+ defaultValue: DEFAULT_SECRET_FILE_MAX_BYTES,
38
+ });
39
+ return { resolvedPath, maxBytes };
40
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAkCA,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CA6G3C"}
1
+ {"version":3,"file":"sibling-staged-file.d.ts","sourceRoot":"","sources":["../src/sibling-staged-file.ts"],"names":[],"mappings":"AAmCA,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE;IACpD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC,CAyG3C"}