@openclaw/fs-safe 0.5.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +16 -10
- package/dist/advanced.d.ts +2 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-crc32.d.ts +2 -0
- package/dist/archive-crc32.d.ts.map +1 -0
- package/dist/archive-crc32.js +14 -0
- package/dist/archive-kind.js +2 -2
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +7 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +24 -5
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +53 -6
- package/dist/archive-tar-pax.d.ts +8 -0
- package/dist/archive-tar-pax.d.ts.map +1 -0
- package/dist/archive-tar-pax.js +100 -0
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-zip-admission.d.ts +7 -0
- package/dist/archive-zip-admission.d.ts.map +1 -0
- package/dist/archive-zip-admission.js +60 -0
- package/dist/archive-zip-count.d.ts +2 -0
- package/dist/archive-zip-count.d.ts.map +1 -0
- package/dist/archive-zip-count.js +140 -0
- package/dist/archive-zip-directory.d.ts +8 -0
- package/dist/archive-zip-directory.d.ts.map +1 -0
- package/dist/archive-zip-directory.js +224 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +1 -14
- package/dist/archive-zip-names.d.ts +12 -0
- package/dist/archive-zip-names.d.ts.map +1 -0
- package/dist/archive-zip-names.js +99 -0
- package/dist/archive-zip-preflight.d.ts +1 -1
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -145
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +32 -23
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +25 -19
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +19 -7
- package/dist/native-binding.d.ts +3 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts +4 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +22 -6
- package/dist/native-pinned-write-windows.d.ts +8 -0
- package/dist/native-pinned-write-windows.d.ts.map +1 -0
- package/dist/native-pinned-write-windows.js +92 -0
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +136 -127
- package/dist/native-staged-file.d.ts +24 -0
- package/dist/native-staged-file.d.ts.map +1 -0
- package/dist/native-staged-file.js +337 -0
- package/dist/native.d.ts.map +1 -1
- package/dist/native.js +4 -4
- package/dist/opened-realpath.d.ts +2 -0
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +12 -7
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +2 -1
- package/dist/permission-exec.d.ts +19 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +57 -11
- package/dist/permissions-public.d.ts +1 -1
- package/dist/permissions-public.d.ts.map +1 -1
- package/dist/permissions-windows.d.ts +3 -0
- package/dist/permissions-windows.d.ts.map +1 -1
- package/dist/permissions-windows.js +16 -5
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/pinned-write.d.ts +6 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +19 -29
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +3 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +78 -72
- package/dist/root-paths.d.ts +11 -14
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +36 -27
- package/dist/root-write-verification.d.ts +11 -0
- package/dist/root-write-verification.d.ts.map +1 -0
- package/dist/root-write-verification.js +91 -0
- package/dist/secret-file.d.ts +1 -6
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +43 -117
- package/dist/secret-read-async.d.ts +1 -1
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +45 -67
- package/dist/secret-read-policy.d.ts +13 -0
- package/dist/secret-read-policy.d.ts.map +1 -0
- package/dist/secret-read-policy.js +28 -0
- package/dist/secret.d.ts +2 -1
- package/dist/secret.d.ts.map +1 -1
- package/dist/secret.js +2 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +25 -16
- package/dist/staged-directory.d.ts +16 -0
- package/dist/staged-directory.d.ts.map +1 -0
- package/dist/staged-directory.js +60 -0
- package/dist/staged-file-types.d.ts +56 -0
- package/dist/staged-file-types.d.ts.map +1 -0
- package/dist/staged-file-types.js +1 -0
- package/dist/staged-file.d.ts +10 -0
- package/dist/staged-file.d.ts.map +1 -0
- package/dist/staged-file.js +15 -0
- package/dist/strict-file-identity.d.ts +6 -0
- package/dist/strict-file-identity.d.ts.map +1 -0
- package/dist/strict-file-identity.js +48 -0
- package/dist/windows-owner.d.ts +3 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +10 -2
- package/docs/advanced.md +11 -1
- package/docs/archive.md +62 -15
- package/docs/config.md +7 -0
- package/docs/contributing.md +45 -7
- package/docs/durability.md +17 -4
- package/docs/errors.md +16 -1
- package/docs/index.md +3 -1
- package/docs/install.md +15 -7
- package/docs/migrating-to-0.5.md +7 -7
- package/docs/migrating-to-0.6.md +43 -0
- package/docs/native-helper.md +15 -5
- package/docs/native.md +40 -11
- package/docs/path-scope.md +28 -2
- package/docs/permissions.md +13 -2
- package/docs/root.md +1 -1
- package/docs/secret-file.md +8 -0
- package/docs/secure-file.md +17 -1
- package/docs/security-model.md +15 -1
- package/docs/sidecar-lock.md +11 -1
- package/docs/staged-file.md +178 -0
- package/docs/testing.md +1 -1
- package/docs/writing.md +19 -0
- package/package.json +15 -8
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
53
|
+
import { randomUUID } from "node:crypto";
|
|
54
|
+
import fs from "node:fs";
|
|
55
|
+
import { FsSafeError } from "./errors.js";
|
|
56
|
+
import { syncNativeFileBestEffort, writeNativeInput } from "./native-operations.js";
|
|
57
|
+
import { assertStagedDirectoryCurrent } from "./staged-directory.js";
|
|
58
|
+
export function assertNativeStaging(binding) {
|
|
59
|
+
if ([
|
|
60
|
+
binding.createStagedFile,
|
|
61
|
+
binding.stagedFileMatches,
|
|
62
|
+
binding.removeStagedFile,
|
|
63
|
+
binding.renameReplace,
|
|
64
|
+
binding.renameNoReplace,
|
|
65
|
+
].some((fn) => typeof fn !== "function")) {
|
|
66
|
+
throw new FsSafeError("helper-unavailable", "native retained-directory staging is unavailable");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function assertBasename(name, portable) {
|
|
70
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\0") ||
|
|
71
|
+
(portable && /[\\:\u0000-\u001f\u007f-\u009f\u2028\u2029]/u.test(name))) {
|
|
72
|
+
throw new FsSafeError("invalid-path", "publication requires one direct-child basename");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const NOT_PUBLISHED = Object.freeze({ status: "not-published" });
|
|
76
|
+
const UNCOMMITTED_RENAME_ERRORS = new Set([
|
|
77
|
+
"EACCES", "EBADF", "EBUSY", "EEXIST", "EINVAL", "EISDIR", "ELOOP", "EMLINK",
|
|
78
|
+
"ENAMETOOLONG", "ENOENT", "ENOSPC", "ENOSYS", "ENOTDIR", "ENOTEMPTY", "ENOTSUP",
|
|
79
|
+
"EPERM", "EROFS", "ETXTBSY", "EXDEV",
|
|
80
|
+
]);
|
|
81
|
+
function failure(error, details) {
|
|
82
|
+
const code = error instanceof FsSafeError
|
|
83
|
+
? error.code
|
|
84
|
+
: error?.code === "EEXIST" ? "already-exists" : "helper-failed";
|
|
85
|
+
return new FsSafeError(code, `staged file ${details.phase} failed`, { cause: error, details });
|
|
86
|
+
}
|
|
87
|
+
class NativeStagedFile {
|
|
88
|
+
#binding;
|
|
89
|
+
#parentFd;
|
|
90
|
+
#directory;
|
|
91
|
+
#portableNames;
|
|
92
|
+
#publishedMode;
|
|
93
|
+
#name = `.fs-safe-${randomUUID()}.tmp`;
|
|
94
|
+
#state = { status: "open", publication: NOT_PUBLISHED };
|
|
95
|
+
#receipt;
|
|
96
|
+
constructor(binding, parentFd, directory, portableNames, publishedMode) {
|
|
97
|
+
this.#binding = binding;
|
|
98
|
+
this.#parentFd = parentFd;
|
|
99
|
+
this.#directory = directory;
|
|
100
|
+
this.#portableNames = portableNames;
|
|
101
|
+
this.#publishedMode = publishedMode;
|
|
102
|
+
}
|
|
103
|
+
// Takes ownership of parentFd, including all construction and preparation failures.
|
|
104
|
+
static async create(binding, parentFd, directory, input, mode, maxBytes,
|
|
105
|
+
// Public staging uses portable names; existing POSIX writes accept literal names.
|
|
106
|
+
portableNames = true) {
|
|
107
|
+
let staged;
|
|
108
|
+
try {
|
|
109
|
+
staged = new NativeStagedFile(binding, parentFd, directory, portableNames, mode);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
try {
|
|
113
|
+
fs.closeSync(parentFd);
|
|
114
|
+
}
|
|
115
|
+
catch (closeError) {
|
|
116
|
+
throw new AggregateError([error, closeError], "staged owner construction and close failed");
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
await staged.#prepare(input, maxBytes);
|
|
121
|
+
return staged;
|
|
122
|
+
}
|
|
123
|
+
static async write(binding, parentFd, directory, params, parentGuard) {
|
|
124
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
125
|
+
try {
|
|
126
|
+
// This owner never escapes. Only the internal verifier borrows its fd;
|
|
127
|
+
// public descriptor methods remain await-free and cannot race disposal.
|
|
128
|
+
const staged = __addDisposableResource(env_1, await NativeStagedFile.create(binding, parentFd, directory, params.input, params.mode, params.maxBytes, false), true);
|
|
129
|
+
const published = await staged.publish(params.basename, { overwrite: params.overwrite !== false });
|
|
130
|
+
const identity = published.staged.identity;
|
|
131
|
+
await params.verifyPublished?.(staged.#file(), identity, parentGuard);
|
|
132
|
+
return { dev: identity.dev, ino: identity.ino };
|
|
133
|
+
}
|
|
134
|
+
catch (e_1) {
|
|
135
|
+
env_1.error = e_1;
|
|
136
|
+
env_1.hasError = true;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
const result_1 = __disposeResources(env_1);
|
|
140
|
+
if (result_1)
|
|
141
|
+
await result_1;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
get receipt() {
|
|
145
|
+
if (!this.#receipt) {
|
|
146
|
+
throw new FsSafeError("helper-failed", "staged file preparation is incomplete");
|
|
147
|
+
}
|
|
148
|
+
return this.#receipt;
|
|
149
|
+
}
|
|
150
|
+
#open() {
|
|
151
|
+
if (this.#state.status === "closed") {
|
|
152
|
+
throw new FsSafeError("helper-failed", "staged file is closed");
|
|
153
|
+
}
|
|
154
|
+
return this.#state;
|
|
155
|
+
}
|
|
156
|
+
#file() {
|
|
157
|
+
const state = this.#open();
|
|
158
|
+
if (state.fileFd === undefined) {
|
|
159
|
+
throw new FsSafeError("helper-failed", "staged file has not been created");
|
|
160
|
+
}
|
|
161
|
+
return state.fileFd;
|
|
162
|
+
}
|
|
163
|
+
#assertNamed(name) {
|
|
164
|
+
const fd = this.#file();
|
|
165
|
+
if (!this.#binding.stagedFileMatches(this.#parentFd, name, fd) ||
|
|
166
|
+
fs.fstatSync(fd, { bigint: true }).nlink !== 1n) {
|
|
167
|
+
throw new FsSafeError("path-mismatch", "staged entry no longer names the exclusive created file");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
#assertCurrent() {
|
|
171
|
+
if (this.#open().publication.status !== "not-published") {
|
|
172
|
+
throw new FsSafeError("helper-failed", "staged file publication has already been attempted");
|
|
173
|
+
}
|
|
174
|
+
assertStagedDirectoryCurrent(this.#directory);
|
|
175
|
+
this.#assertNamed(this.#name);
|
|
176
|
+
}
|
|
177
|
+
async #prepare(input, maxBytes) {
|
|
178
|
+
try {
|
|
179
|
+
const mode = this.#publishedMode;
|
|
180
|
+
if (!Number.isInteger(mode) || mode < 0 || mode > 0o7777) {
|
|
181
|
+
throw new FsSafeError("invalid-path", "invalid staged file mode");
|
|
182
|
+
}
|
|
183
|
+
assertStagedDirectoryCurrent(this.#directory);
|
|
184
|
+
// The exclusive open performs no fallible post-open checks. Store its fd
|
|
185
|
+
// before every subsequent operation, including the first metadata read.
|
|
186
|
+
const state = this.#open();
|
|
187
|
+
state.fileFd = this.#binding.createStagedFile(this.#parentFd, this.#name);
|
|
188
|
+
const fd = state.fileFd;
|
|
189
|
+
fs.fchmodSync(fd, 0o600);
|
|
190
|
+
await writeNativeInput(fd, input, maxBytes);
|
|
191
|
+
syncNativeFileBestEffort(fd);
|
|
192
|
+
const stat = fs.fstatSync(fd, { bigint: true });
|
|
193
|
+
this.#receipt = Object.freeze({
|
|
194
|
+
directory: this.#directory,
|
|
195
|
+
temporaryBasename: this.#name,
|
|
196
|
+
identity: Object.freeze({
|
|
197
|
+
dev: stat.dev,
|
|
198
|
+
ino: stat.ino,
|
|
199
|
+
mode: Number(stat.mode & 4095n),
|
|
200
|
+
nlink: stat.nlink,
|
|
201
|
+
size: stat.size,
|
|
202
|
+
uid: Number(stat.uid),
|
|
203
|
+
gid: Number(stat.gid),
|
|
204
|
+
mtimeNs: stat.mtimeNs,
|
|
205
|
+
ctimeNs: stat.ctimeNs,
|
|
206
|
+
}),
|
|
207
|
+
});
|
|
208
|
+
this.#assertCurrent();
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const { receipt: cleanup, error: cleanupError } = this.#finalize();
|
|
212
|
+
if (cleanupError) {
|
|
213
|
+
throw failure(new AggregateError([error, cleanupError], "preparation and cleanup failed"), { phase: "prepare", publication: cleanup.publication, cleanup });
|
|
214
|
+
}
|
|
215
|
+
if (cleanup.status === "preserved") {
|
|
216
|
+
throw failure(error, { phase: "prepare", publication: cleanup.publication, cleanup });
|
|
217
|
+
}
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Keep public descriptor work await-free: each call completes its mutations
|
|
222
|
+
// before the next invocation, including closure and cached cleanup failures.
|
|
223
|
+
async assertCurrent() {
|
|
224
|
+
this.#assertCurrent();
|
|
225
|
+
}
|
|
226
|
+
async publish(basename, options) {
|
|
227
|
+
const overwrite = options?.overwrite;
|
|
228
|
+
try {
|
|
229
|
+
const state = this.#open();
|
|
230
|
+
assertBasename(basename, this.#portableNames);
|
|
231
|
+
if (basename === this.#name || typeof overwrite !== "boolean") {
|
|
232
|
+
throw new FsSafeError("invalid-path", "publication needs a distinct basename and explicit overwrite policy");
|
|
233
|
+
}
|
|
234
|
+
this.#assertCurrent();
|
|
235
|
+
try {
|
|
236
|
+
if (overwrite) {
|
|
237
|
+
this.#binding.renameReplace(this.#parentFd, this.#name, this.#parentFd, basename);
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
this.#binding.renameNoReplace(this.#parentFd, this.#name, this.#parentFd, basename);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
// Unknown rename errors can be indeterminate on remote filesystems.
|
|
245
|
+
if (!UNCOMMITTED_RENAME_ERRORS.has(error?.code ?? "")) {
|
|
246
|
+
state.publication = Object.freeze({ status: "indeterminate", basename, overwrite });
|
|
247
|
+
}
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
// Commit is recorded synchronously before any post-rename operation.
|
|
251
|
+
const receipt = Object.freeze({
|
|
252
|
+
status: "published",
|
|
253
|
+
staged: this.receipt,
|
|
254
|
+
basename,
|
|
255
|
+
overwrite,
|
|
256
|
+
});
|
|
257
|
+
state.publication = receipt;
|
|
258
|
+
this.#assertNamed(basename);
|
|
259
|
+
assertStagedDirectoryCurrent(this.#directory);
|
|
260
|
+
// Keep contents private until the published name passes its identity
|
|
261
|
+
// fence. Mode changes use the owned fd, including for final mode 000.
|
|
262
|
+
const fd = this.#file();
|
|
263
|
+
fs.fchmodSync(fd, this.#publishedMode);
|
|
264
|
+
syncNativeFileBestEffort(fd);
|
|
265
|
+
syncNativeFileBestEffort(this.#parentFd);
|
|
266
|
+
this.#assertNamed(basename);
|
|
267
|
+
assertStagedDirectoryCurrent(this.#directory);
|
|
268
|
+
return receipt;
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
// Closure rejects further use, not the recorded outcome of an earlier publication.
|
|
272
|
+
const publication = this.#state.status === "closed"
|
|
273
|
+
? this.#state.receipt.publication
|
|
274
|
+
: this.#state.publication;
|
|
275
|
+
throw failure(error, { phase: "publish", publication });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
#finalize() {
|
|
279
|
+
if (this.#state.status === "closed") {
|
|
280
|
+
return this.#state;
|
|
281
|
+
}
|
|
282
|
+
const state = this.#state;
|
|
283
|
+
let outcome = "not-needed";
|
|
284
|
+
const errors = [];
|
|
285
|
+
if (state.publication.status === "indeterminate") {
|
|
286
|
+
outcome = "preserved";
|
|
287
|
+
}
|
|
288
|
+
else if (state.publication.status === "not-published" && state.fileFd !== undefined) {
|
|
289
|
+
try {
|
|
290
|
+
outcome = this.#binding.removeStagedFile(this.#parentFd, this.#name, state.fileFd);
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
outcome = "failed";
|
|
294
|
+
errors.push(error);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
let resources = "closed";
|
|
298
|
+
for (const fd of [state.fileFd, this.#parentFd]) {
|
|
299
|
+
if (fd === undefined) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
fs.closeSync(fd);
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
resources = "close-failed";
|
|
307
|
+
errors.push(error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const receipt = Object.freeze({
|
|
311
|
+
temporaryBasename: this.#name,
|
|
312
|
+
publication: state.publication,
|
|
313
|
+
status: outcome,
|
|
314
|
+
resources,
|
|
315
|
+
});
|
|
316
|
+
const error = errors.length ? failure(errors.length === 1 ? errors[0] : new AggregateError(errors, "staged cleanup failed"), { phase: "cleanup", publication: state.publication, cleanup: receipt }) : undefined;
|
|
317
|
+
this.#state = { status: "closed", receipt, error };
|
|
318
|
+
return this.#state;
|
|
319
|
+
}
|
|
320
|
+
async cleanup() {
|
|
321
|
+
const closed = this.#finalize();
|
|
322
|
+
if (closed.error) {
|
|
323
|
+
throw closed.error;
|
|
324
|
+
}
|
|
325
|
+
return closed.receipt;
|
|
326
|
+
}
|
|
327
|
+
async [Symbol.asyncDispose]() {
|
|
328
|
+
const cleanup = await this.cleanup();
|
|
329
|
+
if (cleanup.status === "preserved") {
|
|
330
|
+
throw new FsSafeError("not-removable", "staged cleanup preserved an unverified entry", {
|
|
331
|
+
details: { phase: "cleanup", publication: cleanup.publication, cleanup },
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
export const createNativeStage = NativeStagedFile.create;
|
|
337
|
+
export const writeNativeStage = NativeStagedFile.write;
|
package/dist/native.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAsKzD,wBAAgB,0BAA0B,IAAI,aAAa,CAE1D;AAED,wBAAgB,8BAA8B,IAAI;IAChD,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,UAAU,EAAE,OAAO,GAAG,SAAS,CAAC;IAChC,cAAc,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAMA;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,IAAI,EAAE,MAAM,EACZ,IAAI,UAAQ,GACX,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,SAAS,CAkB5D;AAED,wBAAgB,oBAAoB,IAAI,aAAa,CAQpD;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAK1E;AAED,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD"}
|
package/dist/native.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import { closeSync, openSync, readSync, readdirSync, } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
3
|
import { FsSafeError } from "./errors.js";
|
|
6
4
|
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
7
5
|
const require = createRequire(import.meta.url);
|
|
8
|
-
const bundledNativeDirectory = fileURLToPath(new URL("../dist/native/", import.meta.url));
|
|
9
6
|
let binding;
|
|
10
7
|
let loadError;
|
|
11
8
|
let attempted = false;
|
|
@@ -139,12 +136,15 @@ function targetFor(platform, arch, musl) {
|
|
|
139
136
|
function bundledTarget() {
|
|
140
137
|
return targetFor(process.platform, process.arch, isMusl());
|
|
141
138
|
}
|
|
139
|
+
function nativePackageForTarget(target) {
|
|
140
|
+
return `@openclaw/fs-safe-${target}`;
|
|
141
|
+
}
|
|
142
142
|
function loadBundledBinding() {
|
|
143
143
|
const target = bundledTarget();
|
|
144
144
|
if (!target) {
|
|
145
145
|
throw new Error(`Unsupported OS or architecture: ${process.platform}-${process.arch}`);
|
|
146
146
|
}
|
|
147
|
-
return require(
|
|
147
|
+
return require(nativePackageForTarget(target));
|
|
148
148
|
}
|
|
149
149
|
export function __loadBundledNativeForTest() {
|
|
150
150
|
return loadBundledBinding();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { FileHandle } from "node:fs/promises";
|
|
2
|
+
import { type FileIdentityStat } from "./file-identity.js";
|
|
2
3
|
export declare function resolveOpenedFileRealPathForHandle(handle: FileHandle, ioPath: string): Promise<string>;
|
|
4
|
+
export declare function resolveOpenedFileRealPathForFd(fd: number, handleStat: FileIdentityStat, ioPath: string): Promise<string>;
|
|
3
5
|
//# sourceMappingURL=opened-realpath.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"opened-realpath.d.ts","sourceRoot":"","sources":["../src/opened-realpath.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,wBAAsB,kCAAkC,CACtD,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED,wBAAsB,8BAA8B,CAClD,EAAE,EAAE,MAAM,EACV,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAqCjB"}
|
package/dist/opened-realpath.js
CHANGED
|
@@ -5,15 +5,20 @@ import { sameFileIdentity } from "./file-identity.js";
|
|
|
5
5
|
import { isNotFoundPathError } from "./path.js";
|
|
6
6
|
export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
7
7
|
const handleStat = await handle.stat();
|
|
8
|
+
return await resolveOpenedFileRealPathForFd(handle.fd, handleStat, ioPath);
|
|
9
|
+
}
|
|
10
|
+
export async function resolveOpenedFileRealPathForFd(fd, handleStat, ioPath) {
|
|
11
|
+
const statOptions = typeof handleStat.dev === "bigint" || typeof handleStat.ino === "bigint"
|
|
12
|
+
? { bigint: true } : undefined;
|
|
8
13
|
const fdCandidates = process.platform === "linux"
|
|
9
|
-
? [`/proc/self/fd/${
|
|
14
|
+
? [`/proc/self/fd/${fd}`, `/dev/fd/${fd}`]
|
|
10
15
|
: process.platform === "win32"
|
|
11
16
|
? []
|
|
12
|
-
: [`/dev/fd/${
|
|
17
|
+
: [`/dev/fd/${fd}`];
|
|
13
18
|
for (const fdPath of fdCandidates) {
|
|
14
19
|
try {
|
|
15
20
|
const fdRealPath = await fs.realpath(fdPath);
|
|
16
|
-
const fdRealStat = await fs.stat(fdRealPath);
|
|
21
|
+
const fdRealStat = statOptions ? await fs.stat(fdRealPath, statOptions) : await fs.stat(fdRealPath);
|
|
17
22
|
if (sameFileIdentity(handleStat, fdRealStat)) {
|
|
18
23
|
return fdRealPath;
|
|
19
24
|
}
|
|
@@ -24,7 +29,7 @@ export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
|
24
29
|
}
|
|
25
30
|
try {
|
|
26
31
|
const ioRealPath = await fs.realpath(ioPath);
|
|
27
|
-
const ioRealStat = await fs.stat(ioRealPath);
|
|
32
|
+
const ioRealStat = statOptions ? await fs.stat(ioRealPath, statOptions) : await fs.stat(ioRealPath);
|
|
28
33
|
if (sameFileIdentity(handleStat, ioRealStat)) {
|
|
29
34
|
return ioRealPath;
|
|
30
35
|
}
|
|
@@ -34,13 +39,13 @@ export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
|
34
39
|
throw err;
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
|
-
const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
|
|
42
|
+
const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions);
|
|
38
43
|
if (parentResolved) {
|
|
39
44
|
return parentResolved;
|
|
40
45
|
}
|
|
41
46
|
throw new FsSafeError("path-mismatch", "unable to resolve opened file path");
|
|
42
47
|
}
|
|
43
|
-
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
48
|
+
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath, statOptions) {
|
|
44
49
|
let parentReal;
|
|
45
50
|
try {
|
|
46
51
|
parentReal = await fs.realpath(path.dirname(ioPath));
|
|
@@ -64,7 +69,7 @@ async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
|
64
69
|
for (const entry of entries.toSorted()) {
|
|
65
70
|
const candidatePath = path.join(parentReal, entry);
|
|
66
71
|
try {
|
|
67
|
-
const candidateStat = await fs.lstat(candidatePath);
|
|
72
|
+
const candidateStat = statOptions ? await fs.lstat(candidatePath, statOptions) : await fs.lstat(candidatePath);
|
|
68
73
|
if (candidateStat.isFile() && sameFileIdentity(handleStat, candidateStat)) {
|
|
69
74
|
return await fs.realpath(candidatePath);
|
|
70
75
|
}
|
package/dist/owner-dacl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACnC,GACD;IACE,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,CAAC;AAEN,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"owner-dacl.d.ts","sourceRoot":"","sources":["../src/owner-dacl.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,KAAK,EAAE,eAAe,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IACE,MAAM,EAAE,WAAW,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,IAAI,EAAE,yBAAyB,EAAE,CAAC;CACnC,GACD;IACE,MAAM,EAAE,sBAAsB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CAC3B,CAAC;AAEN,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB,CA6BvE"}
|
package/dist/owner-dacl.js
CHANGED
|
@@ -6,7 +6,8 @@ export function readOwnerAndDacl(targetPath) {
|
|
|
6
6
|
}
|
|
7
7
|
const native = getNativeBinding();
|
|
8
8
|
if (!native) {
|
|
9
|
-
throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require the
|
|
9
|
+
throw new FsSafeError("helper-unavailable", "Windows owner and DACL facts require the matching optional native platform package; " +
|
|
10
|
+
"install @openclaw/fs-safe with optional dependencies enabled on a supported platform and use FS_SAFE_NATIVE_MODE=auto or require");
|
|
10
11
|
}
|
|
11
12
|
const facts = native.readOwnerAndDacl(targetPath);
|
|
12
13
|
return {
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
export declare const DEFAULT_PERMISSION_EXEC_TIMEOUT_MS = 30000;
|
|
2
|
+
export type PermissionCommandFailure = {
|
|
3
|
+
command: string;
|
|
4
|
+
durationMs: number;
|
|
5
|
+
timedOut: boolean;
|
|
6
|
+
exitCode: number | null;
|
|
7
|
+
signal: string | null;
|
|
8
|
+
stderr: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function formatPermissionErrorDetail(value: string): string;
|
|
11
|
+
export declare class PermissionCommandError extends Error implements PermissionCommandFailure {
|
|
12
|
+
readonly command: string;
|
|
13
|
+
readonly durationMs: number;
|
|
14
|
+
readonly timedOut: boolean;
|
|
15
|
+
readonly exitCode: number | null;
|
|
16
|
+
readonly signal: string | null;
|
|
17
|
+
readonly stderr: string;
|
|
18
|
+
constructor(command: string, durationMs: number, cause: unknown, timeoutMs?: number);
|
|
19
|
+
}
|
|
20
|
+
export declare function getPermissionCommandFailure(error: unknown, command: string, durationMs: number): PermissionCommandFailure | undefined;
|
|
2
21
|
export declare function executePermissionCommand(command: string, args: string[], timeoutMs?: number): Promise<{
|
|
3
22
|
stdout: string;
|
|
4
23
|
stderr: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permission-exec.d.ts","sourceRoot":"","sources":["../src/permission-exec.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permission-exec.d.ts","sourceRoot":"","sources":["../src/permission-exec.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,kCAAkC,QAAS,CAAC;AAEzD,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjE;AAgBD,qBAAa,sBAAuB,SAAQ,KAAM,YAAW,wBAAwB;IACnF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,YACE,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,OAAO,EACd,SAAS,SAAqC,EAc/C;CACF;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,wBAAwB,GAAG,SAAS,CAgBtC;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,SAAqC,GAC7C,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAa7C"}
|
package/dist/permission-exec.js
CHANGED
|
@@ -1,8 +1,64 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { promisify } from "node:util";
|
|
4
|
+
import { formatErrorDetail } from "./error-detail.js";
|
|
3
5
|
const execFileAsync = promisify(execFile);
|
|
4
6
|
export const DEFAULT_PERMISSION_EXEC_TIMEOUT_MS = 30_000;
|
|
7
|
+
export function formatPermissionErrorDetail(value) {
|
|
8
|
+
const formatted = formatErrorDetail(value);
|
|
9
|
+
return formatted.length > 400 ? `${formatted.slice(0, 399)}…` : formatted;
|
|
10
|
+
}
|
|
11
|
+
function commandFailureFields(error) {
|
|
12
|
+
const fields = error && typeof error === "object" ? error : {};
|
|
13
|
+
return {
|
|
14
|
+
timedOut: "killed" in fields && fields.killed === true &&
|
|
15
|
+
"signal" in fields && fields.signal === "SIGKILL",
|
|
16
|
+
exitCode: "code" in fields && typeof fields.code === "number" ? fields.code : null,
|
|
17
|
+
signal: "signal" in fields && typeof fields.signal === "string" ? fields.signal : null,
|
|
18
|
+
stderr: formatPermissionErrorDetail("stderr" in fields && (typeof fields.stderr === "string" || Buffer.isBuffer(fields.stderr))
|
|
19
|
+
? fields.stderr.toString() : ""),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export class PermissionCommandError extends Error {
|
|
23
|
+
command;
|
|
24
|
+
durationMs;
|
|
25
|
+
timedOut;
|
|
26
|
+
exitCode;
|
|
27
|
+
signal;
|
|
28
|
+
stderr;
|
|
29
|
+
constructor(command, durationMs, cause, timeoutMs = DEFAULT_PERMISSION_EXEC_TIMEOUT_MS) {
|
|
30
|
+
const fields = commandFailureFields(cause);
|
|
31
|
+
super(fields.timedOut
|
|
32
|
+
? `Windows permission inspection timed out after ${timeoutMs}ms`
|
|
33
|
+
: `Windows permission command ${formatPermissionErrorDetail(path.win32.basename(command))} failed (exit code ${fields.exitCode}, signal ${formatPermissionErrorDetail(fields.signal ?? "none")})`, { cause });
|
|
34
|
+
this.name = "PermissionCommandError";
|
|
35
|
+
this.command = command;
|
|
36
|
+
this.durationMs = Math.round(durationMs);
|
|
37
|
+
this.timedOut = fields.timedOut;
|
|
38
|
+
this.exitCode = fields.exitCode;
|
|
39
|
+
this.signal = fields.signal;
|
|
40
|
+
this.stderr = fields.stderr;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function getPermissionCommandFailure(error, command, durationMs) {
|
|
44
|
+
if (error instanceof PermissionCommandError) {
|
|
45
|
+
return {
|
|
46
|
+
command: error.command,
|
|
47
|
+
durationMs: error.durationMs,
|
|
48
|
+
timedOut: error.timedOut,
|
|
49
|
+
exitCode: error.exitCode,
|
|
50
|
+
signal: error.signal,
|
|
51
|
+
stderr: error.stderr,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (!error || typeof error !== "object" ||
|
|
55
|
+
!("code" in error || "signal" in error || "killed" in error || "stderr" in error)) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return { command, durationMs: Math.round(durationMs), ...commandFailureFields(error) };
|
|
59
|
+
}
|
|
5
60
|
export async function executePermissionCommand(command, args, timeoutMs = DEFAULT_PERMISSION_EXEC_TIMEOUT_MS) {
|
|
61
|
+
const startedAt = performance.now();
|
|
6
62
|
try {
|
|
7
63
|
return (await execFileAsync(command, args, {
|
|
8
64
|
encoding: "utf8",
|
|
@@ -13,16 +69,6 @@ export async function executePermissionCommand(command, args, timeoutMs = DEFAUL
|
|
|
13
69
|
}));
|
|
14
70
|
}
|
|
15
71
|
catch (err) {
|
|
16
|
-
|
|
17
|
-
typeof err === "object" &&
|
|
18
|
-
"killed" in err &&
|
|
19
|
-
err.killed === true &&
|
|
20
|
-
"signal" in err &&
|
|
21
|
-
err.signal === "SIGKILL") {
|
|
22
|
-
throw new Error(`Windows permission inspection timed out after ${timeoutMs}ms`, {
|
|
23
|
-
cause: err,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
throw err;
|
|
72
|
+
throw new PermissionCommandError(command, performance.now() - startedAt, err, timeoutMs);
|
|
27
73
|
}
|
|
28
74
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, type PermissionCheck, type PermissionCheckOptions, type SafeStatResult, } from "./permissions.js";
|
|
1
|
+
export { formatOctal, formatPermissionDetail, formatPermissionRemediation, inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, safeStat, type PermissionCheck, type PermissionCheckOptions, type PermissionCommandFailure, type SafeStatResult, } from "./permissions.js";
|
|
2
2
|
export { createPrivateDirectory, type CreatePrivateDirectoryOptions, } from "./private-directory.js";
|
|
3
3
|
export { readOwnerAndDacl, type OwnerAndDaclResult, type WindowsAccessControlEntry, type WindowsAceFlags, } from "./owner-dacl.js";
|
|
4
4
|
//# sourceMappingURL=permissions-public.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions-public.d.ts","sourceRoot":"","sources":["../src/permissions-public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,2BAA2B,EAC3B,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"permissions-public.d.ts","sourceRoot":"","sources":["../src/permissions-public.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,2BAA2B,EAC3B,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,eAAe,EACf,eAAe,EACf,QAAQ,EACR,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,cAAc,GACpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,GACnC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,GACrB,MAAM,iBAAiB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PermissionCommandFailure } from "./permission-exec.js";
|
|
1
2
|
import type { PermissionCheck, PermissionCheckOptions, SafeStatResult } from "./permissions.js";
|
|
2
3
|
export type PermissionExec = (command: string, args: string[]) => Promise<{
|
|
3
4
|
stdout: string;
|
|
@@ -19,6 +20,8 @@ export type WindowsAclSummary = {
|
|
|
19
20
|
untrustedGroup: WindowsAclEntry[];
|
|
20
21
|
trusted: WindowsAclEntry[];
|
|
21
22
|
error?: string;
|
|
23
|
+
errorDetail?: PermissionCommandFailure;
|
|
24
|
+
errorCause?: unknown;
|
|
22
25
|
};
|
|
23
26
|
export type WindowsUserInfoProvider = () => {
|
|
24
27
|
username?: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions-windows.d.ts","sourceRoot":"","sources":["../src/permissions-windows.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permissions-windows.d.ts","sourceRoot":"","sources":["../src/permissions-windows.ts"],"names":[],"mappings":"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"}
|