@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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { getNativeBinding } from "./native.js";
|
|
3
|
+
import { executePermissionCommand } from "./permission-exec.js";
|
|
4
|
+
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
5
|
+
import { resolveWindowsSystemCommand } from "./windows-command.js";
|
|
6
|
+
import { inspectWindowsOwner, resolveWindowsCurrentUserSid, resolveWindowsPrincipalSids, } from "./windows-owner.js";
|
|
7
|
+
const INHERIT_FLAGS = new Set(["I", "OI", "CI", "IO", "NP"]);
|
|
8
|
+
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"]);
|
|
9
|
+
const TRUSTED_BASE = new Set([
|
|
10
|
+
"nt authority\\system",
|
|
11
|
+
"system",
|
|
12
|
+
"builtin\\administrators",
|
|
13
|
+
"creator owner",
|
|
14
|
+
"autorite nt\\système",
|
|
15
|
+
"nt-autorität\\system",
|
|
16
|
+
"autoridad nt\\system",
|
|
17
|
+
"autoridade nt\\system",
|
|
18
|
+
]);
|
|
19
|
+
const WORLD_SUFFIXES = ["\\users", "\\authenticated users"];
|
|
20
|
+
const SID_RE = /^\*?s-\d+-\d+(-\d+)+$/i;
|
|
21
|
+
const TRUSTED_SIDS = new Set([
|
|
22
|
+
"s-1-5-18",
|
|
23
|
+
"s-1-5-32-544",
|
|
24
|
+
"s-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464",
|
|
25
|
+
]);
|
|
26
|
+
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"]);
|
|
27
|
+
const STATUS_PREFIXES = [
|
|
28
|
+
"successfully processed",
|
|
29
|
+
"processed",
|
|
30
|
+
"failed processing",
|
|
31
|
+
"no mapping between account names",
|
|
32
|
+
];
|
|
33
|
+
function stripDiacritics(value) {
|
|
34
|
+
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
35
|
+
}
|
|
36
|
+
const TRUSTED_BASE_ASCII = new Set([...TRUSTED_BASE].map(stripDiacritics));
|
|
37
|
+
const normalize = (value) => normalizeLowercaseStringOrEmpty(value);
|
|
38
|
+
const defaultWindowsUserInfo = () => os.userInfo();
|
|
39
|
+
const defaultPermissionExec = executePermissionCommand;
|
|
40
|
+
function inspectWindowsPermissionsNative(params) {
|
|
41
|
+
const native = getNativeBinding();
|
|
42
|
+
if (!native)
|
|
43
|
+
return undefined;
|
|
44
|
+
try {
|
|
45
|
+
const facts = native.readOwnerAndDacl(params.targetPath);
|
|
46
|
+
if (facts.fallbackRequired)
|
|
47
|
+
return undefined;
|
|
48
|
+
return {
|
|
49
|
+
ok: true,
|
|
50
|
+
isSymlink: params.stat.isSymlink,
|
|
51
|
+
isDir: params.effectiveIsDir,
|
|
52
|
+
mode: params.effectiveMode,
|
|
53
|
+
bits: params.bits,
|
|
54
|
+
source: "windows-acl",
|
|
55
|
+
worldWritable: facts.worldWritable,
|
|
56
|
+
groupWritable: facts.groupWritable,
|
|
57
|
+
worldReadable: facts.worldReadable,
|
|
58
|
+
groupReadable: facts.groupReadable,
|
|
59
|
+
ownerSid: facts.ownerSid,
|
|
60
|
+
ownerTrusted: facts.ownerClass !== "foreign",
|
|
61
|
+
aclSummary: `native owner=${facts.ownerClass} world=` +
|
|
62
|
+
`${facts.worldReadable ? "r" : "-"}${facts.worldWritable ? "w" : "-"} ` +
|
|
63
|
+
`group=${facts.groupReadable ? "r" : "-"}${facts.groupWritable ? "w" : "-"}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function inspectWindowsPermissions(params) {
|
|
71
|
+
const native = inspectWindowsPermissionsNative(params);
|
|
72
|
+
if (native)
|
|
73
|
+
return native;
|
|
74
|
+
const unverified = {
|
|
75
|
+
ok: true,
|
|
76
|
+
isSymlink: params.stat.isSymlink,
|
|
77
|
+
isDir: params.effectiveIsDir,
|
|
78
|
+
mode: params.effectiveMode,
|
|
79
|
+
bits: params.bits,
|
|
80
|
+
source: "unknown",
|
|
81
|
+
worldWritable: false,
|
|
82
|
+
groupWritable: false,
|
|
83
|
+
worldReadable: false,
|
|
84
|
+
groupReadable: false,
|
|
85
|
+
};
|
|
86
|
+
const owner = await inspectWindowsOwner({
|
|
87
|
+
targetPath: params.targetPath,
|
|
88
|
+
env: params.opts?.env,
|
|
89
|
+
exec: params.opts?.exec ?? defaultPermissionExec,
|
|
90
|
+
});
|
|
91
|
+
if (owner.error) {
|
|
92
|
+
const error = `Windows owner inspection failed: ${owner.error}`;
|
|
93
|
+
return { ...unverified, ownerError: owner.error, error };
|
|
94
|
+
}
|
|
95
|
+
const acl = await inspectWindowsAcl(params.targetPath, {
|
|
96
|
+
env: params.opts?.env,
|
|
97
|
+
exec: params.opts?.exec,
|
|
98
|
+
currentUserSid: owner.currentUserSid,
|
|
99
|
+
principalSids: owner.principalSids,
|
|
100
|
+
principalTranslationFailed: owner.principalTranslationFailed,
|
|
101
|
+
});
|
|
102
|
+
const ownerFields = {
|
|
103
|
+
...(owner.sid ? { ownerSid: owner.sid } : {}),
|
|
104
|
+
...(owner.trusted !== undefined ? { ownerTrusted: owner.trusted } : {}),
|
|
105
|
+
...(owner.error ? { ownerError: owner.error } : {}),
|
|
106
|
+
};
|
|
107
|
+
if (!acl.ok)
|
|
108
|
+
return { ...unverified, ...ownerFields, error: acl.error };
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
isSymlink: params.stat.isSymlink,
|
|
112
|
+
isDir: params.effectiveIsDir,
|
|
113
|
+
mode: params.effectiveMode,
|
|
114
|
+
bits: params.bits,
|
|
115
|
+
source: "windows-acl",
|
|
116
|
+
worldWritable: acl.untrustedWorld.some((entry) => entry.canWrite),
|
|
117
|
+
groupWritable: acl.untrustedGroup.some((entry) => entry.canWrite),
|
|
118
|
+
worldReadable: acl.untrustedWorld.some((entry) => entry.canRead),
|
|
119
|
+
groupReadable: acl.untrustedGroup.some((entry) => entry.canRead),
|
|
120
|
+
...ownerFields,
|
|
121
|
+
aclSummary: formatWindowsAclSummary(acl),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function normalizeSid(value) {
|
|
125
|
+
const normalized = normalize(value);
|
|
126
|
+
return normalized.startsWith("*") ? normalized.slice(1) : normalized;
|
|
127
|
+
}
|
|
128
|
+
export function resolveWindowsUserPrincipal(env, userInfo = defaultWindowsUserInfo) {
|
|
129
|
+
const username = env?.USERNAME?.trim() || userInfo().username?.trim();
|
|
130
|
+
if (!username)
|
|
131
|
+
return null;
|
|
132
|
+
const domain = env?.USERDOMAIN?.trim();
|
|
133
|
+
return domain ? `${domain}\\${username}` : username;
|
|
134
|
+
}
|
|
135
|
+
function buildTrustedPrincipals(env) {
|
|
136
|
+
const trusted = new Set(TRUSTED_BASE);
|
|
137
|
+
const principal = resolveWindowsUserPrincipal(env);
|
|
138
|
+
if (principal) {
|
|
139
|
+
trusted.add(normalize(principal));
|
|
140
|
+
const userOnly = principal.split("\\").at(-1);
|
|
141
|
+
if (userOnly)
|
|
142
|
+
trusted.add(normalize(userOnly));
|
|
143
|
+
}
|
|
144
|
+
const userSid = normalizeSid(env?.USERSID ?? "");
|
|
145
|
+
if (userSid && SID_RE.test(userSid) && !WORLD_SIDS.has(userSid))
|
|
146
|
+
trusted.add(userSid);
|
|
147
|
+
return trusted;
|
|
148
|
+
}
|
|
149
|
+
function classifyPrincipal(principal, trustedPrincipals) {
|
|
150
|
+
const normalized = normalize(principal);
|
|
151
|
+
if (SID_RE.test(normalized)) {
|
|
152
|
+
const sid = normalizeSid(normalized);
|
|
153
|
+
if (WORLD_SIDS.has(sid))
|
|
154
|
+
return "world";
|
|
155
|
+
if (TRUSTED_SIDS.has(sid) || trustedPrincipals.has(sid))
|
|
156
|
+
return "trusted";
|
|
157
|
+
return "group";
|
|
158
|
+
}
|
|
159
|
+
if (trustedPrincipals.has(normalized) || TRUSTED_BASE.has(normalized))
|
|
160
|
+
return "trusted";
|
|
161
|
+
if (WORLD_PRINCIPALS.has(normalized) || WORLD_SUFFIXES.some((suffix) => normalized.endsWith(suffix)))
|
|
162
|
+
return "world";
|
|
163
|
+
const stripped = stripDiacritics(normalized);
|
|
164
|
+
return stripped !== normalized && TRUSTED_BASE_ASCII.has(stripped) ? "trusted" : "group";
|
|
165
|
+
}
|
|
166
|
+
function rightsFromTokens(tokens) {
|
|
167
|
+
const upper = tokens.join("").toUpperCase();
|
|
168
|
+
return {
|
|
169
|
+
canWrite: upper.includes("F") || upper.includes("M") || upper.includes("W") || upper.includes("D"),
|
|
170
|
+
canRead: upper.includes("F") || upper.includes("M") || upper.includes("R"),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function stripTargetPrefix(params) {
|
|
174
|
+
if (params.lowerLine.startsWith(params.lowerTarget))
|
|
175
|
+
return params.trimmedLine.slice(params.normalizedTarget.length).trim();
|
|
176
|
+
if (params.lowerLine.startsWith(params.quotedLower))
|
|
177
|
+
return params.trimmedLine.slice(params.quotedTarget.length).trim();
|
|
178
|
+
return params.trimmedLine;
|
|
179
|
+
}
|
|
180
|
+
function parseAceEntry(entry) {
|
|
181
|
+
if (!entry.includes("("))
|
|
182
|
+
return null;
|
|
183
|
+
const idx = entry.indexOf(":");
|
|
184
|
+
if (idx === -1)
|
|
185
|
+
return null;
|
|
186
|
+
const principal = entry.slice(0, idx).trim();
|
|
187
|
+
const rawRights = entry.slice(idx + 1).trim();
|
|
188
|
+
const tokens = rawRights.match(/\(([^)]+)\)/g)?.map((token) => token.slice(1, -1).trim()).filter(Boolean) ?? [];
|
|
189
|
+
if (tokens.some((token) => token.toUpperCase() === "DENY"))
|
|
190
|
+
return null;
|
|
191
|
+
const rights = tokens.filter((token) => !INHERIT_FLAGS.has(token.toUpperCase()));
|
|
192
|
+
if (rights.length === 0)
|
|
193
|
+
return null;
|
|
194
|
+
const normalizedPrincipal = normalizeSid(principal);
|
|
195
|
+
return { principal, ...(SID_RE.test(normalizedPrincipal) ? { sid: normalizedPrincipal } : {}), rights, rawRights, ...rightsFromTokens(rights) };
|
|
196
|
+
}
|
|
197
|
+
export function parseIcaclsOutput(output, targetPath) {
|
|
198
|
+
const entries = [];
|
|
199
|
+
const normalizedTarget = targetPath.trim();
|
|
200
|
+
const lowerTarget = normalizedTarget.toLowerCase();
|
|
201
|
+
const quotedTarget = `"${normalizedTarget}"`;
|
|
202
|
+
const quotedLower = quotedTarget.toLowerCase();
|
|
203
|
+
for (const rawLine of output.split(/\r?\n/)) {
|
|
204
|
+
const line = rawLine.trimEnd();
|
|
205
|
+
if (!line.trim())
|
|
206
|
+
continue;
|
|
207
|
+
const trimmed = line.trim();
|
|
208
|
+
const lowerLine = trimmed.toLowerCase();
|
|
209
|
+
if (STATUS_PREFIXES.some((prefix) => lowerLine.startsWith(prefix)))
|
|
210
|
+
continue;
|
|
211
|
+
const parsed = parseAceEntry(stripTargetPrefix({ trimmedLine: trimmed, lowerLine, normalizedTarget, lowerTarget, quotedTarget, quotedLower }));
|
|
212
|
+
if (parsed)
|
|
213
|
+
entries.push(parsed);
|
|
214
|
+
}
|
|
215
|
+
return entries;
|
|
216
|
+
}
|
|
217
|
+
export function summarizeWindowsAcl(entries, env) {
|
|
218
|
+
const trustedPrincipals = buildTrustedPrincipals(env);
|
|
219
|
+
const trusted = [];
|
|
220
|
+
const untrustedWorld = [];
|
|
221
|
+
const untrustedGroup = [];
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
const classification = classifyPrincipal(entry.sid ?? entry.principal, trustedPrincipals);
|
|
224
|
+
if (classification === "trusted")
|
|
225
|
+
trusted.push(entry);
|
|
226
|
+
else if (classification === "world")
|
|
227
|
+
untrustedWorld.push(entry);
|
|
228
|
+
else
|
|
229
|
+
untrustedGroup.push(entry);
|
|
230
|
+
}
|
|
231
|
+
return { trusted, untrustedWorld, untrustedGroup };
|
|
232
|
+
}
|
|
233
|
+
export async function inspectWindowsAcl(targetPath, opts) {
|
|
234
|
+
const exec = opts?.exec ?? defaultPermissionExec;
|
|
235
|
+
try {
|
|
236
|
+
if (opts?.principalTranslationFailed)
|
|
237
|
+
throw new Error("Windows ACL principal SID translation failed");
|
|
238
|
+
const { stdout, stderr } = await exec(resolveWindowsSystemCommand("icacls.exe", opts?.env), [targetPath]);
|
|
239
|
+
let entries = parseIcaclsOutput(`${stdout}\n${stderr}`.trim(), targetPath);
|
|
240
|
+
if (!entries.length)
|
|
241
|
+
throw new Error("Windows ACL output could not be verified");
|
|
242
|
+
const unresolvedPrincipals = entries.filter((entry) => !entry.sid).map((entry) => entry.principal);
|
|
243
|
+
const principalSids = await resolveWindowsPrincipalSids({ principals: unresolvedPrincipals, known: opts?.principalSids, env: opts?.env, exec });
|
|
244
|
+
entries = entries.map((entry) => {
|
|
245
|
+
const sid = entry.sid ?? principalSids[entry.principal.toLowerCase()];
|
|
246
|
+
if (!sid)
|
|
247
|
+
throw new Error(`Windows ACL principal SID could not be verified: ${entry.principal}`);
|
|
248
|
+
return { ...entry, sid };
|
|
249
|
+
});
|
|
250
|
+
let currentUserSid = normalizeSid(opts?.currentUserSid ?? "");
|
|
251
|
+
let effectiveEnv = currentUserSid ? { USERSID: currentUserSid } : undefined;
|
|
252
|
+
let { trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv);
|
|
253
|
+
if (!currentUserSid && untrustedGroup.some((entry) => entry.sid && !TRUSTED_SIDS.has(entry.sid))) {
|
|
254
|
+
currentUserSid = (await resolveWindowsCurrentUserSid({ exec, env: opts?.env })) ?? "";
|
|
255
|
+
if (currentUserSid) {
|
|
256
|
+
effectiveEnv = { USERSID: currentUserSid };
|
|
257
|
+
({ trusted, untrustedWorld, untrustedGroup } = summarizeWindowsAcl(entries, effectiveEnv));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return { ok: true, entries, trusted, untrustedWorld, untrustedGroup };
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
return { ok: false, entries: [], trusted: [], untrustedWorld: [], untrustedGroup: [], error: String(err) };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
export function formatWindowsAclSummary(summary) {
|
|
267
|
+
if (!summary.ok)
|
|
268
|
+
return "unknown";
|
|
269
|
+
const untrusted = [...summary.untrustedWorld, ...summary.untrustedGroup];
|
|
270
|
+
return untrusted.length === 0 ? "trusted-only" : untrusted.map((entry) => `${entry.principal}:${entry.rawRights}`).join(", ");
|
|
271
|
+
}
|
|
272
|
+
export function formatIcaclsResetCommand(targetPath, opts) {
|
|
273
|
+
const command = resolveWindowsSystemCommand("icacls.exe", opts.env);
|
|
274
|
+
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo) ?? "%USERNAME%";
|
|
275
|
+
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
276
|
+
return [command, `"${targetPath}"`, "/inheritance:r", "/grant:r", `"${user}:${grant}"`, "/grant:r", `"*S-1-5-18:${grant}"`].join(" ");
|
|
277
|
+
}
|
|
278
|
+
export function createIcaclsResetCommand(targetPath, opts) {
|
|
279
|
+
const user = resolveWindowsUserPrincipal(opts.env, opts.userInfo);
|
|
280
|
+
if (!user)
|
|
281
|
+
return null;
|
|
282
|
+
const grant = opts.isDir ? "(OI)(CI)F" : "F";
|
|
283
|
+
const args = [targetPath, "/inheritance:r", "/grant:r", `${user}:${grant}`, "/grant:r", `*S-1-5-18:${grant}`];
|
|
284
|
+
return { command: resolveWindowsSystemCommand("icacls.exe", opts.env), args, display: formatIcaclsResetCommand(targetPath, opts) };
|
|
285
|
+
}
|
package/dist/permissions.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}>;
|
|
1
|
+
import { type PermissionExec } from "./permissions-windows.js";
|
|
2
|
+
export { createIcaclsResetCommand, formatIcaclsResetCommand, formatWindowsAclSummary, inspectWindowsAcl, parseIcaclsOutput, resolveWindowsUserPrincipal, summarizeWindowsAcl, } from "./permissions-windows.js";
|
|
3
|
+
export type { IcaclsResetCommandOptions, PermissionExec, WindowsAclEntry, WindowsAclSummary, WindowsUserInfoProvider, } from "./permissions-windows.js";
|
|
5
4
|
export type PermissionCheck = {
|
|
6
5
|
ok: boolean;
|
|
7
6
|
isSymlink: boolean;
|
|
@@ -36,31 +35,6 @@ export type SafeStatResult = {
|
|
|
36
35
|
gid: number | null;
|
|
37
36
|
error?: string;
|
|
38
37
|
};
|
|
39
|
-
export type WindowsAclEntry = {
|
|
40
|
-
principal: string;
|
|
41
|
-
/** Canonical principal SID when resolved from Windows. */
|
|
42
|
-
sid?: string;
|
|
43
|
-
rights: string[];
|
|
44
|
-
rawRights: string;
|
|
45
|
-
canRead: boolean;
|
|
46
|
-
canWrite: boolean;
|
|
47
|
-
};
|
|
48
|
-
export type WindowsAclSummary = {
|
|
49
|
-
ok: boolean;
|
|
50
|
-
entries: WindowsAclEntry[];
|
|
51
|
-
untrustedWorld: WindowsAclEntry[];
|
|
52
|
-
untrustedGroup: WindowsAclEntry[];
|
|
53
|
-
trusted: WindowsAclEntry[];
|
|
54
|
-
error?: string;
|
|
55
|
-
};
|
|
56
|
-
export type WindowsUserInfoProvider = () => {
|
|
57
|
-
username?: string | null;
|
|
58
|
-
};
|
|
59
|
-
export type IcaclsResetCommandOptions = {
|
|
60
|
-
isDir: boolean;
|
|
61
|
-
env?: NodeJS.ProcessEnv;
|
|
62
|
-
userInfo?: WindowsUserInfoProvider;
|
|
63
|
-
};
|
|
64
38
|
export declare function safeStat(targetPath: string): Promise<SafeStatResult>;
|
|
65
39
|
export declare function inspectPathPermissions(targetPath: string, opts?: PermissionCheckOptions): Promise<PermissionCheck>;
|
|
66
40
|
export declare function formatPermissionDetail(targetPath: string, perms: PermissionCheck): string;
|
|
@@ -77,21 +51,4 @@ export declare function isWorldWritable(bits: number | null): boolean;
|
|
|
77
51
|
export declare function isGroupWritable(bits: number | null): boolean;
|
|
78
52
|
export declare function isWorldReadable(bits: number | null): boolean;
|
|
79
53
|
export declare function isGroupReadable(bits: number | null): boolean;
|
|
80
|
-
export declare function resolveWindowsUserPrincipal(env?: NodeJS.ProcessEnv, userInfo?: WindowsUserInfoProvider): string | null;
|
|
81
|
-
export declare function parseIcaclsOutput(output: string, targetPath: string): WindowsAclEntry[];
|
|
82
|
-
export declare function summarizeWindowsAcl(entries: WindowsAclEntry[], env?: NodeJS.ProcessEnv): Pick<WindowsAclSummary, "trusted" | "untrustedWorld" | "untrustedGroup">;
|
|
83
|
-
export declare function inspectWindowsAcl(targetPath: string, opts?: {
|
|
84
|
-
env?: NodeJS.ProcessEnv;
|
|
85
|
-
exec?: PermissionExec;
|
|
86
|
-
currentUserSid?: string;
|
|
87
|
-
principalSids?: Record<string, string>;
|
|
88
|
-
principalTranslationFailed?: boolean;
|
|
89
|
-
}): Promise<WindowsAclSummary>;
|
|
90
|
-
export declare function formatWindowsAclSummary(summary: WindowsAclSummary): string;
|
|
91
|
-
export declare function formatIcaclsResetCommand(targetPath: string, opts: IcaclsResetCommandOptions): string;
|
|
92
|
-
export declare function createIcaclsResetCommand(targetPath: string, opts: IcaclsResetCommandOptions): {
|
|
93
|
-
command: string;
|
|
94
|
-
args: string[];
|
|
95
|
-
display: string;
|
|
96
|
-
} | null;
|
|
97
54
|
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
|