@openclaw/fs-safe 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +134 -0
- package/LICENSE +1 -0
- package/README.md +52 -4
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +3 -2
- package/dist/advanced.d.ts +5 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +5 -0
- package/dist/archive-crc32.d.ts.map +1 -1
- package/dist/archive-crc32.js +6 -1
- package/dist/archive-deadline.d.ts.map +1 -1
- package/dist/archive-deadline.js +3 -4
- package/dist/archive-durability.d.ts +6 -6
- package/dist/archive-durability.d.ts.map +1 -1
- package/dist/archive-durability.js +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -5
- package/dist/archive-gzip-tail.d.ts +3 -0
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +23 -3
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +4 -2
- package/dist/archive-merge.d.ts +5 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +16 -13
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -6
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +83 -74
- package/dist/archive-staging.d.ts +6 -3
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +42 -22
- package/dist/archive-tar-stream.d.ts +11 -4
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +23 -10
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +18 -14
- package/dist/archive-zip-admission.d.ts +1 -1
- package/dist/archive-zip-admission.d.ts.map +1 -1
- package/dist/archive-zip-admission.js +48 -12
- package/dist/archive-zip-loader.d.ts +6 -0
- package/dist/archive-zip-loader.d.ts.map +1 -0
- package/dist/archive-zip-loader.js +38 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +13 -8
- package/dist/archive-zip-preflight.d.ts +2 -3
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +2 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +12 -10
- package/dist/bounded-read.d.ts +12 -0
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +82 -45
- package/dist/clone-metadata.d.ts +19 -0
- package/dist/clone-metadata.d.ts.map +1 -0
- package/dist/clone-metadata.js +32 -0
- package/dist/copy-file-input.d.ts +22 -0
- package/dist/copy-file-input.d.ts.map +1 -0
- package/dist/copy-file-input.js +69 -0
- package/dist/copy-policy.d.ts +3 -0
- package/dist/copy-policy.d.ts.map +1 -0
- package/dist/copy-policy.js +8 -0
- package/dist/copy-publication.d.ts +10 -1
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +27 -0
- package/dist/copy-tree-portable.d.ts +9 -0
- package/dist/copy-tree-portable.d.ts.map +1 -0
- package/dist/copy-tree-portable.js +222 -0
- package/dist/copy.d.ts +16 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +125 -0
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +5 -4
- package/dist/directory-guard.d.ts +11 -1
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +53 -11
- package/dist/durability.d.ts +1 -1
- package/dist/durability.d.ts.map +1 -1
- package/dist/durability.js +1 -1
- package/dist/error-detail.d.ts.map +1 -1
- package/dist/error-detail.js +4 -1
- package/dist/file-handle-transfer.d.ts +14 -0
- package/dist/file-handle-transfer.d.ts.map +1 -0
- package/dist/file-handle-transfer.js +64 -0
- package/dist/file-hash.d.ts +9 -2
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +135 -39
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +8 -4
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +7 -5
- package/dist/file-store-path.d.ts +3 -0
- package/dist/file-store-path.d.ts.map +1 -0
- package/dist/file-store-path.js +27 -0
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +6 -4
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +56 -44
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +2 -18
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +4 -13
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +4 -2
- package/dist/guarded-mutation.d.ts +2 -0
- package/dist/guarded-mutation.d.ts.map +1 -1
- package/dist/guarded-mutation.js +8 -4
- package/dist/guest-dispatch-python.d.ts +2 -0
- package/dist/guest-dispatch-python.d.ts.map +1 -0
- package/dist/guest-dispatch-python.js +117 -0
- package/dist/guest-native-python.d.ts +4 -0
- package/dist/guest-native-python.d.ts.map +1 -0
- package/dist/guest-native-python.js +135 -0
- package/dist/guest.d.ts +9 -0
- package/dist/guest.d.ts.map +1 -0
- package/dist/guest.js +413 -0
- package/dist/home-dir.d.ts.map +1 -1
- package/dist/home-dir.js +9 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +5 -8
- package/dist/json-durable-queue-directory.js +3 -3
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +19 -18
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +2 -1
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +24 -25
- package/dist/move-path-stage.d.ts +8 -0
- package/dist/move-path-stage.d.ts.map +1 -0
- package/dist/move-path-stage.js +56 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +30 -34
- package/dist/mutation-authority.d.ts +9 -0
- package/dist/mutation-authority.d.ts.map +1 -0
- package/dist/mutation-authority.js +36 -0
- package/dist/native-binding.d.ts +29 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +11 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +19 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +3 -1
- package/dist/native-staged-file.d.ts +3 -3
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +35 -8
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +5 -4
- package/dist/output.d.ts +2 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -0
- package/dist/overwrite-file-handle.d.ts +8 -0
- package/dist/overwrite-file-handle.d.ts.map +1 -0
- package/dist/overwrite-file-handle.js +42 -0
- package/dist/path-case.d.ts +7 -0
- package/dist/path-case.d.ts.map +1 -0
- package/dist/path-case.js +136 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +5 -2
- package/dist/permissions-windows.d.ts +1 -1
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +85 -53
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +3 -1
- package/dist/pinned-operation.js +1 -1
- package/dist/pinned-write.d.ts +7 -3
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +51 -30
- package/dist/positional-read.d.ts +9 -0
- package/dist/positional-read.d.ts.map +1 -0
- package/dist/positional-read.js +36 -0
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +6 -4
- package/dist/publish-copy-stage.d.ts +13 -0
- package/dist/publish-copy-stage.d.ts.map +1 -0
- package/dist/publish-copy-stage.js +47 -0
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +6 -5
- package/dist/realpath.d.ts +4 -0
- package/dist/realpath.d.ts.map +1 -0
- package/dist/realpath.js +43 -0
- package/dist/recursive-mkdir-path.d.ts +3 -0
- package/dist/recursive-mkdir-path.d.ts.map +1 -0
- package/dist/recursive-mkdir-path.js +8 -0
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +2 -1
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +23 -31
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +7 -12
- package/dist/replace-file-mode.d.ts +3 -0
- package/dist/replace-file-mode.d.ts.map +1 -0
- package/dist/replace-file-mode.js +10 -0
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +6 -2
- package/dist/replace-file.d.ts +1 -0
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +14 -8
- package/dist/root-context.d.ts +3 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +53 -28
- package/dist/root-create-input.d.ts +10 -0
- package/dist/root-create-input.d.ts.map +1 -0
- package/dist/root-create-input.js +80 -0
- package/dist/root-directory-list.d.ts +26 -0
- package/dist/root-directory-list.d.ts.map +1 -0
- package/dist/root-directory-list.js +219 -0
- package/dist/root-entries.d.ts +11 -0
- package/dist/root-entries.d.ts.map +1 -0
- package/dist/root-entries.js +61 -0
- package/dist/root-errors.d.ts +6 -5
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +16 -12
- package/dist/root-file.d.ts +2 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +12 -4
- package/dist/root-impl.d.ts +17 -51
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +358 -234
- package/dist/root-options.d.ts +77 -0
- package/dist/root-options.d.ts.map +1 -0
- package/dist/root-options.js +18 -0
- package/dist/root-path-existing.d.ts +5 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +59 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +3 -2
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +74 -162
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +13 -9
- package/dist/root-remove.d.ts +5 -0
- package/dist/root-remove.d.ts.map +1 -0
- package/dist/root-remove.js +286 -0
- package/dist/root-symlink-policy.d.ts +14 -0
- package/dist/root-symlink-policy.d.ts.map +1 -0
- package/dist/root-symlink-policy.js +34 -0
- package/dist/root-walk.d.ts +5 -4
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +99 -50
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +3 -5
- package/dist/root.d.ts +4 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +25 -8
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +2 -1
- package/dist/sibling-staged-file.d.ts +1 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +42 -8
- package/dist/sibling-temp.d.ts +2 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +6 -4
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +17 -5
- package/dist/sidecar-lock-policy.d.ts +2 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +20 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +4 -3
- package/dist/temp-cleanup.d.ts.map +1 -1
- package/dist/temp-cleanup.js +3 -2
- package/dist/temp-target.d.ts +14 -12
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +12 -6
- package/dist/timing.d.ts +1 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +25 -6
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +9 -7
- package/dist/unicode-path.d.ts +3 -0
- package/dist/unicode-path.d.ts.map +1 -0
- package/dist/unicode-path.js +13 -0
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +9 -28
- package/dist/windows-owner.d.ts +9 -12
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +24 -58
- package/dist/write-file-handle.d.ts +7 -0
- package/dist/write-file-handle.d.ts.map +1 -0
- package/dist/write-file-handle.js +26 -0
- package/docs/advanced.md +22 -4
- package/docs/archive.md +44 -9
- package/docs/atomic.md +24 -1
- package/docs/config.md +1 -0
- package/docs/contributing.md +36 -1
- package/docs/copy.md +155 -0
- package/docs/directory-identity.md +85 -0
- package/docs/durability.md +53 -3
- package/docs/entries.md +109 -0
- package/docs/errors.md +4 -4
- package/docs/file-store.md +15 -0
- package/docs/guest.md +141 -0
- package/docs/in-place-write.md +81 -0
- package/docs/index.md +2 -0
- package/docs/install.md +31 -0
- package/docs/local-roots.md +8 -1
- package/docs/native-helper.md +10 -3
- package/docs/native.md +18 -1
- package/docs/output.md +32 -6
- package/docs/path-case.md +64 -0
- package/docs/path-scope.md +1 -1
- package/docs/permissions.md +29 -10
- package/docs/positional-read.md +63 -0
- package/docs/public-api.md +31 -2
- package/docs/root.md +196 -6
- package/docs/secure-file.md +2 -0
- package/docs/security-model.md +14 -0
- package/docs/sidecar-lock.md +12 -3
- package/docs/temp.md +35 -6
- package/docs/timing.md +2 -0
- package/docs/types.md +19 -12
- package/docs/walk.md +54 -5
- package/docs/writing.md +173 -5
- package/package.json +19 -8
package/dist/root-walk.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { FsSafeError } from "./errors.js";
|
|
3
3
|
import { resolveRootPath } from "./root-path.js";
|
|
4
|
+
import { createSuppressedError } from "./suppressed-error.js";
|
|
4
5
|
function validateBudget(name, value) {
|
|
5
6
|
if (value === undefined)
|
|
6
7
|
return Number.POSITIVE_INFINITY;
|
|
@@ -25,6 +26,9 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
25
26
|
if (!["skip", "follow-within-root"].includes(options.symlinkPolicy)) {
|
|
26
27
|
throw new TypeError(`invalid root walk symlink policy: ${String(options.symlinkPolicy)}`);
|
|
27
28
|
}
|
|
29
|
+
if (options.order !== undefined && !["sorted", "filesystem"].includes(options.order)) {
|
|
30
|
+
throw new TypeError(`invalid root walk order: ${String(options.order)}`);
|
|
31
|
+
}
|
|
28
32
|
if (options.limitBehavior !== undefined &&
|
|
29
33
|
!["truncate", "throw"].includes(options.limitBehavior)) {
|
|
30
34
|
throw new TypeError(`invalid root walk limit behavior: ${String(options.limitBehavior)}`);
|
|
@@ -38,6 +42,12 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
38
42
|
const visitedDirectories = new Set();
|
|
39
43
|
let examined = 0;
|
|
40
44
|
let truncated = false;
|
|
45
|
+
const admitEntry = () => {
|
|
46
|
+
if (examined >= maxEntries)
|
|
47
|
+
return false;
|
|
48
|
+
examined += 1;
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
41
51
|
const onLimit = (atPath) => {
|
|
42
52
|
if ((options.limitBehavior ?? "truncate") === "throw") {
|
|
43
53
|
throw new FsSafeError("too-large", `root walk budget exceeded at ${atPath || "."}`);
|
|
@@ -45,9 +55,15 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
45
55
|
truncated = true;
|
|
46
56
|
return limitEntry(atPath);
|
|
47
57
|
};
|
|
58
|
+
const onDirectoryError = (directory, error) => {
|
|
59
|
+
// Rethrow cancellation with any disposal failure already attached.
|
|
60
|
+
if (options.signal?.aborted || (options.onDirectoryError ?? "throw") === "throw")
|
|
61
|
+
throw error;
|
|
62
|
+
return { relativePath: directory, kind: "directory-error", size: 0, error };
|
|
63
|
+
};
|
|
48
64
|
async function* visit(directory, depth) {
|
|
49
65
|
options.signal?.throwIfAborted();
|
|
50
|
-
let
|
|
66
|
+
let listing;
|
|
51
67
|
try {
|
|
52
68
|
const resolvedDirectory = await resolveRootPath({
|
|
53
69
|
absolutePath: path.resolve(root.rootReal, directory),
|
|
@@ -67,66 +83,99 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
67
83
|
.relative(root.rootReal, resolvedDirectory.canonicalPath)
|
|
68
84
|
.split(path.sep)
|
|
69
85
|
.join(path.posix.sep);
|
|
70
|
-
|
|
86
|
+
listing = await root.list(listingDirectory, {
|
|
87
|
+
order: options.order ?? "sorted",
|
|
88
|
+
signal: options.signal,
|
|
89
|
+
snapshot: maxEntries === Number.POSITIVE_INFINITY,
|
|
90
|
+
admitEntry,
|
|
91
|
+
});
|
|
71
92
|
}
|
|
72
93
|
catch (error) {
|
|
73
|
-
|
|
74
|
-
options.signal?.throwIfAborted();
|
|
75
|
-
if ((options.onDirectoryError ?? "throw") === "throw")
|
|
76
|
-
throw error;
|
|
77
|
-
yield { relativePath: directory, kind: "directory-error", size: 0, error };
|
|
94
|
+
yield onDirectoryError(directory, error);
|
|
78
95
|
return;
|
|
79
96
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (
|
|
97
|
+
// A thrown undefined still needs to be retained if closing also fails.
|
|
98
|
+
let failed = false;
|
|
99
|
+
let operationError;
|
|
100
|
+
try {
|
|
101
|
+
while (true) {
|
|
102
|
+
let next;
|
|
103
|
+
try {
|
|
104
|
+
next = await listing.next();
|
|
105
|
+
options.signal?.throwIfAborted();
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
yield onDirectoryError(directory, error);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (next === undefined)
|
|
112
|
+
return;
|
|
113
|
+
const name = next.kind === "entry" ? next.entry.name : next.name;
|
|
114
|
+
const child = directory
|
|
115
|
+
? path.posix.join(directory.split(path.sep).join(path.posix.sep), name)
|
|
116
|
+
: name;
|
|
117
|
+
if (next.kind === "limit") {
|
|
118
|
+
yield onLimit(child);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const entry = next.entry;
|
|
122
|
+
let kind = entryKind(entry);
|
|
123
|
+
let size = entry.size;
|
|
124
|
+
if (kind === "symlink") {
|
|
125
|
+
if (options.symlinkPolicy === "skip") {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const resolved = await resolveRootPath({
|
|
129
|
+
absolutePath: path.resolve(root.rootReal, child),
|
|
130
|
+
rootPath: root.rootReal,
|
|
131
|
+
rootCanonicalPath: root.rootReal,
|
|
132
|
+
boundaryLabel: "root walk",
|
|
133
|
+
});
|
|
134
|
+
if (!resolved.exists) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const target = await root.stat(path.relative(root.rootReal, resolved.canonicalPath));
|
|
138
|
+
kind = target.isDirectory ? "directory" : target.isFile ? "file" : "other";
|
|
139
|
+
size = target.size;
|
|
140
|
+
}
|
|
141
|
+
const walkEntry = { relativePath: child, kind, size };
|
|
142
|
+
const filterResult = options.entryFilter?.(walkEntry) ?? "include";
|
|
143
|
+
if (!["include", "skip", "skip-subtree"].includes(filterResult)) {
|
|
144
|
+
throw new TypeError(`invalid root walk entryFilter result: ${String(filterResult)}`);
|
|
145
|
+
}
|
|
146
|
+
if (filterResult === "include") {
|
|
147
|
+
yield walkEntry;
|
|
148
|
+
}
|
|
149
|
+
if (kind !== "directory") {
|
|
95
150
|
continue;
|
|
96
151
|
}
|
|
97
|
-
|
|
98
|
-
absolutePath: path.resolve(root.rootReal, child),
|
|
99
|
-
rootPath: root.rootReal,
|
|
100
|
-
rootCanonicalPath: root.rootReal,
|
|
101
|
-
boundaryLabel: "root walk",
|
|
102
|
-
});
|
|
103
|
-
if (!resolved.exists) {
|
|
152
|
+
if (filterResult === "skip-subtree") {
|
|
104
153
|
continue;
|
|
105
154
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
if (filterResult === "include") {
|
|
115
|
-
yield walkEntry;
|
|
116
|
-
}
|
|
117
|
-
if (kind !== "directory") {
|
|
118
|
-
continue;
|
|
155
|
+
if (depth >= maxDepth) {
|
|
156
|
+
yield onLimit(child);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
yield* visit(child, depth + 1);
|
|
160
|
+
if (truncated)
|
|
161
|
+
return;
|
|
119
162
|
}
|
|
120
|
-
|
|
121
|
-
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
failed = true;
|
|
166
|
+
operationError = error;
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
try {
|
|
171
|
+
await listing[Symbol.asyncDispose]();
|
|
122
172
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
173
|
+
catch (closeError) {
|
|
174
|
+
if (failed) {
|
|
175
|
+
throw createSuppressedError(closeError, operationError, "directory walk and close both failed");
|
|
176
|
+
}
|
|
177
|
+
throw closeError;
|
|
126
178
|
}
|
|
127
|
-
yield* visit(child, depth + 1);
|
|
128
|
-
if (truncated)
|
|
129
|
-
return;
|
|
130
179
|
}
|
|
131
180
|
}
|
|
132
181
|
yield* visit(relativePath, 0);
|
|
@@ -1 +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,
|
|
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,CAsClB"}
|
package/dist/root-write-mode.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
|
-
import { sameFileIdentity } from "./file-identity.js";
|
|
5
4
|
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
6
5
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
6
|
+
import { realpathSync } from "./realpath.js";
|
|
7
7
|
import { hardlinkedPathNotAllowedError, outsideWorkspaceError } from "./root-errors.js";
|
|
8
8
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
9
9
|
// The caller has already resolved and guarded this write target.
|
|
@@ -23,9 +23,7 @@ export async function inheritWriteTargetMode(params) {
|
|
|
23
23
|
const handle = await fs.open(params.targetPath, resolveReadOpenFlags());
|
|
24
24
|
try {
|
|
25
25
|
// Bind admission to the inode whose metadata is inherited.
|
|
26
|
-
|
|
27
|
-
throw new FsSafeError("path-mismatch", "write target changed during mode inheritance");
|
|
28
|
-
}
|
|
26
|
+
await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }), existing);
|
|
29
27
|
}
|
|
30
28
|
finally {
|
|
31
29
|
await handle.close().catch(() => undefined);
|
|
@@ -33,7 +31,7 @@ export async function inheritWriteTargetMode(params) {
|
|
|
33
31
|
try {
|
|
34
32
|
// A parent can change after guarded resolution. Do not inherit metadata
|
|
35
33
|
// from an outside inode, even if the parent is restored before publication.
|
|
36
|
-
const realPath =
|
|
34
|
+
const realPath = realpathSync.native(params.targetPath);
|
|
37
35
|
if (!isPathInside(params.rootWithSep, realPath))
|
|
38
36
|
throw outsideWorkspaceError();
|
|
39
37
|
await inspectFileIdentity(async () => {
|
package/dist/root.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
|
|
1
|
+
export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type MutationSymlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCopySource, type RootCreateJsonOptions, type RootCreateOptions, type RootCreateStreamOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
|
|
2
|
+
export type { RootCopyPublicationReceipt } from "./copy-publication.js";
|
|
3
|
+
export type { CopyCloneMode } from "./copy-policy.js";
|
|
4
|
+
export type { RootEntriesOptions } from "./root-entries.js";
|
|
2
5
|
export type { ContainmentGuarantee } from "./containment.js";
|
|
3
6
|
export type { RootWalkDataEntry, RootWalkDataEntryKind, RootWalkDirectoryErrorBehavior, RootWalkEntry, RootWalkEntryFilter, RootWalkEntryFilterResult, RootWalkEntryKind, RootWalkLimitBehavior, RootWalkOptions, RootWalkSymlinkPolicy, } from "./root-walk.js";
|
|
4
7
|
//# sourceMappingURL=root.d.ts.map
|
package/dist/root.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,8BAA8B,EAC9B,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,8BAA8B,EAC9B,aAAa,EACb,mBAAmB,EACnB,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,qBAAqB,GACtB,MAAM,gBAAgB,CAAC"}
|
|
@@ -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;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,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;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,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;AAwDD,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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs, {} from "node:fs";
|
|
2
2
|
import fsp from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
4
5
|
import { canonicalPathFromExistingAncestor } from "./absolute-path.js";
|
|
5
6
|
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
6
7
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard, inspectDirectoryIdentity } from "./directory-guard.js";
|
|
@@ -166,7 +167,7 @@ async function ensurePrivateDirectory(rootDir, targetDir, mode) {
|
|
|
166
167
|
catch (error) {
|
|
167
168
|
if (error.code !== "ENOENT")
|
|
168
169
|
throw error;
|
|
169
|
-
await fsp.mkdir(path.dirname(resolvedRoot), { recursive: true, mode });
|
|
170
|
+
await fsp.mkdir(recursiveMkdirPath(path.dirname(resolvedRoot)), { recursive: true, mode });
|
|
170
171
|
createdRoot = await createPrivateDirectory(resolvedRoot, mode);
|
|
171
172
|
}
|
|
172
173
|
rootStat = await inspectPrivateDirectory(resolvedRoot, "root");
|
|
@@ -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":"AAQA,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"}
|
|
@@ -4,6 +4,7 @@ import { readFileHandleBounded } from "./bounded-read.js";
|
|
|
4
4
|
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
7
|
+
import { realpathSync } from "./realpath.js";
|
|
7
8
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
8
9
|
import { assertSecretFilePreview, resolveSecretReadPolicy, secretPathErrorCode, secretReadError, trimSecretFileContent, } from "./secret-read-policy.js";
|
|
9
10
|
export async function readSecretFile(filePath, label, options = {}) {
|
|
@@ -29,7 +30,7 @@ export async function readSecretFile(filePath, label, options = {}) {
|
|
|
29
30
|
let handle;
|
|
30
31
|
let raw;
|
|
31
32
|
try {
|
|
32
|
-
const realPath =
|
|
33
|
+
const realPath = realpathSync.native(resolvedPath);
|
|
33
34
|
assertNoUnsafeDeviceReadPath(realPath);
|
|
34
35
|
handle = await fs.open(realPath, resolveReadOpenFlags());
|
|
35
36
|
const openedHandle = handle;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAe,
|
|
1
|
+
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAc/D,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAI1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AAmNF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAuB/B"}
|
package/dist/secure-file.js
CHANGED
|
@@ -9,8 +9,10 @@ import { isWindowsDriveLetterPath, isWindowsNetworkPath } from "./local-file-acc
|
|
|
9
9
|
import { isPathInside, isSymlinkOpenError } from "./path.js";
|
|
10
10
|
import { formatPermissionErrorDetail } from "./permission-exec.js";
|
|
11
11
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
12
|
+
import { realpathSync } from "./realpath.js";
|
|
12
13
|
import { inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, } from "./permissions.js";
|
|
13
14
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
15
|
+
import { scheduleTimeout } from "./timing.js";
|
|
14
16
|
function isAbsolutePathname(value) {
|
|
15
17
|
return (path.isAbsolute(value) ||
|
|
16
18
|
(process.platform === "win32" &&
|
|
@@ -19,6 +21,11 @@ function isAbsolutePathname(value) {
|
|
|
19
21
|
function label(options) {
|
|
20
22
|
return options.label ?? "Secure file";
|
|
21
23
|
}
|
|
24
|
+
function assertNotHardlinked(options, stat) {
|
|
25
|
+
if (stat.nlink > 1n) {
|
|
26
|
+
throw new FsSafeError("hardlink", `${label(options)} must not be hardlinked: ${options.filePath}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
22
29
|
async function openSecureHandle(options, maxBytes) {
|
|
23
30
|
assertNoUnsafeDeviceReadPath(options.filePath);
|
|
24
31
|
if (isWindowsNetworkPath(options.filePath, "win32") && !options.trust?.allowNetworkPath) {
|
|
@@ -62,6 +69,7 @@ async function openSecureHandle(options, maxBytes) {
|
|
|
62
69
|
throw new FsSafeError("not-file", `${label(options)} must be a file: ${options.filePath}`);
|
|
63
70
|
}
|
|
64
71
|
const openedIdentity = await inspectFileIdentity(() => fsSync.fstatSync(handle.fd, { bigint: true }));
|
|
72
|
+
assertNotHardlinked(options, openedIdentity);
|
|
65
73
|
await inspectFileIdentity(async () => {
|
|
66
74
|
const pathStat = options.trust?.allowSymlink
|
|
67
75
|
? fsSync.statSync(options.filePath, { bigint: true })
|
|
@@ -69,14 +77,19 @@ async function openSecureHandle(options, maxBytes) {
|
|
|
69
77
|
if (!options.trust?.allowSymlink && pathStat.isSymbolicLink()) {
|
|
70
78
|
throw new FsSafeError("symlink", `${label(options)} must not be a symlink: ${options.filePath}`);
|
|
71
79
|
}
|
|
80
|
+
assertNotHardlinked(options, pathStat);
|
|
72
81
|
return pathStat;
|
|
73
82
|
}, openedIdentity);
|
|
74
|
-
const realPath =
|
|
75
|
-
await inspectFileIdentity(() =>
|
|
83
|
+
const realPath = realpathSync.native(options.filePath);
|
|
84
|
+
await inspectFileIdentity(() => {
|
|
85
|
+
const realPathStat = fsSync.statSync(realPath, { bigint: true });
|
|
86
|
+
assertNotHardlinked(options, realPathStat);
|
|
87
|
+
return realPathStat;
|
|
88
|
+
}, openedIdentity);
|
|
76
89
|
if (maxBytes !== undefined && openedStat.size > maxBytes) {
|
|
77
90
|
throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${maxBytes}).`);
|
|
78
91
|
}
|
|
79
|
-
return { handle, pathStat: openedStat, realPath };
|
|
92
|
+
return { handle, identity: openedIdentity, pathStat: openedStat, realPath };
|
|
80
93
|
}
|
|
81
94
|
catch (err) {
|
|
82
95
|
await handle.close().catch(() => undefined);
|
|
@@ -90,7 +103,7 @@ async function assertTrustedDirs(options, realPath) {
|
|
|
90
103
|
const trusted = await Promise.all(options.trust.trustedDirs.map(async (dir) => {
|
|
91
104
|
const resolved = path.resolve(dir);
|
|
92
105
|
try {
|
|
93
|
-
return
|
|
106
|
+
return realpathSync.native(resolved);
|
|
94
107
|
}
|
|
95
108
|
catch {
|
|
96
109
|
return resolved;
|
|
@@ -160,12 +173,12 @@ async function readHandleWithTimeout(handle, timeoutMs, maxBytes) {
|
|
|
160
173
|
if (timeoutMs === undefined || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
161
174
|
return await read();
|
|
162
175
|
}
|
|
163
|
-
let
|
|
176
|
+
let cancelTimeout;
|
|
164
177
|
try {
|
|
165
178
|
return await Promise.race([
|
|
166
179
|
read(),
|
|
167
180
|
new Promise((_resolve, reject) => {
|
|
168
|
-
|
|
181
|
+
cancelTimeout = scheduleTimeout(() => {
|
|
169
182
|
void handle.close().catch(() => undefined);
|
|
170
183
|
reject(new FsSafeError("timeout", `secure file read timed out after ${timeoutMs}ms`));
|
|
171
184
|
}, timeoutMs);
|
|
@@ -173,8 +186,7 @@ async function readHandleWithTimeout(handle, timeoutMs, maxBytes) {
|
|
|
173
186
|
]);
|
|
174
187
|
}
|
|
175
188
|
finally {
|
|
176
|
-
|
|
177
|
-
clearTimeout(timeout);
|
|
189
|
+
cancelTimeout?.();
|
|
178
190
|
}
|
|
179
191
|
}
|
|
180
192
|
export async function readSecureFile(options) {
|
|
@@ -184,6 +196,11 @@ export async function readSecureFile(options) {
|
|
|
184
196
|
await assertTrustedDirs(options, opened.realPath);
|
|
185
197
|
const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath);
|
|
186
198
|
const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs, maxBytes);
|
|
199
|
+
const finalIdentity = await inspectFileIdentity(() => fsSync.fstatSync(opened.handle.fd, { bigint: true }), opened.identity);
|
|
200
|
+
if (!finalIdentity.isFile()) {
|
|
201
|
+
throw new FsSafeError("not-file", `${label(options)} must remain a file: ${options.filePath}`);
|
|
202
|
+
}
|
|
203
|
+
assertNotHardlinked(options, finalIdentity);
|
|
187
204
|
return { buffer, realPath: opened.realPath, stat: opened.pathStat, permissions };
|
|
188
205
|
}
|
|
189
206
|
finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-temp-dir.d.ts","sourceRoot":"","sources":["../src/secure-temp-dir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secure-temp-dir.d.ts","sourceRoot":"","sources":["../src/secure-temp-dir.ts"],"names":[],"mappings":"AAQA,KAAK,aAAa,GAAG;IACnB,WAAW,IAAI,OAAO,CAAC;IACvB,cAAc,IAAI,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,4BAA4B,GAAG;IACzC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,CAAC;IAC5C,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChF,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAWF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,MAAM,CA+JnF"}
|
package/dist/secure-temp-dir.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { tmpdir as getOsTmpDir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
4
5
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
5
6
|
function isNodeErrorWithCode(err, code) {
|
|
6
7
|
return (typeof err === "object" &&
|
|
@@ -17,7 +18,7 @@ export function resolveSecureTempRoot(options) {
|
|
|
17
18
|
const accessSync = options.accessSync ?? fs.accessSync;
|
|
18
19
|
const chmodSync = options.chmodSync ?? fs.chmodSync;
|
|
19
20
|
const lstatSync = options.lstatSync ?? fs.lstatSync;
|
|
20
|
-
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
|
21
|
+
const mkdirSync = options.mkdirSync ?? ((directory, mkdirOptions) => fs.mkdirSync(recursiveMkdirPath(directory), mkdirOptions));
|
|
21
22
|
const warn = options.warn ?? ((message) => console.warn(message));
|
|
22
23
|
const warningPrefix = options.warningPrefix ?? "[fs-safe]";
|
|
23
24
|
const unsafeFallbackLabel = options.unsafeFallbackLabel ?? "secure temp dir";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare function writeCallbackSibling<T>(params: {
|
|
2
2
|
tempPath: string;
|
|
3
3
|
write: (tempPath: string) => Promise<T>;
|
|
4
|
+
producerIsolation?: "private-directory";
|
|
4
5
|
resolveFinalPath: (result: T) => string;
|
|
5
6
|
mode?: number;
|
|
6
7
|
/** Preserve the caller's historical best-effort mode behavior. */
|
|
@@ -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":"AAuEA,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,iBAAiB,CAAC,EAAE,mBAAmB,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,CA+G3C"}
|
|
@@ -2,12 +2,14 @@ import { syncFileBestEffort } from "./file-sync.js";
|
|
|
2
2
|
import fsSync, {} from "node:fs";
|
|
3
3
|
import fs, {} from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
|
+
import { assertAsyncDirectoryGuard, assertDirectoryIdentitySync, createAsyncDirectoryGuard, } from "./directory-guard.js";
|
|
6
6
|
import { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
7
7
|
import { FsSafeError } from "./errors.js";
|
|
8
8
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
9
|
+
import { root } from "./root.js";
|
|
9
10
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
10
11
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
12
|
+
import { createOwnedTempFile } from "./temp-target.js";
|
|
11
13
|
import { serializePathWrite } from "./write-queue.js";
|
|
12
14
|
function assertRegularFile(stat) {
|
|
13
15
|
if (stat.isSymbolicLink()) {
|
|
@@ -27,17 +29,39 @@ async function inspectStage(inspect, expected) {
|
|
|
27
29
|
return stat;
|
|
28
30
|
}, expected);
|
|
29
31
|
}
|
|
32
|
+
// Own the workspace before the producer can leave partial output. The finished
|
|
33
|
+
// file still enters the ordinary sibling admission and publication lifecycle.
|
|
34
|
+
async function writeIsolatedProducer(params) {
|
|
35
|
+
const targetRoot = await root(path.dirname(params.tempPath));
|
|
36
|
+
const { target, identity } = await createOwnedTempFile({
|
|
37
|
+
rootDir: targetRoot.rootReal,
|
|
38
|
+
prefix: "fs-safe-output",
|
|
39
|
+
fileName: path.basename(params.tempPath),
|
|
40
|
+
});
|
|
41
|
+
const assertCurrent = () => {
|
|
42
|
+
params.assertParent();
|
|
43
|
+
assertDirectoryIdentitySync(target.dir, { ...identity, realPath: target.dir });
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
assertCurrent();
|
|
47
|
+
const result = await params.write(target.path);
|
|
48
|
+
assertCurrent();
|
|
49
|
+
await targetRoot.move(path.relative(targetRoot.rootReal, target.path), path.basename(params.tempPath), {
|
|
50
|
+
assertBeforeMutation: assertCurrent,
|
|
51
|
+
});
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await target.cleanup();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
30
58
|
// Callback paths are not owned until all three admission observations agree.
|
|
31
59
|
// Keep one descriptor and one exact identity through mode, sync, rename and cleanup.
|
|
32
60
|
// Read/write access is needed only when the caller requests file synchronization.
|
|
33
61
|
export async function writeCallbackSibling(params) {
|
|
34
62
|
const parent = path.dirname(params.tempPath);
|
|
35
|
-
const guard = await createAsyncDirectoryGuard(parent);
|
|
36
|
-
const
|
|
37
|
-
const assertParent = async () => {
|
|
38
|
-
await assertAsyncDirectoryGuard(guard);
|
|
39
|
-
await inspectFileIdentity(() => fsSync.lstatSync(parent, { bigint: true }), parentIdentity);
|
|
40
|
-
};
|
|
63
|
+
const guard = await createAsyncDirectoryGuard(parent, { bigint: true });
|
|
64
|
+
const assertParent = () => assertAsyncDirectoryGuard(guard);
|
|
41
65
|
let handle;
|
|
42
66
|
let identity;
|
|
43
67
|
let unregister;
|
|
@@ -54,7 +78,17 @@ export async function writeCallbackSibling(params) {
|
|
|
54
78
|
}
|
|
55
79
|
};
|
|
56
80
|
try {
|
|
57
|
-
const result =
|
|
81
|
+
const result = params.producerIsolation === "private-directory"
|
|
82
|
+
? await writeIsolatedProducer({
|
|
83
|
+
tempPath: params.tempPath,
|
|
84
|
+
write: params.write,
|
|
85
|
+
assertParent: () => assertDirectoryIdentitySync(parent, {
|
|
86
|
+
dev: guard.stat.dev,
|
|
87
|
+
ino: guard.stat.ino,
|
|
88
|
+
realPath: guard.realPath,
|
|
89
|
+
}),
|
|
90
|
+
})
|
|
91
|
+
: await params.write(params.tempPath);
|
|
58
92
|
await assertParent();
|
|
59
93
|
const before = await inspectPath(params.tempPath);
|
|
60
94
|
try {
|
package/dist/sibling-temp.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type WriteSiblingTempFileOptions<T> = {
|
|
2
2
|
dir: string;
|
|
3
3
|
writeTemp: (tempPath: string) => Promise<T>;
|
|
4
|
+
/** Own a private sibling workspace before invoking the producer. */
|
|
5
|
+
producerIsolation?: "private-directory";
|
|
4
6
|
resolveFinalPath: (result: T) => string;
|
|
5
7
|
tempPrefix?: string;
|
|
6
8
|
dirMode?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sibling-temp.d.ts","sourceRoot":"","sources":["../src/sibling-temp.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5C,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;IACxC,gBAAgB,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uGAAuG;IACvG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,CAAC,CAAC;CACX,CAAC;AASF,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAqBxC;AAwBD,wBAAsB,uBAAuB,CAAC,MAAM,EAAE;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6ChB"}
|
package/dist/sibling-temp.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import crypto, { randomUUID } from "node:crypto";
|
|
2
|
-
import fsSync from "node:fs";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
6
5
|
import { fitFileNameToPortableComponent, sanitizeUntrustedFileName } from "./filename.js";
|
|
7
6
|
import { applyDirectoryMode } from "./replace-file-descriptor.js";
|
|
7
|
+
import { realpathSync } from "./realpath.js";
|
|
8
|
+
import { recursiveMkdirPath } from "./recursive-mkdir-path.js";
|
|
8
9
|
import { root } from "./root.js";
|
|
9
10
|
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
10
11
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
@@ -19,7 +20,7 @@ function buildTempPath(dir, tempPrefix) {
|
|
|
19
20
|
}
|
|
20
21
|
export async function writeSiblingTempFile(options) {
|
|
21
22
|
const dir = path.resolve(options.dir);
|
|
22
|
-
await fs.mkdir(dir, { recursive: true, mode: options.dirMode ?? 0o700 });
|
|
23
|
+
await fs.mkdir(recursiveMkdirPath(dir), { recursive: true, mode: options.dirMode ?? 0o700 });
|
|
23
24
|
if (options.chmodDir !== false) {
|
|
24
25
|
await applyDirectoryMode({
|
|
25
26
|
fsModule: fs,
|
|
@@ -31,6 +32,7 @@ export async function writeSiblingTempFile(options) {
|
|
|
31
32
|
return await writeCallbackSibling({
|
|
32
33
|
tempPath: buildTempPath(dir, options.tempPrefix),
|
|
33
34
|
write: options.writeTemp,
|
|
35
|
+
producerIsolation: options.producerIsolation,
|
|
34
36
|
resolveFinalPath: options.resolveFinalPath,
|
|
35
37
|
mode: options.mode,
|
|
36
38
|
ignoreModeError: true,
|
|
@@ -55,7 +57,7 @@ function buildSiblingTempPath(params) {
|
|
|
55
57
|
export async function writeViaSiblingTempPath(params) {
|
|
56
58
|
let rootDir;
|
|
57
59
|
try {
|
|
58
|
-
rootDir =
|
|
60
|
+
rootDir = realpathSync.native(path.resolve(params.rootDir));
|
|
59
61
|
}
|
|
60
62
|
catch {
|
|
61
63
|
rootDir = path.resolve(params.rootDir);
|
|
@@ -63,7 +65,7 @@ export async function writeViaSiblingTempPath(params) {
|
|
|
63
65
|
const requestedTargetPath = path.resolve(params.targetPath);
|
|
64
66
|
let targetPath;
|
|
65
67
|
try {
|
|
66
|
-
const realDir =
|
|
68
|
+
const realDir = realpathSync.native(path.dirname(requestedTargetPath));
|
|
67
69
|
targetPath = path.join(realDir, path.basename(requestedTargetPath));
|
|
68
70
|
}
|
|
69
71
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sidecar-lock-acquire.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-acquire.ts"],"names":[],"mappings":"AAUA,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAW3C,OAAO,EAWL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAI5F,KAAK,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;CAClC,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,2BAA2B,IAAI,IAAI,CAAC;IACpC,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB,CAAC;IAC1F,eAAe,CACb,oBAAoB,EAAE,MAAM,EAC5B,IAAI,EAAE,eAAe,EACrB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;CACrB,CAAC;AAqBF,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/E,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,EAC5C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,iBAAiB,CAAC,CA6T5B"}
|