@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.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +8 -7
- package/dist/advanced.d.ts +2 -2
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +2 -2
- package/dist/archive-kind.d.ts +1 -0
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +6 -0
- package/dist/archive-limits.d.ts +0 -1
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-limits.js +0 -3
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +20 -76
- package/dist/archive-parser-errors.d.ts +2 -0
- package/dist/archive-parser-errors.d.ts.map +1 -0
- package/dist/archive-parser-errors.js +12 -0
- package/dist/archive-plan.d.ts +20 -0
- package/dist/archive-plan.d.ts.map +1 -0
- package/dist/archive-plan.js +55 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +7 -13
- package/dist/archive-tar-inspect.d.ts +7 -0
- package/dist/archive-tar-inspect.d.ts.map +1 -0
- package/dist/archive-tar-inspect.js +59 -0
- package/dist/archive-tar-wasm.d.ts +1 -1
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +5 -8
- package/dist/archive-tar.d.ts +3 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +12 -49
- package/dist/archive.d.ts +1 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +54 -33
- package/dist/atomic.d.ts +1 -1
- package/dist/atomic.d.ts.map +1 -1
- package/dist/atomic.js +1 -1
- package/dist/bounded-read-stream.d.ts +1 -5
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +5 -13
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +31 -4
- package/dist/byte-budget.d.ts +0 -1
- package/dist/byte-budget.d.ts.map +1 -1
- package/dist/byte-budget.js +1 -1
- package/dist/directory-durability.js +5 -5
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +5 -6
- package/dist/directory-mode-owner.d.ts.map +1 -1
- package/dist/directory-mode-owner.js +2 -0
- package/dist/error-detail.d.ts +1 -0
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +6 -0
- package/dist/file-store-boundary.d.ts +1 -1
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -14
- package/dist/file-store-prune.js +17 -8
- package/dist/file-store.js +3 -3
- package/dist/file-sync.d.ts +5 -0
- package/dist/file-sync.d.ts.map +1 -0
- package/dist/file-sync.js +19 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -4
- package/dist/guarded-mutation.d.ts +3 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +5 -1
- package/dist/home-dir.d.ts +0 -10
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +0 -28
- package/dist/json-durable-queue-ownership.d.ts +1 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +139 -18
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +5 -10
- package/dist/json-store.d.ts.map +1 -1
- package/dist/json-store.js +1 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +2 -3
- package/dist/move-path-cleanup.d.ts +1 -1
- package/dist/move-path-cleanup.d.ts.map +1 -1
- package/dist/move-path-cleanup.js +4 -2
- package/dist/move-path.d.ts +9 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +53 -16
- package/dist/native-binding.d.ts +2 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +0 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -10
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +8 -4
- package/dist/native-pinned-write.js +3 -3
- package/dist/native-staged-file.d.ts +10 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +38 -13
- package/dist/opened-realpath.d.ts +12 -1
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +16 -14
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +26 -2
- package/dist/path-policy.js +3 -3
- package/dist/permissions.d.ts +1 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +3 -0
- package/dist/pinned-write.d.ts +2 -2
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +17 -22
- package/dist/private-temp-workspace.js +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/regular-file.js +8 -8
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +2 -7
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +11 -20
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +15 -14
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +21 -8
- package/dist/root-context.js +5 -5
- package/dist/root-errors.d.ts +1 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +9 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +17 -28
- package/dist/root-impl.d.ts +2 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +46 -58
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +2 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +2 -3
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +51 -97
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +16 -15
- package/dist/root-write-mode.d.ts +6 -0
- package/dist/root-write-mode.d.ts.map +1 -0
- package/dist/root-write-mode.js +61 -0
- package/dist/root-write-verification.js +8 -8
- package/dist/safe-path-segment.d.ts +1 -0
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +4 -11
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -10
- package/dist/secret-read-policy.d.ts +4 -0
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +12 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +2 -7
- package/dist/sidecar-lock-policy.d.ts +0 -1
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +0 -4
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/string-coerce.d.ts +0 -9
- package/dist/string-coerce.d.ts.map +1 -1
- package/dist/string-coerce.js +0 -56
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +1 -2
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +1 -12
- package/docs/archive.md +57 -0
- package/docs/atomic.md +53 -19
- package/docs/reading.md +2 -0
- package/docs/root.md +10 -1
- package/docs/staged-file.md +6 -2
- package/docs/types.md +2 -1
- package/docs/writing.md +30 -2
- package/package.json +8 -8
- package/dist/archive-tar-extract.d.ts +0 -11
- package/dist/archive-tar-extract.d.ts.map +0 -1
- package/dist/archive-tar-extract.js +0 -49
- package/dist/fsync.d.ts +0 -2
- package/dist/fsync.d.ts.map +0 -1
- package/dist/fsync.js +0 -1
- package/dist/json-durable-queue-retirement.d.ts +0 -9
- package/dist/json-durable-queue-retirement.d.ts.map +0 -1
- package/dist/json-durable-queue-retirement.js +0 -126
- package/dist/json-durable-queue-transfer-lock.d.ts +0 -2
- package/dist/json-durable-queue-transfer-lock.d.ts.map +0 -1
- package/dist/json-durable-queue-transfer-lock.js +0 -19
- package/dist/mode.d.ts +0 -2
- package/dist/mode.d.ts.map +0 -1
- package/dist/mode.js +0 -3
- package/dist/output-sibling.d.ts +0 -8
- package/dist/output-sibling.d.ts.map +0 -1
- package/dist/output-sibling.js +0 -30
- package/dist/read-error.d.ts +0 -2
- package/dist/read-error.d.ts.map +0 -1
- package/dist/read-error.js +0 -11
- package/dist/short-path.d.ts +0 -2
- package/dist/short-path.d.ts.map +0 -1
- package/dist/short-path.js +0 -7
- package/dist/staged-file.d.ts +0 -10
- package/dist/staged-file.d.ts.map +0 -1
- 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
|
|
209
|
-
if (
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
});
|
|
285
|
-
if (!stat) {
|
|
286
|
-
break;
|
|
234
|
+
let stat;
|
|
235
|
+
try {
|
|
236
|
+
stat = fs.lstatSync(state.lexicalCursor);
|
|
287
237
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
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
|
-
|
|
297
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (handleLexicalLstatFailure(context, error, idx))
|
|
285
|
+
break;
|
|
286
|
+
throw error;
|
|
332
287
|
}
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
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
|
-
|
|
342
|
-
|
|
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
|
-
|
|
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
|
-
?
|
|
472
|
-
:
|
|
425
|
+
? fs.lstatSync(absolutePath)
|
|
426
|
+
: fs.statSync(absolutePath);
|
|
473
427
|
return { exists: true, kind: toResolvedKind(stat) };
|
|
474
428
|
}
|
|
475
429
|
catch (error) {
|
package/dist/root-paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"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"}
|
package/dist/root-paths.js
CHANGED
|
@@ -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
|
|
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 =
|
|
28
|
+
const rootLstat = fsSync.lstatSync(rootDir);
|
|
28
29
|
if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
|
|
29
30
|
return undefined;
|
|
30
31
|
}
|
|
31
|
-
return
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
172
|
+
const rootReal = fsSync.realpathSync.native(rootDir);
|
|
172
173
|
const nearestExistingPath = await resolveNearestExistingPath(targetPath);
|
|
173
|
-
const nearestExistingReal =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
354
|
+
const existingRealPath = fsSync.realpathSync.native(existingPath);
|
|
354
355
|
if (!isPathInside(rootRealPath, existingRealPath)) {
|
|
355
356
|
return invalidPath(params.scopeLabel);
|
|
356
357
|
}
|
|
@@ -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(
|
|
50
|
-
const realPath = await resolveOpenedFileRealPathForFd(params.fd, stat, params.targetPath);
|
|
51
|
-
assertPath(
|
|
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(
|
|
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(
|
|
77
|
-
const reopenedPath = await resolveOpenedFileRealPathForFd(opened.fd, reopenedStat, params.targetPath);
|
|
78
|
-
assertPath(
|
|
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(
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/secret-file.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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":"
|
|
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,
|
|
8
|
+
import { assertSecretFilePreview, resolveSecretReadPolicy, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
11
9
|
export async function readSecretFile(filePath, label, options = {}) {
|
|
12
|
-
const resolvedPath =
|
|
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":"
|
|
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":"
|
|
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"}
|