@openclaw/fs-safe 0.15.0 → 0.16.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 +38 -0
- package/README.md +28 -6
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-kind.d.ts +0 -1
- package/dist/archive-kind.d.ts.map +1 -1
- package/dist/archive-kind.js +5 -17
- package/dist/archive-parser.wasm +0 -0
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +6 -7
- package/dist/archive-tar-stream.d.ts +3 -0
- package/dist/archive-tar-stream.d.ts.map +1 -1
- package/dist/archive-tar-stream.js +56 -37
- package/dist/archive-tar-wasm.d.ts +16 -4
- package/dist/archive-tar-wasm.d.ts.map +1 -1
- package/dist/archive-tar-wasm.js +134 -34
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +5 -6
- package/dist/clone-metadata.d.ts +1 -0
- package/dist/clone-metadata.d.ts.map +1 -1
- package/dist/clone-metadata.js +6 -2
- package/dist/create-directory.d.ts +20 -0
- package/dist/create-directory.d.ts.map +1 -0
- package/dist/create-directory.js +130 -0
- package/dist/create-file-async.d.ts +7 -0
- package/dist/create-file-async.d.ts.map +1 -0
- package/dist/create-file-async.js +121 -0
- package/dist/create-file.d.ts +8 -0
- package/dist/create-file.d.ts.map +1 -0
- package/dist/create-file.js +190 -0
- package/dist/create-owned-file.d.ts +8 -0
- package/dist/create-owned-file.d.ts.map +1 -0
- package/dist/create-owned-file.js +16 -0
- package/dist/create.d.ts +4 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +2 -0
- package/dist/creation-darwin.d.ts +7 -0
- package/dist/creation-darwin.d.ts.map +1 -0
- package/dist/creation-darwin.js +79 -0
- package/dist/creation-file-state.d.ts +19 -0
- package/dist/creation-file-state.d.ts.map +1 -0
- package/dist/creation-file-state.js +118 -0
- package/dist/creation-path.d.ts +21 -0
- package/dist/creation-path.d.ts.map +1 -0
- package/dist/creation-path.js +71 -0
- package/dist/creation-permissions.d.ts +19 -0
- package/dist/creation-permissions.d.ts.map +1 -0
- package/dist/creation-permissions.js +125 -0
- package/dist/directory-durability.d.ts +1 -1
- package/dist/directory-durability.d.ts.map +1 -1
- package/dist/directory-durability.js +22 -80
- package/dist/directory-guard.d.ts +3 -0
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-mode-node.d.ts +2 -0
- package/dist/directory-mode-node.d.ts.map +1 -1
- package/dist/directory-mode-node.js +8 -0
- package/dist/directory-receipt.d.ts +24 -0
- package/dist/directory-receipt.d.ts.map +1 -0
- package/dist/directory-receipt.js +127 -0
- package/dist/file-cleanup.d.ts +19 -0
- package/dist/file-cleanup.d.ts.map +1 -0
- package/dist/file-cleanup.js +78 -0
- package/dist/file-observation.d.ts +1 -1
- package/dist/file-observation.d.ts.map +1 -1
- package/dist/file-store-boundary.d.ts +2 -6
- package/dist/file-store-boundary.d.ts.map +1 -1
- package/dist/file-store-boundary.js +3 -9
- package/dist/file-store-sync-write.d.ts.map +1 -1
- package/dist/file-store-sync-write.js +2 -5
- package/dist/guarded-mkdir.d.ts +1 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +36 -7
- package/dist/move-path.js +1 -1
- package/dist/native-binding.d.ts +11 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-fallback-warning.d.ts +4 -0
- package/dist/native-fallback-warning.d.ts.map +1 -0
- package/dist/native-fallback-warning.js +11 -0
- package/dist/native-operations.d.ts +0 -2
- package/dist/native-operations.d.ts.map +1 -1
- package/dist/native-operations.js +0 -24
- package/dist/native-parent-admission.d.ts +2 -0
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +3 -2
- package/dist/native-pinned-write-windows.d.ts +1 -1
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +173 -28
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +19 -3
- package/dist/native-policy-parent-windows.d.ts.map +1 -1
- package/dist/native-policy-parent-windows.js +15 -6
- package/dist/native-staged-file.d.ts +3 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +86 -39
- package/dist/owner-dacl.d.ts.map +1 -1
- package/dist/owner-dacl.js +10 -4
- package/dist/pinned-write-input.d.ts +4 -0
- package/dist/pinned-write-input.d.ts.map +1 -0
- package/dist/pinned-write-input.js +25 -0
- package/dist/pinned-write-mode.d.ts +5 -0
- package/dist/pinned-write-mode.d.ts.map +1 -0
- package/dist/pinned-write-mode.js +24 -0
- package/dist/pinned-write-staged.d.ts +6 -0
- package/dist/pinned-write-staged.d.ts.map +1 -0
- package/dist/pinned-write-staged.js +187 -0
- package/dist/pinned-write-types.d.ts +3 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +35 -145
- package/dist/private-directory.d.ts.map +1 -1
- package/dist/private-directory.js +18 -4
- package/dist/private-producer-handoff-sync.d.ts +14 -0
- package/dist/private-producer-handoff-sync.d.ts.map +1 -0
- package/dist/private-producer-handoff-sync.js +114 -0
- package/dist/private-producer-handoff.d.ts +22 -4
- package/dist/private-producer-handoff.d.ts.map +1 -1
- package/dist/private-producer-handoff.js +140 -77
- package/dist/publish-copy-stage.d.ts +2 -1
- package/dist/publish-copy-stage.d.ts.map +1 -1
- package/dist/publish-copy-stage.js +16 -7
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +2 -2
- package/dist/replace-file-temp-owner.d.ts +0 -7
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +3 -54
- package/dist/root-create-input.d.ts +2 -1
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +13 -4
- package/dist/root-directory-creation.d.ts +3 -3
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +15 -3
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +28 -7
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +22 -13
- package/dist/root-options.d.ts +12 -4
- package/dist/root-options.d.ts.map +1 -1
- package/dist/root-path-stat.d.ts.map +1 -1
- package/dist/root-path-stat.js +59 -7
- package/dist/root-write-publication.js +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +1 -0
- package/dist/secure-file-windows.d.ts +6 -0
- package/dist/secure-file-windows.d.ts.map +1 -1
- package/dist/secure-file-windows.js +34 -117
- package/dist/secure-file.js +2 -2
- package/dist/sidecar-lock-root.d.ts.map +1 -1
- package/dist/sidecar-lock-root.js +2 -1
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +6 -6
- package/dist/staged-file-settlement.d.ts +17 -0
- package/dist/staged-file-settlement.d.ts.map +1 -0
- package/dist/staged-file-settlement.js +57 -0
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -1
- package/dist/windows-security-bridge.cs +336 -0
- package/dist/windows-security-bridge.ps1 +15 -0
- package/dist/windows-security-command.d.ts +26 -0
- package/dist/windows-security-command.d.ts.map +1 -0
- package/dist/windows-security-command.js +363 -0
- package/dist/windows-security-facts.d.ts +6 -0
- package/dist/windows-security-facts.d.ts.map +1 -0
- package/dist/windows-security-facts.js +108 -0
- package/docs/advanced.md +3 -1
- package/docs/archive.md +61 -37
- package/docs/config.md +6 -2
- package/docs/contributing.md +44 -4
- package/docs/copy.md +2 -0
- package/docs/creation.md +128 -0
- package/docs/durability.md +24 -0
- package/docs/install.md +31 -7
- package/docs/migrating-to-0.5.md +15 -6
- package/docs/migrating-to-0.6.md +9 -4
- package/docs/native-helper.md +22 -9
- package/docs/native.md +38 -7
- package/docs/permissions.md +37 -14
- package/docs/root.md +34 -0
- package/docs/secret-file.md +11 -2
- package/docs/secure-file.md +9 -4
- package/docs/sidecar-lock.md +5 -4
- package/docs/staged-file.md +5 -0
- package/docs/writing.md +71 -5
- package/package.json +18 -15
|
@@ -1,30 +1,12 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
2
|
import { recordFileObservationFailure } from "./file-observation.js";
|
|
3
3
|
import { getNativeBinding } from "./native.js";
|
|
4
|
+
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
5
|
+
import { warnNativeFallback } from "./native-fallback-warning.js";
|
|
6
|
+
import { inspectWindowsDescriptorCommand } from "./windows-security-command.js";
|
|
7
|
+
import { validateSecureWindowsSecurityFacts } from "./windows-security-facts.js";
|
|
4
8
|
const IDENTITY_RE = /^([0-9a-f]{8}):([0-9a-f]{16})$/;
|
|
5
|
-
const SID_RE = /^s-\d+-\d+(?:-\d+)+$/;
|
|
6
|
-
const OWNER_CLASSES = new Set(["current-user", "system", "administrators", "foreign"]);
|
|
7
9
|
const TRUSTED_OWNER_CLASSES = new Set(["current-user", "system", "administrators"]);
|
|
8
|
-
const SYSTEM_SID = "s-1-5-18";
|
|
9
|
-
const ADMINISTRATORS_SID = "s-1-5-32-544";
|
|
10
|
-
const WORLD_SIDS = new Set([
|
|
11
|
-
"s-1-1-0", "s-1-5-11", "s-1-5-32-545", "s-1-5-7",
|
|
12
|
-
"s-1-5-32-546", "s-1-5-4", "s-1-5-2",
|
|
13
|
-
]);
|
|
14
|
-
const GENERIC_READ = 0x8000_0000;
|
|
15
|
-
const GENERIC_WRITE = 0x4000_0000;
|
|
16
|
-
const GENERIC_ALL = 0x1000_0000;
|
|
17
|
-
const DELETE_ACCESS = 0x0001_0000;
|
|
18
|
-
const WRITE_DAC = 0x0004_0000;
|
|
19
|
-
const WRITE_OWNER = 0x0008_0000;
|
|
20
|
-
const FILE_READ_DATA = 0x0000_0001;
|
|
21
|
-
const FILE_WRITE_DATA = 0x0000_0002;
|
|
22
|
-
const FILE_APPEND_DATA = 0x0000_0004;
|
|
23
|
-
const FILE_READ_EA = 0x0000_0008;
|
|
24
|
-
const FILE_WRITE_EA = 0x0000_0010;
|
|
25
|
-
const FILE_DELETE_CHILD = 0x0000_0040;
|
|
26
|
-
const FILE_READ_ATTRIBUTES = 0x0000_0080;
|
|
27
|
-
const FILE_WRITE_ATTRIBUTES = 0x0000_0100;
|
|
28
10
|
function permissionUnverified(message, cause) {
|
|
29
11
|
throw new FsSafeError("permission-unverified", message, cause === undefined ? {} : { cause });
|
|
30
12
|
}
|
|
@@ -36,9 +18,6 @@ function identityMismatch() {
|
|
|
36
18
|
function isRecord(value) {
|
|
37
19
|
return typeof value === "object" && value !== null;
|
|
38
20
|
}
|
|
39
|
-
function isUint32(value) {
|
|
40
|
-
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 0xffff_ffff;
|
|
41
|
-
}
|
|
42
21
|
function parseIdentity(value) {
|
|
43
22
|
if (typeof value !== "string")
|
|
44
23
|
identityMismatch();
|
|
@@ -50,95 +29,6 @@ function parseIdentity(value) {
|
|
|
50
29
|
ino: BigInt(`0x${match[2]}`),
|
|
51
30
|
};
|
|
52
31
|
}
|
|
53
|
-
function validateFlags(value) {
|
|
54
|
-
if (!isRecord(value))
|
|
55
|
-
return false;
|
|
56
|
-
const raw = value.raw;
|
|
57
|
-
if (!isUint32(raw) || (raw & ~0xdf) !== 0)
|
|
58
|
-
return false;
|
|
59
|
-
const fields = [
|
|
60
|
-
["objectInherit", 0x01], ["containerInherit", 0x02],
|
|
61
|
-
["noPropagateInherit", 0x04], ["inheritOnly", 0x08], ["inherited", 0x10],
|
|
62
|
-
["successfulAccess", 0x40], ["failedAccess", 0x80],
|
|
63
|
-
];
|
|
64
|
-
return fields.every(([field, bit]) => typeof value[field] === "boolean" && value[field] === ((raw & bit) !== 0));
|
|
65
|
-
}
|
|
66
|
-
function validateAce(value) {
|
|
67
|
-
return isRecord(value) && typeof value.sid === "string" && SID_RE.test(value.sid) &&
|
|
68
|
-
isUint32(value.mask) && (value.aceType === "allow" || value.aceType === "deny") &&
|
|
69
|
-
validateFlags(value.flags);
|
|
70
|
-
}
|
|
71
|
-
function canRead(mask) {
|
|
72
|
-
return (mask & (GENERIC_ALL | GENERIC_READ | FILE_READ_DATA | FILE_READ_EA |
|
|
73
|
-
FILE_READ_ATTRIBUTES)) !== 0;
|
|
74
|
-
}
|
|
75
|
-
function canWrite(mask) {
|
|
76
|
-
return (mask & (GENERIC_ALL | GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA |
|
|
77
|
-
FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | FILE_DELETE_CHILD | DELETE_ACCESS |
|
|
78
|
-
WRITE_DAC | WRITE_OWNER)) !== 0;
|
|
79
|
-
}
|
|
80
|
-
function expectedOwnerClass(facts) {
|
|
81
|
-
if (facts.ownerSid === facts.currentUserSid)
|
|
82
|
-
return "current-user";
|
|
83
|
-
if (facts.ownerSid === SYSTEM_SID)
|
|
84
|
-
return "system";
|
|
85
|
-
if (facts.ownerSid === ADMINISTRATORS_SID)
|
|
86
|
-
return "administrators";
|
|
87
|
-
return "foreign";
|
|
88
|
-
}
|
|
89
|
-
function validateSecurity(value) {
|
|
90
|
-
if (!isRecord(value))
|
|
91
|
-
permissionUnverified("Windows descriptor ACL facts were malformed");
|
|
92
|
-
const facts = value;
|
|
93
|
-
const booleans = [
|
|
94
|
-
"worldWritable", "groupWritable", "worldReadable", "groupReadable",
|
|
95
|
-
"fallbackRequired", "daclPresent", "isLocal", "aceListComplete",
|
|
96
|
-
];
|
|
97
|
-
if (!booleans.every((field) => typeof facts[field] === "boolean") ||
|
|
98
|
-
typeof facts.ownerSid !== "string" || !SID_RE.test(facts.ownerSid) ||
|
|
99
|
-
typeof facts.currentUserSid !== "string" || !SID_RE.test(facts.currentUserSid) ||
|
|
100
|
-
typeof facts.ownerClass !== "string" || !OWNER_CLASSES.has(facts.ownerClass) ||
|
|
101
|
-
!Array.isArray(facts.unsupportedAceTypes) ||
|
|
102
|
-
!facts.unsupportedAceTypes.every(isUint32) ||
|
|
103
|
-
!Array.isArray(facts.aces) || !facts.aces.every(validateAce)) {
|
|
104
|
-
permissionUnverified("Windows descriptor ACL facts were malformed");
|
|
105
|
-
}
|
|
106
|
-
if (facts.ownerClass !== expectedOwnerClass(facts)) {
|
|
107
|
-
permissionUnverified("Windows descriptor owner facts were inconsistent");
|
|
108
|
-
}
|
|
109
|
-
if (facts.fallbackRequired || !facts.isLocal || !facts.aceListComplete ||
|
|
110
|
-
facts.unsupportedAceTypes.length !== 0) {
|
|
111
|
-
permissionUnverified("Windows descriptor ACL facts were incomplete or unsupported");
|
|
112
|
-
}
|
|
113
|
-
if (!facts.daclPresent && facts.aces.length !== 0) {
|
|
114
|
-
permissionUnverified("Windows descriptor DACL facts were inconsistent");
|
|
115
|
-
}
|
|
116
|
-
return facts;
|
|
117
|
-
}
|
|
118
|
-
function validatePermissionSummary(facts) {
|
|
119
|
-
let worldReadable = !facts.daclPresent;
|
|
120
|
-
let worldWritable = !facts.daclPresent;
|
|
121
|
-
let groupReadable = false;
|
|
122
|
-
let groupWritable = false;
|
|
123
|
-
const trustedSids = new Set([facts.currentUserSid, SYSTEM_SID, ADMINISTRATORS_SID]);
|
|
124
|
-
for (const ace of facts.aces) {
|
|
125
|
-
if (ace.aceType !== "allow" || ace.flags.inheritOnly || trustedSids.has(ace.sid))
|
|
126
|
-
continue;
|
|
127
|
-
const world = WORLD_SIDS.has(ace.sid);
|
|
128
|
-
if (world) {
|
|
129
|
-
worldReadable ||= canRead(ace.mask);
|
|
130
|
-
worldWritable ||= canWrite(ace.mask);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
groupReadable ||= canRead(ace.mask);
|
|
134
|
-
groupWritable ||= canWrite(ace.mask);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (facts.worldReadable !== worldReadable || facts.worldWritable !== worldWritable ||
|
|
138
|
-
facts.groupReadable !== groupReadable || facts.groupWritable !== groupWritable) {
|
|
139
|
-
permissionUnverified("Windows descriptor ACL summary was inconsistent");
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
32
|
export function inspectSecureWindowsDescriptor(params) {
|
|
143
33
|
let native;
|
|
144
34
|
try {
|
|
@@ -158,14 +48,16 @@ export function inspectSecureWindowsDescriptor(params) {
|
|
|
158
48
|
catch (cause) {
|
|
159
49
|
permissionUnverified("Windows descriptor ACL verification failed", cause);
|
|
160
50
|
}
|
|
51
|
+
return inspectDescriptorResult(params, result, "native");
|
|
52
|
+
}
|
|
53
|
+
function inspectDescriptorResult(params, result, mechanism) {
|
|
161
54
|
if (!isRecord(result))
|
|
162
55
|
permissionUnverified("Windows descriptor ACL facts were malformed");
|
|
163
56
|
const observed = parseIdentity(result.identity);
|
|
164
57
|
if (observed.dev !== params.identity.dev || observed.ino !== params.identity.ino) {
|
|
165
58
|
identityMismatch();
|
|
166
59
|
}
|
|
167
|
-
const facts =
|
|
168
|
-
validatePermissionSummary(facts);
|
|
60
|
+
const facts = validateSecureWindowsSecurityFacts(result.security);
|
|
169
61
|
return {
|
|
170
62
|
ok: true,
|
|
171
63
|
isSymlink: false,
|
|
@@ -179,8 +71,33 @@ export function inspectSecureWindowsDescriptor(params) {
|
|
|
179
71
|
groupReadable: facts.groupReadable,
|
|
180
72
|
ownerSid: facts.ownerSid,
|
|
181
73
|
ownerTrusted: TRUSTED_OWNER_CLASSES.has(facts.ownerClass),
|
|
182
|
-
aclSummary:
|
|
74
|
+
aclSummary: `${mechanism} descriptor owner=${facts.ownerClass} world=` +
|
|
183
75
|
`${facts.worldReadable ? "r" : "-"}${facts.worldWritable ? "w" : "-"} ` +
|
|
184
76
|
`group=${facts.groupReadable ? "r" : "-"}${facts.groupWritable ? "w" : "-"}`,
|
|
185
77
|
};
|
|
186
78
|
}
|
|
79
|
+
/** Both mechanisms inspect the same borrowed handle; the caller owns its lifetime. */
|
|
80
|
+
export async function inspectSecureWindowsFile(params) {
|
|
81
|
+
let native;
|
|
82
|
+
try {
|
|
83
|
+
native = getNativeBinding();
|
|
84
|
+
}
|
|
85
|
+
catch (cause) {
|
|
86
|
+
permissionUnverified("Windows descriptor ACL verification requires the matching native helper", cause);
|
|
87
|
+
}
|
|
88
|
+
if (typeof native?.inspectWindowsSecureFileHandle === "function") {
|
|
89
|
+
return inspectSecureWindowsDescriptor(params);
|
|
90
|
+
}
|
|
91
|
+
if (getFsSafeNativeConfig().mode === "require") {
|
|
92
|
+
permissionUnverified("Windows descriptor ACL verification requires an up-to-date native helper");
|
|
93
|
+
}
|
|
94
|
+
warnNativeFallback("windows-secure-file", "Windows descriptor ACL inspection uses a slower built-in system command.");
|
|
95
|
+
let result;
|
|
96
|
+
try {
|
|
97
|
+
result = await inspectWindowsDescriptorCommand(params.fd);
|
|
98
|
+
}
|
|
99
|
+
catch (cause) {
|
|
100
|
+
permissionUnverified("Windows descriptor ACL verification failed", cause);
|
|
101
|
+
}
|
|
102
|
+
return inspectDescriptorResult(params, result, "system-command");
|
|
103
|
+
}
|
package/dist/secure-file.js
CHANGED
|
@@ -13,7 +13,7 @@ import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
|
13
13
|
import { realpathSync } from "./realpath.js";
|
|
14
14
|
import { inspectPathPermissions, isGroupReadable, isGroupWritable, isWorldReadable, isWorldWritable, modeBits, } from "./permissions.js";
|
|
15
15
|
import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
16
|
-
import {
|
|
16
|
+
import { inspectSecureWindowsFile } from "./secure-file-windows.js";
|
|
17
17
|
import { scheduleTimeout } from "./timing.js";
|
|
18
18
|
import { anchorWindowsDriveRelativePath, assertNoWindowsPathAlias, resolvePathPreservingWindowsRoot, } from "./windows-path-alias.js";
|
|
19
19
|
function snapshotTrustedDirs(dirs) {
|
|
@@ -209,7 +209,7 @@ async function assertSecurePermissions(options, stat, realPath, identity, fd) {
|
|
|
209
209
|
const platform = options.inject?.platform ?? process.platform;
|
|
210
210
|
const permissions = platform === "win32"
|
|
211
211
|
? process.platform === "win32"
|
|
212
|
-
?
|
|
212
|
+
? await inspectSecureWindowsFile({ fd, identity, stat })
|
|
213
213
|
: await inspectPathPermissions(realPath, options.inject)
|
|
214
214
|
: inspectOpenedPermissions(stat, platform);
|
|
215
215
|
const reason = permissions.error ? `: ${formatPermissionErrorDetail(permissions.error)}` : "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"sidecar-lock-root.d.ts","sourceRoot":"","sources":["../src/sidecar-lock-root.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAGvD,wBAAsB,eAAe,CACnC,QAAQ,EAAE,IAAI,EACd,QAAQ,EAAE,MAAM,EAChB,kBAAkB,CAAC,EAAE,UAAU,GAAG,SAAS,EAC3C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACvC,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CA0D5B"}
|
|
@@ -61,7 +61,8 @@ export async function openSidecarRoot(lockRoot, relative, discardObservation, on
|
|
|
61
61
|
}
|
|
62
62
|
catch (probeError) {
|
|
63
63
|
if (!(probeError instanceof FsSafeError && probeError.code === "not-found") &&
|
|
64
|
-
!probe.has(probeError, `stat-leaf-missing:${expectedRealPath}`)
|
|
64
|
+
!probe.has(probeError, `stat-leaf-missing:${expectedRealPath}`) &&
|
|
65
|
+
!probe.has(probeError, `stat-leaf-changed:${expectedRealPath}`))
|
|
65
66
|
throw probeError;
|
|
66
67
|
}
|
|
67
68
|
for (const { dir, stat } of parents)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"staged-directory.d.ts","sourceRoot":"","sources":["../src/staged-directory.ts"],"names":[],"mappings":"AAAA,OAAW,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAGlE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,EAEL,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAE/B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,MAAM,uBAAuB,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAErE,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,EAAE,uBAAuB,CAAC;IACnC,WAAW,EAAE,4BAA4B,CAAC;IAC1C,IAAI,EAAE,WAAW,CAAC;IAClB,cAAc,CAAC,EAAE,yBAAyB,CAAC;IAC3C,kBAAkB,CAAC,IAAI,IAAI,CAAC;CAC7B,CAAC,CAAC;AAEH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,QAAQ,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,GAC7C,OAAO,CAKT;AAED,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,uBAAuB,CAY7F;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,uBAAuB,GAAG,WAAW,CAS1F;AASD,wBAAgB,6BAA6B,CAC3C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,aAAa,GACtB,qBAAqB,CA2CvB;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,qBAAqB,GAC9B,WAAW,CAeb;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,qBAAqB,GAC9B,4BAA4B,CAoB9B;AAED,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,GAAG;IACzE,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,uBAAuB,CAAC;CAClC,CAgCA"}
|
package/dist/staged-directory.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs, {} from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { directoryReceiptAuthority, ownDirectoryReceipt } from "./directory-receipt.js";
|
|
3
4
|
import { FsSafeError } from "./errors.js";
|
|
4
5
|
import { inspectDirectoryIdentitySync } from "./directory-guard.js";
|
|
5
6
|
import { nativePolicyDirectoryObserver, NativePolicyDirectoryMismatch } from "./native-policy-directory-observation.js";
|
|
@@ -116,16 +117,15 @@ export function refreshPolicyStagedDirectoryObservation(captured) {
|
|
|
116
117
|
return checkedMutationDirectory(captured.directory.path, canonicalPath, current);
|
|
117
118
|
}
|
|
118
119
|
export function openStagedDirectory(directory) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
};
|
|
120
|
+
const expected = typeof directory === "string"
|
|
121
|
+
? undefined
|
|
122
|
+
: directoryReceiptAuthority(ownDirectoryReceipt(directory));
|
|
123
|
+
const pathname = resolvePathPreservingWindowsRoot(typeof directory === "string" ? directory : expected.path);
|
|
124
124
|
const before = fs.lstatSync(pathname, { bigint: true });
|
|
125
125
|
if (!before.isDirectory()) {
|
|
126
126
|
throw new FsSafeError("not-file", "staging parent must be a real directory");
|
|
127
127
|
}
|
|
128
|
-
if (expected && (!exactIdentityMatches(expected, before) || realpathSync(pathname) !== expected.realPath)) {
|
|
128
|
+
if (expected && (!exactIdentityMatches(expected.identity, before) || realpathSync(pathname) !== expected.realPath)) {
|
|
129
129
|
throw new FsSafeError("path-mismatch", "stale staging directory receipt");
|
|
130
130
|
}
|
|
131
131
|
const fd = fs.openSync(pathname, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BigIntStats } from "node:fs";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import type { StagedFileCleanupReceipt, StagedFileFailureDetails, StagedFilePublication, StagedFileReceipt } from "./staged-file-types.js";
|
|
4
|
+
export declare function createStagedFileReceipt(directory: StagedFileReceipt["directory"], temporaryBasename: string, stat: BigIntStats): StagedFileReceipt;
|
|
5
|
+
export declare function stagedFileFailure(error: unknown, details: StagedFileFailureDetails): FsSafeError;
|
|
6
|
+
/** Finish owned cleanup and every close before surfacing publication evidence. */
|
|
7
|
+
export declare function settleStagedFile(params: {
|
|
8
|
+
temporaryBasename: string;
|
|
9
|
+
publication: StagedFilePublication;
|
|
10
|
+
phase: StagedFileFailureDetails["phase"];
|
|
11
|
+
failure?: {
|
|
12
|
+
error: unknown;
|
|
13
|
+
};
|
|
14
|
+
cleanup: () => Promise<StagedFileCleanupReceipt["status"]>;
|
|
15
|
+
close: readonly (() => Promise<void> | void)[];
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=staged-file-settlement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staged-file-settlement.d.ts","sourceRoot":"","sources":["../src/staged-file-settlement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,iBAAiB,CAAC,WAAW,CAAC,EACzC,iBAAiB,EAAE,MAAM,EACzB,IAAI,EAAE,WAAW,GAChB,iBAAiB,CAUnB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,GAAG,WAAW,CAKhG;AAED,kFAAkF;AAClF,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,qBAAqB,CAAC;IACnC,KAAK,EAAE,wBAAwB,CAAC,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC7B,OAAO,EAAE,MAAM,OAAO,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3D,KAAK,EAAE,SAAS,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,EAAE,CAAC;CAChD,GAAG,OAAO,CAAC,IAAI,CAAC,CAkChB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export function createStagedFileReceipt(directory, temporaryBasename, stat) {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
directory,
|
|
5
|
+
temporaryBasename,
|
|
6
|
+
identity: Object.freeze({
|
|
7
|
+
dev: stat.dev, ino: stat.ino, mode: Number(stat.mode & 4095n),
|
|
8
|
+
nlink: stat.nlink, size: stat.size, uid: Number(stat.uid), gid: Number(stat.gid),
|
|
9
|
+
mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs,
|
|
10
|
+
}),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function stagedFileFailure(error, details) {
|
|
14
|
+
const code = error instanceof FsSafeError
|
|
15
|
+
? error.code
|
|
16
|
+
: error?.code === "EEXIST" ? "already-exists" : "helper-failed";
|
|
17
|
+
return new FsSafeError(code, `staged file ${details.phase} failed`, { cause: error, details });
|
|
18
|
+
}
|
|
19
|
+
/** Finish owned cleanup and every close before surfacing publication evidence. */
|
|
20
|
+
export async function settleStagedFile(params) {
|
|
21
|
+
const errors = params.failure ? [params.failure.error] : [];
|
|
22
|
+
let status;
|
|
23
|
+
try {
|
|
24
|
+
status = await params.cleanup();
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
status = "failed";
|
|
28
|
+
errors.push(error);
|
|
29
|
+
}
|
|
30
|
+
let resources = "closed";
|
|
31
|
+
for (const close of params.close) {
|
|
32
|
+
try {
|
|
33
|
+
await close();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
resources = "close-failed";
|
|
37
|
+
errors.push(error);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const incomplete = status === "failed" || status === "preserved" || resources === "close-failed";
|
|
41
|
+
// A settled pre-publication failure retains the producer/authority rejection exactly.
|
|
42
|
+
if (!incomplete && (!params.failure || params.publication.status === "not-published"))
|
|
43
|
+
return;
|
|
44
|
+
const error = errors.length > 1 ? new AggregateError(errors, "staged operation and settlement failed")
|
|
45
|
+
: errors.length === 1 ? errors[0] : new FsSafeError("not-removable", "staged cleanup preserved an unverified entry");
|
|
46
|
+
const cleanup = Object.freeze({
|
|
47
|
+
temporaryBasename: params.temporaryBasename,
|
|
48
|
+
publication: params.publication,
|
|
49
|
+
status,
|
|
50
|
+
resources,
|
|
51
|
+
});
|
|
52
|
+
throw stagedFileFailure(error, {
|
|
53
|
+
phase: params.failure ? params.phase : "cleanup",
|
|
54
|
+
publication: params.publication,
|
|
55
|
+
cleanup,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"windows-owner.d.ts","sourceRoot":"","sources":["../src/windows-owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAI9B,MAAM,MAAM,gBAAgB,GAAG,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAsDF,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,EAAE,gBAAgB,CAAC;CACxB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAmD/B"}
|
package/dist/windows-owner.js
CHANGED
|
@@ -14,6 +14,7 @@ function windowsOwnerQueryCommand(targetPath) {
|
|
|
14
14
|
const encodedPath = Buffer.from(targetPath, "utf8").toString("base64");
|
|
15
15
|
return [
|
|
16
16
|
"$ErrorActionPreference='Stop'",
|
|
17
|
+
"$env:PSModulePath=[IO.Path]::Combine($PSHOME,'Modules')",
|
|
17
18
|
`$p=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPath}'))`,
|
|
18
19
|
"$sections=[System.Security.AccessControl.AccessControlSections]::Access -bor [System.Security.AccessControl.AccessControlSections]::Owner",
|
|
19
20
|
"$acl=if([IO.Directory]::Exists($p)){[IO.Directory]::GetAccessControl($p,$sections)}else{[IO.File]::GetAccessControl($p,$sections)}",
|
|
@@ -29,7 +30,7 @@ function windowsOwnerQueryCommand(targetPath) {
|
|
|
29
30
|
"$raw=[System.Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(),0)",
|
|
30
31
|
"$dacl=$raw.DiscretionaryAcl;$complete=$true",
|
|
31
32
|
"$aces=@(foreach($ace in $dacl){if($ace -isnot [System.Security.AccessControl.CommonAce] -or $ace.IsCallback -or [int]$ace.AceType -notin @(0,1)){$complete=$false;continue};@{sid=$ace.SecurityIdentifier.Value;mask=([long]$ace.AccessMask -band 4294967295);deny=([int]$ace.AceType -eq 1);inheritOnly=([int]$ace.AceFlags -band 8) -ne 0}})",
|
|
32
|
-
"@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|ConvertTo-Json -Depth 4 -Compress",
|
|
33
|
+
"@{ownerSid=$ownerSid;currentUserSid=$currentSid;daclPresent=($null -ne $dacl);aces=$aces;complete=$complete;remote=$remote}|Microsoft.PowerShell.Utility\\ConvertTo-Json -Depth 4 -Compress",
|
|
33
34
|
].join(";");
|
|
34
35
|
}
|
|
35
36
|
function parseWindowsAclFacts(parsed) {
|