@openclaw/fs-safe 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +86 -0
- package/README.md +19 -6
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +20 -12
- package/dist/archive-entry.d.ts +1 -0
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +37 -6
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +4 -0
- package/dist/archive-input.d.ts +7 -0
- package/dist/archive-input.d.ts.map +1 -1
- package/dist/archive-input.js +11 -1
- package/dist/archive-limits.js +1 -1
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +12 -6
- package/dist/archive-policy.d.ts.map +1 -1
- package/dist/archive-policy.js +2 -1
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +62 -37
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +3 -2
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +15 -1
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +11 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +8 -4
- package/dist/archive-zip-entry.d.ts +7 -1
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +11 -1
- package/dist/archive-zip-integrity.d.ts +5 -0
- package/dist/archive-zip-integrity.d.ts.map +1 -0
- package/dist/archive-zip-integrity.js +54 -0
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +12 -1
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +9 -5
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +13 -2
- package/dist/deny-mutations.d.ts +1 -0
- package/dist/deny-mutations.d.ts.map +1 -1
- package/dist/deny-mutations.js +6 -39
- package/dist/device-path.d.ts +1 -0
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +6 -6
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +5 -4
- package/dist/error-detail.d.ts +2 -0
- package/dist/error-detail.d.ts.map +1 -0
- package/dist/error-detail.js +4 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +4 -1
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +88 -35
- package/dist/file-store-boundary.d.ts +6 -0
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +20 -5
- package/dist/file-store-sync-write.d.ts +9 -0
- package/dist/file-store-sync-write.d.ts.map +1 -0
- package/dist/file-store-sync-write.js +97 -0
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +62 -139
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +27 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -6
- package/dist/install-path.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +19 -13
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +1 -3
- package/dist/local-file-access.d.ts +3 -2
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +12 -6
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +35 -30
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +51 -12
- 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
- package/dist/native-binding.d.ts +1 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +14 -1
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +44 -17
- package/dist/output-sibling.d.ts.map +1 -1
- package/dist/output-sibling.js +6 -5
- package/dist/path-policy.d.ts.map +1 -1
- package/dist/path-policy.js +1 -7
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +4 -0
- package/dist/permission-exec.d.ts +6 -0
- package/dist/permission-exec.d.ts.map +1 -0
- package/dist/permission-exec.js +28 -0
- package/dist/permissions-windows.d.ts +56 -0
- package/dist/permissions-windows.d.ts.map +1 -0
- package/dist/permissions-windows.js +285 -0
- package/dist/permissions.d.ts +3 -46
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +17 -328
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +21 -4
- package/dist/pinned-write.js +2 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +45 -4
- package/dist/read-error.d.ts +2 -0
- package/dist/read-error.d.ts.map +1 -0
- package/dist/read-error.js +11 -0
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +21 -2
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +10 -1
- package/dist/replace-file-copy-fallback.d.ts +3 -0
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +18 -11
- package/dist/replace-file-descriptor.d.ts +33 -0
- package/dist/replace-file-descriptor.d.ts.map +1 -0
- package/dist/replace-file-descriptor.js +96 -0
- package/dist/replace-file.d.ts +9 -2
- package/dist/replace-file.d.ts.map +1 -1
- package/dist/replace-file.js +64 -72
- package/dist/root-context.d.ts +8 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +43 -2
- package/dist/root-errors.d.ts +7 -0
- package/dist/root-errors.d.ts.map +1 -1
- package/dist/root-errors.js +46 -1
- package/dist/root-file.d.ts +1 -0
- package/dist/root-file.d.ts.map +1 -1
- package/dist/root-file.js +2 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +216 -169
- package/dist/root-path-symlink.d.ts +3 -0
- package/dist/root-path-symlink.d.ts.map +1 -0
- package/dist/root-path-symlink.js +35 -0
- package/dist/root-path.d.ts +1 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +129 -190
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +15 -0
- package/dist/safe-path-segment.d.ts +2 -0
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +12 -0
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +29 -6
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +5 -1
- package/dist/secure-temp-dir.d.ts.map +1 -1
- package/dist/secure-temp-dir.js +6 -1
- package/dist/short-path.d.ts +2 -0
- package/dist/short-path.d.ts.map +1 -0
- package/dist/short-path.js +7 -0
- package/dist/sidecar-lock-acquire.d.ts +30 -0
- package/dist/sidecar-lock-acquire.d.ts.map +1 -0
- package/dist/sidecar-lock-acquire.js +260 -0
- package/dist/sidecar-lock-policy.d.ts +3 -0
- package/dist/sidecar-lock-policy.d.ts.map +1 -1
- package/dist/sidecar-lock-policy.js +20 -3
- package/dist/sidecar-lock-reclaim.d.ts +5 -1
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +59 -9
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +27 -226
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +9 -8
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +15 -4
- package/dist/test-hooks.d.ts +1 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/dist/timing.d.ts +2 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +9 -0
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +10 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +15 -0
- package/docs/advanced.md +7 -1
- package/docs/archive.md +27 -20
- package/docs/assets/readme-banner.jpg +0 -0
- package/docs/atomic.md +48 -19
- package/docs/errors.md +34 -8
- package/docs/file-store.md +14 -1
- package/docs/filename.md +17 -16
- package/docs/index.md +1 -0
- package/docs/install-path.md +9 -6
- package/docs/json.md +8 -2
- package/docs/local-roots.md +79 -103
- package/docs/native-helper.md +11 -5
- package/docs/native.md +2 -2
- package/docs/output.md +8 -3
- package/docs/path-scope.md +4 -1
- package/docs/path.md +7 -3
- package/docs/permissions.md +8 -0
- package/docs/public-api.md +109 -0
- package/docs/quickstart.md +6 -2
- package/docs/reading.md +13 -10
- package/docs/root.md +26 -4
- package/docs/secret-file.md +21 -4
- package/docs/security-model.md +21 -4
- package/docs/sidecar-lock.md +15 -5
- package/docs/temp.md +42 -5
- package/docs/test-hooks.md +2 -0
- package/docs/types.md +32 -15
- package/docs/writing.md +25 -12
- package/package.json +15 -4
- package/dist/archive-file-io.d.ts +0 -9
- package/dist/archive-file-io.d.ts.map +0 -1
- package/dist/archive-file-io.js +0 -11
- package/dist/file-store-source.d.ts +0 -5
- package/dist/file-store-source.d.ts.map +0 -1
- package/dist/file-store-source.js +0 -30
- package/dist/file-url.d.ts +0 -2
- package/dist/file-url.d.ts.map +0 -1
- package/dist/file-url.js +0 -1
- package/dist/path-stat.d.ts +0 -4
- package/dist/path-stat.d.ts.map +0 -1
- package/dist/path-stat.js +0 -15
- package/dist/windows-permissions-native.d.ts +0 -9
- package/dist/windows-permissions-native.d.ts.map +0 -1
- package/dist/windows-permissions-native.js +0 -31
package/dist/permissions.js
CHANGED
|
@@ -1,52 +1,6 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import fs from "node:fs/promises";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
7
|
-
import { inspectWindowsPermissionsNative } from "./windows-permissions-native.js";
|
|
8
|
-
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
9
|
-
import { inspectWindowsOwner, resolveWindowsCurrentUserSid, resolveWindowsPrincipalSids, } from "./windows-owner.js";
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
|
-
const INHERIT_FLAGS = new Set(["I", "OI", "CI", "IO", "NP"]);
|
|
12
|
-
const WORLD_PRINCIPALS = new Set(["everyone", "users", "builtin\\users", "authenticated users", "nt authority\\authenticated users", "anonymous logon", "nt authority\\anonymous logon", "guests", "builtin\\guests", "interactive", "nt authority\\interactive", "network", "nt authority\\network", "local"]);
|
|
13
|
-
const TRUSTED_BASE = new Set([
|
|
14
|
-
"nt authority\\system",
|
|
15
|
-
"system",
|
|
16
|
-
"builtin\\administrators",
|
|
17
|
-
"creator owner",
|
|
18
|
-
"autorite nt\\système",
|
|
19
|
-
"nt-autorität\\system",
|
|
20
|
-
"autoridad nt\\system",
|
|
21
|
-
"autoridade nt\\system",
|
|
22
|
-
]);
|
|
23
|
-
const WORLD_SUFFIXES = ["\\users", "\\authenticated users"];
|
|
24
|
-
const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
|
|
25
|
-
const TRUSTED_SIDS = new Set([
|
|
26
|
-
"s-1-5-18",
|
|
27
|
-
"s-1-5-32-544",
|
|
28
|
-
"s-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464",
|
|
29
|
-
]);
|
|
30
|
-
const WORLD_SIDS = new Set(["s-1-1-0", "s-1-5-11", "s-1-5-32-545", "s-1-5-7", "s-1-5-32-546", "s-1-5-4", "s-1-2-0", "s-1-5-2"]);
|
|
31
|
-
const STATUS_PREFIXES = [
|
|
32
|
-
"successfully processed",
|
|
33
|
-
"processed",
|
|
34
|
-
"failed processing",
|
|
35
|
-
"no mapping between account names",
|
|
36
|
-
];
|
|
37
|
-
function stripDiacritics(value) {
|
|
38
|
-
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
39
|
-
}
|
|
40
|
-
const TRUSTED_BASE_ASCII = new Set([...TRUSTED_BASE].map(stripDiacritics));
|
|
41
|
-
const normalize = (value) => normalizeLowercaseStringOrEmpty(value);
|
|
42
|
-
const defaultWindowsUserInfo = () => os.userInfo();
|
|
43
|
-
function defaultPermissionExec(command, args) {
|
|
44
|
-
return execFileAsync(command, args, {
|
|
45
|
-
encoding: "utf8",
|
|
46
|
-
windowsHide: true,
|
|
47
|
-
maxBuffer: 1024 * 1024,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
2
|
+
import { formatIcaclsResetCommand, inspectWindowsPermissions, } from "./permissions-windows.js";
|
|
3
|
+
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
50
4
|
export async function safeStat(targetPath) {
|
|
51
5
|
try {
|
|
52
6
|
const lst = await fs.lstat(targetPath);
|
|
@@ -103,58 +57,14 @@ export async function inspectPathPermissions(targetPath, opts) {
|
|
|
103
57
|
const bits = modeBits(effectiveMode);
|
|
104
58
|
const platform = opts?.platform ?? process.platform;
|
|
105
59
|
if (platform === "win32") {
|
|
106
|
-
|
|
107
|
-
targetPath, stat: st, effectiveIsDir, effectiveMode, bits,
|
|
108
|
-
});
|
|
109
|
-
if (native)
|
|
110
|
-
return native;
|
|
111
|
-
const owner = await inspectWindowsOwner({
|
|
60
|
+
return await inspectWindowsPermissions({
|
|
112
61
|
targetPath,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const acl = await inspectWindowsAcl(targetPath, {
|
|
117
|
-
env: opts?.env,
|
|
118
|
-
exec: opts?.exec,
|
|
119
|
-
currentUserSid: owner.currentUserSid,
|
|
120
|
-
principalSids: owner.principalSids,
|
|
121
|
-
principalTranslationFailed: owner.principalTranslationFailed,
|
|
122
|
-
});
|
|
123
|
-
const ownerFields = {
|
|
124
|
-
...(owner.sid ? { ownerSid: owner.sid } : {}),
|
|
125
|
-
...(owner.trusted !== undefined ? { ownerTrusted: owner.trusted } : {}),
|
|
126
|
-
...(owner.error ? { ownerError: owner.error } : {}),
|
|
127
|
-
};
|
|
128
|
-
if (!acl.ok) {
|
|
129
|
-
return {
|
|
130
|
-
ok: true,
|
|
131
|
-
isSymlink: st.isSymlink,
|
|
132
|
-
isDir: effectiveIsDir,
|
|
133
|
-
mode: effectiveMode,
|
|
134
|
-
bits,
|
|
135
|
-
source: "unknown",
|
|
136
|
-
worldWritable: false,
|
|
137
|
-
groupWritable: false,
|
|
138
|
-
worldReadable: false,
|
|
139
|
-
groupReadable: false,
|
|
140
|
-
...ownerFields,
|
|
141
|
-
error: acl.error,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
ok: true,
|
|
146
|
-
isSymlink: st.isSymlink,
|
|
147
|
-
isDir: effectiveIsDir,
|
|
148
|
-
mode: effectiveMode,
|
|
62
|
+
stat: st,
|
|
63
|
+
effectiveIsDir,
|
|
64
|
+
effectiveMode,
|
|
149
65
|
bits,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
groupWritable: acl.untrustedGroup.some((entry) => entry.canWrite),
|
|
153
|
-
worldReadable: acl.untrustedWorld.some((entry) => entry.canRead),
|
|
154
|
-
groupReadable: acl.untrustedGroup.some((entry) => entry.canRead),
|
|
155
|
-
...ownerFields,
|
|
156
|
-
aclSummary: formatWindowsAclSummary(acl),
|
|
157
|
-
};
|
|
66
|
+
opts,
|
|
67
|
+
});
|
|
158
68
|
}
|
|
159
69
|
return {
|
|
160
70
|
ok: true,
|
|
@@ -182,7 +92,15 @@ export function formatPermissionRemediation(params) {
|
|
|
182
92
|
env: params.env,
|
|
183
93
|
});
|
|
184
94
|
}
|
|
185
|
-
|
|
95
|
+
const optionSeparator = params.targetPath.startsWith("-") ? "-- " : "";
|
|
96
|
+
return (`chmod ${params.posixMode.toString(8).padStart(3, "0")} ${optionSeparator}` +
|
|
97
|
+
formatPosixShellArgument(params.targetPath));
|
|
98
|
+
}
|
|
99
|
+
function formatPosixShellArgument(value) {
|
|
100
|
+
if (value && /^[A-Za-z0-9_@%+=:,./-]+$/.test(value)) {
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
186
104
|
}
|
|
187
105
|
export function modeBits(mode) {
|
|
188
106
|
return mode == null ? null : mode & 0o777;
|
|
@@ -202,232 +120,3 @@ export function isWorldReadable(bits) {
|
|
|
202
120
|
export function isGroupReadable(bits) {
|
|
203
121
|
return bits != null && (bits & 0o040) !== 0;
|
|
204
122
|
}
|
|
205
|
-
function normalizeSid(value) {
|
|
206
|
-
const normalized = normalize(value);
|
|
207
|
-
return normalized.startsWith("*") ? normalized.slice(1) : normalized;
|
|
208
|
-
}
|
|
209
|
-
export function resolveWindowsUserPrincipal(env, userInfo = defaultWindowsUserInfo) {
|
|
210
|
-
const username = env?.USERNAME?.trim() || userInfo().username?.trim();
|
|
211
|
-
if (!username) {
|
|
212
|
-
return null;
|
|
213
|
-
}
|
|
214
|
-
const domain = env?.USERDOMAIN?.trim();
|
|
215
|
-
return domain ? `${domain}\\${username}` : username;
|
|
216
|
-
}
|
|
217
|
-
function buildTrustedPrincipals(env) {
|
|
218
|
-
const trusted = new Set(TRUSTED_BASE);
|
|
219
|
-
const principal = resolveWindowsUserPrincipal(env);
|
|
220
|
-
if (principal) {
|
|
221
|
-
trusted.add(normalize(principal));
|
|
222
|
-
const userOnly = principal.split("\\").at(-1);
|
|
223
|
-
if (userOnly) {
|
|
224
|
-
trusted.add(normalize(userOnly));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
const userSid = normalizeSid(env?.USERSID ?? "");
|
|
228
|
-
if (userSid && SID_RE.test(userSid) && !WORLD_SIDS.has(userSid)) {
|
|
229
|
-
trusted.add(userSid);
|
|
230
|
-
}
|
|
231
|
-
return trusted;
|
|
232
|
-
}
|
|
233
|
-
function classifyPrincipal(principal, trustedPrincipals) {
|
|
234
|
-
const normalized = normalize(principal);
|
|
235
|
-
if (SID_RE.test(normalized)) {
|
|
236
|
-
const sid = normalizeSid(normalized);
|
|
237
|
-
if (WORLD_SIDS.has(sid))
|
|
238
|
-
return "world";
|
|
239
|
-
if (TRUSTED_SIDS.has(sid) || trustedPrincipals.has(sid))
|
|
240
|
-
return "trusted";
|
|
241
|
-
return "group";
|
|
242
|
-
}
|
|
243
|
-
if (trustedPrincipals.has(normalized) || TRUSTED_BASE.has(normalized)) {
|
|
244
|
-
return "trusted";
|
|
245
|
-
}
|
|
246
|
-
if (WORLD_PRINCIPALS.has(normalized) ||
|
|
247
|
-
WORLD_SUFFIXES.some((suffix) => normalized.endsWith(suffix))) {
|
|
248
|
-
return "world";
|
|
249
|
-
}
|
|
250
|
-
const stripped = stripDiacritics(normalized);
|
|
251
|
-
if (stripped !== normalized && TRUSTED_BASE_ASCII.has(stripped)) {
|
|
252
|
-
return "trusted";
|
|
253
|
-
}
|
|
254
|
-
return "group";
|
|
255
|
-
}
|
|
256
|
-
function rightsFromTokens(tokens) {
|
|
257
|
-
const upper = tokens.join("").toUpperCase();
|
|
258
|
-
return {
|
|
259
|
-
canWrite: upper.includes("F") || upper.includes("M") || upper.includes("W") || upper.includes("D"),
|
|
260
|
-
canRead: upper.includes("F") || upper.includes("M") || upper.includes("R"),
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
function stripTargetPrefix(params) {
|
|
264
|
-
if (params.lowerLine.startsWith(params.lowerTarget)) {
|
|
265
|
-
return params.trimmedLine.slice(params.normalizedTarget.length).trim();
|
|
266
|
-
}
|
|
267
|
-
if (params.lowerLine.startsWith(params.quotedLower)) {
|
|
268
|
-
return params.trimmedLine.slice(params.quotedTarget.length).trim();
|
|
269
|
-
}
|
|
270
|
-
return params.trimmedLine;
|
|
271
|
-
}
|
|
272
|
-
function parseAceEntry(entry) {
|
|
273
|
-
if (!entry.includes("("))
|
|
274
|
-
return null;
|
|
275
|
-
const idx = entry.indexOf(":");
|
|
276
|
-
if (idx === -1)
|
|
277
|
-
return null;
|
|
278
|
-
const principal = entry.slice(0, idx).trim();
|
|
279
|
-
const rawRights = entry.slice(idx + 1).trim();
|
|
280
|
-
const tokens = rawRights
|
|
281
|
-
.match(/\(([^)]+)\)/g)
|
|
282
|
-
?.map((token) => token.slice(1, -1).trim())
|
|
283
|
-
.filter(Boolean) ?? [];
|
|
284
|
-
if (tokens.some((token) => token.toUpperCase() === "DENY"))
|
|
285
|
-
return null;
|
|
286
|
-
const rights = tokens.filter((token) => !INHERIT_FLAGS.has(token.toUpperCase()));
|
|
287
|
-
if (rights.length === 0)
|
|
288
|
-
return null;
|
|
289
|
-
const normalizedPrincipal = normalizeSid(principal);
|
|
290
|
-
return {
|
|
291
|
-
principal,
|
|
292
|
-
...(SID_RE.test(normalizedPrincipal) ? { sid: normalizedPrincipal } : {}),
|
|
293
|
-
rights,
|
|
294
|
-
rawRights,
|
|
295
|
-
...rightsFromTokens(rights),
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
export function parseIcaclsOutput(output, targetPath) {
|
|
299
|
-
const entries = [];
|
|
300
|
-
const normalizedTarget = targetPath.trim();
|
|
301
|
-
const lowerTarget = normalizedTarget.toLowerCase();
|
|
302
|
-
const quotedTarget = `"${normalizedTarget}"`;
|
|
303
|
-
const quotedLower = quotedTarget.toLowerCase();
|
|
304
|
-
for (const rawLine of output.split(/\r?\n/)) {
|
|
305
|
-
const line = rawLine.trimEnd();
|
|
306
|
-
if (!line.trim())
|
|
307
|
-
continue;
|
|
308
|
-
const trimmed = line.trim();
|
|
309
|
-
const lower = trimmed.toLowerCase();
|
|
310
|
-
if (STATUS_PREFIXES.some((prefix) => lower.startsWith(prefix)))
|
|
311
|
-
continue;
|
|
312
|
-
const parsed = parseAceEntry(stripTargetPrefix({
|
|
313
|
-
trimmedLine: trimmed,
|
|
314
|
-
lowerLine: lower,
|
|
315
|
-
normalizedTarget,
|
|
316
|
-
lowerTarget,
|
|
317
|
-
quotedTarget,
|
|
318
|
-
quotedLower,
|
|
319
|
-
}));
|
|
320
|
-
if (parsed)
|
|
321
|
-
entries.push(parsed);
|
|
322
|
-
}
|
|
323
|
-
return entries;
|
|
324
|
-
}
|
|
325
|
-
export function summarizeWindowsAcl(entries, env) {
|
|
326
|
-
const trustedPrincipals = buildTrustedPrincipals(env);
|
|
327
|
-
const trusted = [];
|
|
328
|
-
const untrustedWorld = [];
|
|
329
|
-
const untrustedGroup = [];
|
|
330
|
-
for (const entry of entries) {
|
|
331
|
-
const classification = classifyPrincipal(entry.sid ?? entry.principal, trustedPrincipals);
|
|
332
|
-
if (classification === "trusted")
|
|
333
|
-
trusted.push(entry);
|
|
334
|
-
else if (classification === "world")
|
|
335
|
-
untrustedWorld.push(entry);
|
|
336
|
-
else
|
|
337
|
-
untrustedGroup.push(entry);
|
|
338
|
-
}
|
|
339
|
-
return { trusted, untrustedWorld, untrustedGroup };
|
|
340
|
-
}
|
|
341
|
-
export async function inspectWindowsAcl(targetPath, opts) {
|
|
342
|
-
const exec = opts?.exec ?? defaultPermissionExec;
|
|
343
|
-
try {
|
|
344
|
-
if (opts?.principalTranslationFailed) {
|
|
345
|
-
throw new Error("Windows ACL principal SID translation failed");
|
|
346
|
-
}
|
|
347
|
-
const { stdout, stderr } = await exec(resolveWindowsSystemCommand("icacls.exe", opts?.env), [
|
|
348
|
-
targetPath,
|
|
349
|
-
]);
|
|
350
|
-
let entries = parseIcaclsOutput(`${stdout}\n${stderr}`.trim(), targetPath);
|
|
351
|
-
const unresolvedPrincipals = entries
|
|
352
|
-
.filter((entry) => !entry.sid)
|
|
353
|
-
.map((entry) => entry.principal);
|
|
354
|
-
const principalSids = await resolveWindowsPrincipalSids({
|
|
355
|
-
principals: unresolvedPrincipals,
|
|
356
|
-
known: opts?.principalSids,
|
|
357
|
-
env: opts?.env,
|
|
358
|
-
exec,
|
|
359
|
-
});
|
|
360
|
-
entries = entries.map((entry) => {
|
|
361
|
-
const sid = entry.sid ?? principalSids[entry.principal.toLowerCase()];
|
|
362
|
-
if (!sid) {
|
|
363
|
-
throw new Error(`Windows ACL principal SID could not be verified: ${entry.principal}`);
|
|
364
|
-
}
|
|
365
|
-
return { ...entry, sid };
|
|
366
|
-
});
|
|
367
|
-
let currentUserSid = normalizeSid(opts?.currentUserSid ?? "");
|
|
368
|
-
let effectiveEnv = currentUserSid ? { USERSID: currentUserSid } : undefined;
|
|
369
|
-
let { trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv);
|
|
370
|
-
const needsUserSidResolution = !currentUserSid && untrustedGroup.some((entry) => entry.sid && !TRUSTED_SIDS.has(entry.sid));
|
|
371
|
-
if (needsUserSidResolution) {
|
|
372
|
-
currentUserSid =
|
|
373
|
-
(await resolveWindowsCurrentUserSid({ exec, env: opts?.env })) ?? "";
|
|
374
|
-
if (currentUserSid) {
|
|
375
|
-
effectiveEnv = { USERSID: currentUserSid };
|
|
376
|
-
({ trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv));
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
return { ok: true, entries, trusted, untrustedWorld, untrustedGroup };
|
|
380
|
-
}
|
|
381
|
-
catch (err) {
|
|
382
|
-
return {
|
|
383
|
-
ok: false,
|
|
384
|
-
entries: [],
|
|
385
|
-
trusted: [],
|
|
386
|
-
untrustedWorld: [],
|
|
387
|
-
untrustedGroup: [],
|
|
388
|
-
error: String(err),
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
export function formatWindowsAclSummary(summary) {
|
|
393
|
-
if (!summary.ok)
|
|
394
|
-
return "unknown";
|
|
395
|
-
const untrusted = [...summary.untrustedWorld, ...summary.untrustedGroup];
|
|
396
|
-
return untrusted.length === 0
|
|
397
|
-
? "trusted-only"
|
|
398
|
-
: untrusted.map((entry) => `${entry.principal}:${entry.rawRights}`).join(", ");
|
|
399
|
-
}
|
|
400
|
-
export function formatIcaclsResetCommand(targetPath, opts) {
|
|
401
|
-
const command = resolveWindowsSystemCommand("icacls.exe", opts.env);
|
|
402
|
-
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo) ?? "%USERNAME%";
|
|
403
|
-
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
404
|
-
return [
|
|
405
|
-
command,
|
|
406
|
-
`"${targetPath}"`,
|
|
407
|
-
"/inheritance:r",
|
|
408
|
-
"/grant:r",
|
|
409
|
-
`"${user}:${grant}"`,
|
|
410
|
-
"/grant:r",
|
|
411
|
-
`"*S-1-5-18:${grant}"`,
|
|
412
|
-
].join(" ");
|
|
413
|
-
}
|
|
414
|
-
export function createIcaclsResetCommand(targetPath, opts) {
|
|
415
|
-
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo);
|
|
416
|
-
if (!user) {
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
420
|
-
const args = [
|
|
421
|
-
targetPath,
|
|
422
|
-
"/inheritance:r",
|
|
423
|
-
"/grant:r",
|
|
424
|
-
`${user}:${grant}`,
|
|
425
|
-
"/grant:r",
|
|
426
|
-
`*S-1-5-18:${grant}`,
|
|
427
|
-
];
|
|
428
|
-
return {
|
|
429
|
-
command: resolveWindowsSystemCommand("icacls.exe", opts.env),
|
|
430
|
-
args,
|
|
431
|
-
display: formatIcaclsResetCommand(targetPath, opts),
|
|
432
|
-
};
|
|
433
|
-
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-open.d.ts","sourceRoot":"","sources":["../src/pinned-open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"pinned-open.d.ts","sourceRoot":"","sources":["../src/pinned-open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAKzB,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,2BAA2B,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAExE,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,OAAO,EAAE,EACT,WAAW,GAAG,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CACpF,CAAC;AAYF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,yBAAyB,CAAC;IACxC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,GAAG,oBAAoB,CAiFvB"}
|
package/dist/pinned-open.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { isUnsafeDeviceReadPath } from "./device-path.js";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
3
4
|
import { sameFileIdentity as hasSameFileIdentity } from "./file-identity.js";
|
|
4
5
|
function isExpectedPathError(error) {
|
|
5
6
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
@@ -30,10 +31,18 @@ export function openPinnedFileSync(params) {
|
|
|
30
31
|
}
|
|
31
32
|
const preOpenStat = ioFs.lstatSync(realPath);
|
|
32
33
|
if (!isAllowedType(preOpenStat, allowedType)) {
|
|
33
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
ok: false,
|
|
36
|
+
reason: "validation",
|
|
37
|
+
error: new FsSafeError("not-file", "path does not have the required file type"),
|
|
38
|
+
};
|
|
34
39
|
}
|
|
35
40
|
if (params.rejectHardlinks && preOpenStat.isFile() && preOpenStat.nlink > 1) {
|
|
36
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
reason: "validation",
|
|
44
|
+
error: new FsSafeError("hardlink", "path must not be hardlinked"),
|
|
45
|
+
};
|
|
37
46
|
}
|
|
38
47
|
if (params.maxBytes !== undefined &&
|
|
39
48
|
preOpenStat.isFile() &&
|
|
@@ -43,10 +52,18 @@ export function openPinnedFileSync(params) {
|
|
|
43
52
|
fd = ioFs.openSync(realPath, openReadFlags);
|
|
44
53
|
const openedStat = ioFs.fstatSync(fd);
|
|
45
54
|
if (!isAllowedType(openedStat, allowedType)) {
|
|
46
|
-
return {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
reason: "validation",
|
|
58
|
+
error: new FsSafeError("not-file", "path does not have the required file type"),
|
|
59
|
+
};
|
|
47
60
|
}
|
|
48
61
|
if (params.rejectHardlinks && openedStat.isFile() && openedStat.nlink > 1) {
|
|
49
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
reason: "validation",
|
|
65
|
+
error: new FsSafeError("hardlink", "path must not be hardlinked"),
|
|
66
|
+
};
|
|
50
67
|
}
|
|
51
68
|
if (params.maxBytes !== undefined && openedStat.isFile() && openedStat.size > params.maxBytes) {
|
|
52
69
|
return { ok: false, reason: "validation" };
|
package/dist/pinned-write.js
CHANGED
|
@@ -69,7 +69,7 @@ export async function runPinnedWriteHelper(params) {
|
|
|
69
69
|
return await runPinnedWriteFallback(params);
|
|
70
70
|
}
|
|
71
71
|
const native = getNativeBinding();
|
|
72
|
-
if (native
|
|
72
|
+
if (native) {
|
|
73
73
|
return await runPinnedWriteNative(native, params);
|
|
74
74
|
}
|
|
75
75
|
return await runPinnedWriteFallback(params);
|
|
@@ -107,6 +107,7 @@ async function runPinnedWriteFallback(params) {
|
|
|
107
107
|
parentPath = await mkdirPathComponentsWithGuards({
|
|
108
108
|
rootReal: params.rootPath,
|
|
109
109
|
targetPath: parentPath,
|
|
110
|
+
beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
|
|
110
111
|
});
|
|
111
112
|
}
|
|
112
113
|
const parentGuard = params.mkdir
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"private-temp-workspace.d.ts","sourceRoot":"","sources":["../src/private-temp-workspace.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAUzB,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,SAAS,GAAG,mBAAmB,CAAC;AAErF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CACP,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GACtC,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAC/C,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,uBAAuB,CAAC;IAClC,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;IAC3D,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,CAAC;IAC5F,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,IAAI,0BAA0B,CAAC;IACtC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;CAC1B,CAAC;AA2KF,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,aAAa,CAAC,CAExB;AAED,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC5C,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,oBAAoB,GAC5B,iBAAiB,CA0EnB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,CAAC,GACvC,CAAC,CAUH"}
|
|
@@ -3,8 +3,11 @@ import fsSync from "node:fs";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileStore, fileStoreSync, } from "./file-store.js";
|
|
6
|
-
import {
|
|
6
|
+
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { sameFileIdentity } from "./file-identity.js";
|
|
8
|
+
import { isNotFoundPathError } from "./path.js";
|
|
9
|
+
import { throwFsSafeReadError } from "./read-error.js";
|
|
10
|
+
import { matchRootFileOpenFailure, openRootFileSync, } from "./root-file.js";
|
|
8
11
|
import { registerTempPathForExit, } from "./temp-cleanup.js";
|
|
9
12
|
function sanitizeTempPrefix(prefix) {
|
|
10
13
|
const sanitized = prefix.trim().replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
@@ -29,6 +32,32 @@ function assertWorkspaceFileName(fileName) {
|
|
|
29
32
|
}
|
|
30
33
|
return value;
|
|
31
34
|
}
|
|
35
|
+
function throwTempWorkspaceOpenFailure(failure) {
|
|
36
|
+
return matchRootFileOpenFailure(failure, {
|
|
37
|
+
path: ({ error }) => {
|
|
38
|
+
if (isNotFoundPathError(error)) {
|
|
39
|
+
throw new FsSafeError("not-found", "temp workspace file not found", { cause: error });
|
|
40
|
+
}
|
|
41
|
+
throw new FsSafeError("path-mismatch", "temp workspace target changed during read", {
|
|
42
|
+
cause: error,
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
validation: ({ error }) => {
|
|
46
|
+
if (error instanceof FsSafeError) {
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
throw new FsSafeError("path-mismatch", "temp workspace target failed read validation", {
|
|
50
|
+
cause: error,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
io: ({ error }) => throwFsSafeReadError(error, "temp workspace"),
|
|
54
|
+
fallback: ({ error }) => {
|
|
55
|
+
throw new FsSafeError("path-mismatch", "temp workspace target changed during read", {
|
|
56
|
+
cause: error,
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
32
61
|
async function ensurePrivateDirectory(dir, mode) {
|
|
33
62
|
await fs.mkdir(dir, { recursive: true, mode });
|
|
34
63
|
const stat = await fs.stat(dir);
|
|
@@ -105,7 +134,14 @@ async function createTempWorkspace(options) {
|
|
|
105
134
|
trailingNewline: writeOptions?.trailingNewline,
|
|
106
135
|
}),
|
|
107
136
|
copyIn: async (fileName, sourcePath) => await store.copyIn(assertWorkspaceFileName(fileName), sourcePath, { mode }),
|
|
108
|
-
read: async (fileName) =>
|
|
137
|
+
read: async (fileName) => {
|
|
138
|
+
try {
|
|
139
|
+
return await store.readBytes(assertWorkspaceFileName(fileName));
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throwFsSafeReadError(error, "temp workspace");
|
|
143
|
+
}
|
|
144
|
+
},
|
|
109
145
|
cleanup: async () => {
|
|
110
146
|
try {
|
|
111
147
|
return await cleanupWorkspace(dir, identity);
|
|
@@ -184,10 +220,15 @@ export function tempWorkspaceSync(options) {
|
|
|
184
220
|
rejectHardlinks: true,
|
|
185
221
|
});
|
|
186
222
|
if (!opened.ok) {
|
|
187
|
-
|
|
223
|
+
throwTempWorkspaceOpenFailure(opened);
|
|
188
224
|
}
|
|
189
225
|
try {
|
|
190
|
-
|
|
226
|
+
try {
|
|
227
|
+
return fsSync.readFileSync(opened.fd);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
throwFsSafeReadError(error, "temp workspace");
|
|
231
|
+
}
|
|
191
232
|
}
|
|
192
233
|
finally {
|
|
193
234
|
fsSync.closeSync(opened.fd);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-error.d.ts","sourceRoot":"","sources":["../src/read-error.ts"],"names":[],"mappings":"AAGA,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAQzE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { isNodeError } from "./path.js";
|
|
3
|
+
export function throwFsSafeReadError(error, label) {
|
|
4
|
+
if (error instanceof FsSafeError) {
|
|
5
|
+
throw error;
|
|
6
|
+
}
|
|
7
|
+
if (isNodeError(error)) {
|
|
8
|
+
throw new FsSafeError("read-failed", `${label} target could not be read`, { cause: error });
|
|
9
|
+
}
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;AAW7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CAQR;AAsBD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CActF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAc3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAyD3C;
|
|
1
|
+
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;AAW7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CAQR;AAsBD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CActF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAc3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAyD3C;AA6DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CA6BA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDxF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CA2D7E"}
|
package/dist/regular-file.js
CHANGED
|
@@ -133,9 +133,19 @@ function verifyStableReadTarget(params) {
|
|
|
133
133
|
}
|
|
134
134
|
function readOpenedRegularFileSync(params) {
|
|
135
135
|
const stat = fsSync.fstatSync(params.fd);
|
|
136
|
+
let pathStat;
|
|
137
|
+
try {
|
|
138
|
+
pathStat = fsSync.lstatSync(params.filePath);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (isNotFoundPathError(error)) {
|
|
142
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
136
146
|
verifyStableReadTarget({
|
|
137
147
|
filePath: params.filePath,
|
|
138
|
-
pathStat
|
|
148
|
+
pathStat,
|
|
139
149
|
postOpenStat: stat,
|
|
140
150
|
preOpenStat: params.preOpenStat,
|
|
141
151
|
});
|
|
@@ -168,7 +178,16 @@ export function readRegularFileSync(params) {
|
|
|
168
178
|
if (params.maxBytes !== undefined && result.stat.size > params.maxBytes) {
|
|
169
179
|
throw regularFileTooLargeError(params.filePath, params.maxBytes);
|
|
170
180
|
}
|
|
171
|
-
|
|
181
|
+
let fd;
|
|
182
|
+
try {
|
|
183
|
+
fd = fsSync.openSync(params.filePath, resolveRegularFileReadFlags());
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (isNotFoundPathError(error)) {
|
|
187
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
172
191
|
try {
|
|
173
192
|
return readOpenedRegularFileSync({
|
|
174
193
|
fd,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"replace-directory.d.ts","sourceRoot":"","sources":["../src/replace-directory.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { guardedRename, guardedRm } from "./guarded-mutation.js";
|
|
5
|
+
import { assertSafePathPrefix } from "./safe-path-segment.js";
|
|
6
|
+
import { serializePathWrite } from "./write-queue.js";
|
|
4
7
|
export async function replaceDirectoryAtomic(options) {
|
|
5
8
|
const targetDir = path.resolve(options.targetDir);
|
|
9
|
+
await serializePathWrite(targetDir, async () => {
|
|
10
|
+
await replaceDirectoryAtomicUnserialized(options, targetDir);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
async function replaceDirectoryAtomicUnserialized(options, targetDir) {
|
|
6
14
|
const stagedDir = path.resolve(options.stagedDir);
|
|
7
15
|
const parentDir = path.dirname(targetDir);
|
|
8
|
-
const
|
|
16
|
+
const backupPrefix = assertSafePathPrefix(options.backupPrefix ?? ".fs-safe-dir-backup-", { label: "atomic directory backup prefix" });
|
|
17
|
+
const backupDir = path.join(parentDir, `${backupPrefix}${process.pid}-${randomUUID()}`);
|
|
9
18
|
let backupCreated = false;
|
|
10
19
|
await fs.mkdir(parentDir, { recursive: true });
|
|
11
20
|
try {
|
|
@@ -21,6 +21,7 @@ export declare function copyFallbackReplace(params: {
|
|
|
21
21
|
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
22
22
|
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
23
23
|
maxRestoreBytes?: number;
|
|
24
|
+
sync: boolean;
|
|
24
25
|
}): Promise<void>;
|
|
25
26
|
export declare function copyFallbackReplaceSync(params: {
|
|
26
27
|
fsModule: SyncFallbackFs;
|
|
@@ -29,6 +30,8 @@ export declare function copyFallbackReplaceSync(params: {
|
|
|
29
30
|
destinationHardlinks?: ReplaceFileDestinationHardlinkPolicy;
|
|
30
31
|
restore: ReplaceFileCopyFallbackRestorePolicy;
|
|
31
32
|
maxRestoreBytes?: number;
|
|
33
|
+
fchmodSync?: (fd: number, mode: number) => void;
|
|
34
|
+
sync: boolean;
|
|
32
35
|
}): void;
|
|
33
36
|
export {};
|
|
34
37
|
//# sourceMappingURL=replace-file-copy-fallback.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAsB,MAAM,SAAS,CAAC;AAK7C,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAuFF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAwBN;
|
|
1
|
+
{"version":3,"file":"replace-file-copy-fallback.d.ts","sourceRoot":"","sources":["../src/replace-file-copy-fallback.ts"],"names":[],"mappings":"AAAA,OAAO,MAAsB,MAAM,SAAS,CAAC;AAK7C,MAAM,MAAM,oCAAoC,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,oCAAoC,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAC/E,MAAM,MAAM,+BAA+B,GAAG,UAAU,GAAG,gBAAgB,CAAC;AAC5E,MAAM,MAAM,sCAAsC,GAAG;IACnD,OAAO,EAAE,+BAA+B,CAAC;CAC1C,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,cAAc,kBAAkB,EAAE,KAAK,CAAC;IAC/C,IAAI,EAAE,cAAc,kBAAkB,EAAE,IAAI,CAAC;IAC7C,EAAE,EAAE,cAAc,kBAAkB,EAAE,EAAE,CAAC;IACzC,MAAM,EAAE,cAAc,kBAAkB,EAAE,MAAM,CAAC;CAClD,CAAC;AAEF,KAAK,cAAc,GAAG,IAAI,CACxB,OAAO,MAAM,EACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,WAAW,CACd,CAAC;AAuFF,wBAAsB,+BAA+B,CACnD,QAAQ,EAAE,eAAe,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,wBAAgB,mCAAmC,CACjD,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,oCAAoC,GAC5C,IAAI,CAwBN;AAmID,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,eAAe,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DhB;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,cAAc,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB,CAAC,EAAE,oCAAoC,CAAC;IAC5D,OAAO,EAAE,oCAAoC,CAAC;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,IAAI,CA6EP"}
|