@openclaw/fs-safe 0.13.1 → 0.15.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 +64 -1
- package/README.md +10 -4
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-merge.d.ts.map +1 -1
- package/dist/archive-merge.js +113 -46
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +4 -4
- package/dist/archive-zip-directory.d.ts +4 -0
- package/dist/archive-zip-directory.d.ts.map +1 -1
- package/dist/archive-zip-directory.js +2 -0
- package/dist/archive-zip-entry.d.ts +6 -2
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +23 -8
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +3 -4
- package/dist/archive-zip-loader.d.ts.map +1 -1
- package/dist/archive-zip-loader.js +107 -31
- package/dist/archive-zip-names.d.ts +1 -0
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +6 -0
- package/dist/archive.js +6 -5
- package/dist/bounded-read-stream.d.ts +0 -1
- package/dist/bounded-read-stream.d.ts.map +1 -1
- package/dist/bounded-read-stream.js +0 -6
- package/dist/copy-publication.d.ts +6 -0
- package/dist/copy-publication.d.ts.map +1 -1
- package/dist/copy-publication.js +3 -0
- package/dist/copy-tree-portable.d.ts.map +1 -1
- package/dist/copy-tree-portable.js +44 -24
- package/dist/copy.d.ts.map +1 -1
- package/dist/copy.js +29 -11
- package/dist/directory-mode-owner.js +5 -5
- package/dist/file-handle-transfer.d.ts +2 -0
- package/dist/file-handle-transfer.d.ts.map +1 -1
- package/dist/file-handle-transfer.js +57 -2
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +18 -4
- package/dist/file-lock-sync-admission.d.ts +19 -0
- package/dist/file-lock-sync-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-admission.js +93 -0
- package/dist/file-lock-sync-root-acquire.d.ts +4 -0
- package/dist/file-lock-sync-root-acquire.d.ts.map +1 -0
- package/dist/file-lock-sync-root-acquire.js +370 -0
- package/dist/file-lock-sync-root-arbitration.d.ts +18 -0
- package/dist/file-lock-sync-root-arbitration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-arbitration.js +66 -0
- package/dist/file-lock-sync-root-held.d.ts +34 -0
- package/dist/file-lock-sync-root-held.d.ts.map +1 -0
- package/dist/file-lock-sync-root-held.js +393 -0
- package/dist/file-lock-sync-root-io.d.ts +44 -0
- package/dist/file-lock-sync-root-io.d.ts.map +1 -0
- package/dist/file-lock-sync-root-io.js +209 -0
- package/dist/file-lock-sync-root-mutation.d.ts +17 -0
- package/dist/file-lock-sync-root-mutation.d.ts.map +1 -0
- package/dist/file-lock-sync-root-mutation.js +277 -0
- package/dist/file-lock-sync-root-options.d.ts +20 -0
- package/dist/file-lock-sync-root-options.d.ts.map +1 -0
- package/dist/file-lock-sync-root-options.js +58 -0
- package/dist/file-lock-sync-root-registration.d.ts +2 -0
- package/dist/file-lock-sync-root-registration.d.ts.map +1 -0
- package/dist/file-lock-sync-root-registration.js +90 -0
- package/dist/file-lock-sync-root.d.ts +36 -0
- package/dist/file-lock-sync-root.d.ts.map +1 -0
- package/dist/file-lock-sync-root.js +361 -0
- package/dist/file-lock-sync-stale-admission.d.ts +24 -0
- package/dist/file-lock-sync-stale-admission.d.ts.map +1 -0
- package/dist/file-lock-sync-stale-admission.js +205 -0
- package/dist/file-lock-sync.d.ts.map +1 -1
- package/dist/file-lock-sync.js +245 -205
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +5 -1
- package/dist/file-store-sync-directory.d.ts.map +1 -1
- package/dist/file-store-sync-directory.js +14 -7
- package/dist/file-store-sync-write.js +3 -3
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +47 -12
- package/dist/guest-dispatch-python.js +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +22 -15
- package/dist/json-durable-queue-ownership.d.ts +0 -1
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +0 -6
- package/dist/native-binding.d.ts +8 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-parent-admission.d.ts +3 -2
- package/dist/native-parent-admission.d.ts.map +1 -1
- package/dist/native-parent-admission.js +24 -5
- package/dist/native-pinned-write-windows.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +7 -7
- package/dist/native-pinned-write.d.ts.map +1 -1
- package/dist/native-pinned-write.js +202 -172
- package/dist/native-policy-directory-observation.d.ts +12 -0
- package/dist/native-policy-directory-observation.d.ts.map +1 -0
- package/dist/native-policy-directory-observation.js +61 -0
- package/dist/native-policy-parent-windows.d.ts +14 -0
- package/dist/native-policy-parent-windows.d.ts.map +1 -0
- package/dist/native-policy-parent-windows.js +200 -0
- package/dist/native-rename-outcome.d.ts +4 -0
- package/dist/native-rename-outcome.d.ts.map +1 -0
- package/dist/native-rename-outcome.js +8 -0
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +42 -40
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +12 -8
- package/dist/path-prefix.d.ts.map +1 -1
- package/dist/path-prefix.js +30 -8
- package/dist/path-suffix-aliases.d.ts +2 -0
- package/dist/path-suffix-aliases.d.ts.map +1 -1
- package/dist/path-suffix-aliases.js +25 -17
- package/dist/permission-exec.d.ts +2 -0
- package/dist/permission-exec.d.ts.map +1 -1
- package/dist/permission-exec.js +150 -21
- package/dist/permissions-windows.js +1 -1
- package/dist/pinned-mutation-admission.d.ts.map +1 -1
- package/dist/pinned-mutation-admission.js +23 -29
- package/dist/pinned-mutation-observation.d.ts +7 -2
- package/dist/pinned-mutation-observation.d.ts.map +1 -1
- package/dist/pinned-mutation-observation.js +81 -18
- package/dist/pinned-mutation-shared-route.d.ts +1 -0
- package/dist/pinned-mutation-shared-route.d.ts.map +1 -1
- package/dist/pinned-mutation-shared-route.js +1 -1
- package/dist/pinned-write-types.d.ts +2 -0
- package/dist/pinned-write-types.d.ts.map +1 -1
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +75 -121
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +1 -15
- package/dist/replace-directory.d.ts.map +1 -1
- package/dist/replace-directory.js +256 -18
- package/dist/replace-file-copy-fallback.d.ts.map +1 -1
- package/dist/replace-file-copy-fallback.js +62 -70
- package/dist/replace-file-copy-source.d.ts.map +1 -1
- package/dist/replace-file-copy-source.js +10 -12
- package/dist/replace-file-temp-owner.d.ts +5 -2
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +65 -30
- package/dist/replace-file.js +6 -6
- package/dist/retained-directory-replacement.d.ts +26 -0
- package/dist/retained-directory-replacement.d.ts.map +1 -0
- package/dist/retained-directory-replacement.js +193 -0
- package/dist/root-boundary.d.ts +1 -0
- package/dist/root-boundary.d.ts.map +1 -1
- package/dist/root-boundary.js +4 -0
- package/dist/root-context.d.ts +0 -8
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +0 -3
- package/dist/root-create-input.d.ts +6 -0
- package/dist/root-create-input.d.ts.map +1 -1
- package/dist/root-create-input.js +5 -1
- package/dist/root-directory-creation.d.ts.map +1 -1
- package/dist/root-directory-creation.js +5 -4
- package/dist/root-directory-list.d.ts +1 -0
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +1 -0
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +20 -11
- package/dist/root-move-noreplace.d.ts.map +1 -1
- package/dist/root-move-noreplace.js +2 -2
- package/dist/root-path-errors.d.ts +1 -0
- package/dist/root-path-errors.d.ts.map +1 -1
- package/dist/root-path-errors.js +11 -2
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +12 -37
- package/dist/root-path.js +1 -13
- package/dist/root-remove.d.ts +1 -0
- package/dist/root-remove.d.ts.map +1 -1
- package/dist/root-remove.js +4 -0
- package/dist/root-walk.d.ts +1 -1
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +17 -2
- package/dist/root-write-admission.d.ts +0 -2
- package/dist/root-write-admission.d.ts.map +1 -1
- package/dist/root-write-admission.js +1 -15
- package/dist/root-write-compatibility.d.ts +1 -2
- package/dist/root-write-compatibility.d.ts.map +1 -1
- package/dist/root-write-compatibility.js +17 -68
- package/dist/root-write-complete-parent.d.ts.map +1 -1
- package/dist/root-write-complete-parent.js +10 -24
- package/dist/root-write-lock-binding.d.ts +15 -0
- package/dist/root-write-lock-binding.d.ts.map +1 -0
- package/dist/root-write-lock-binding.js +162 -0
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +29 -42
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +2 -24
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +3 -24
- package/dist/secret-read-policy.d.ts +6 -2
- package/dist/secret-read-policy.d.ts.map +1 -1
- package/dist/secret-read-policy.js +26 -2
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +23 -12
- package/dist/sidecar-lock-acquire.d.ts +2 -28
- package/dist/sidecar-lock-acquire.d.ts.map +1 -1
- package/dist/sidecar-lock-acquire.js +288 -199
- package/dist/sidecar-lock-admission-context.d.ts +19 -0
- package/dist/sidecar-lock-admission-context.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-context.js +60 -0
- package/dist/sidecar-lock-admission-parser.d.ts +43 -0
- package/dist/sidecar-lock-admission-parser.d.ts.map +1 -0
- package/dist/sidecar-lock-admission-parser.js +113 -0
- package/dist/sidecar-lock-admission.d.ts +35 -0
- package/dist/sidecar-lock-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-admission.js +7 -0
- package/dist/sidecar-lock-reclaim.d.ts +9 -4
- package/dist/sidecar-lock-reclaim.d.ts.map +1 -1
- package/dist/sidecar-lock-reclaim.js +80 -25
- package/dist/sidecar-lock-stale-admission.d.ts +39 -0
- package/dist/sidecar-lock-stale-admission.d.ts.map +1 -0
- package/dist/sidecar-lock-stale-admission.js +232 -0
- package/dist/sidecar-lock-target.d.ts +8 -0
- package/dist/sidecar-lock-target.d.ts.map +1 -0
- package/dist/sidecar-lock-target.js +55 -0
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +100 -16
- package/dist/staged-directory.d.ts +5 -2
- package/dist/staged-directory.d.ts.map +1 -1
- package/dist/staged-directory.js +37 -1
- package/dist/temp-workspace-admission.d.ts.map +1 -1
- package/dist/temp-workspace-admission.js +28 -4
- package/dist/temp-workspace-descriptor.d.ts.map +1 -1
- package/dist/temp-workspace-descriptor.js +9 -27
- package/dist/temp-workspace-owner.d.ts.map +1 -1
- package/dist/temp-workspace-owner.js +8 -8
- package/dist/walk.d.ts +5 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +19 -6
- package/dist/windows-owner.d.ts.map +1 -1
- package/dist/windows-owner.js +2 -2
- package/docs/advanced.md +1 -1
- package/docs/archive.md +36 -9
- package/docs/atomic.md +85 -8
- package/docs/copy.md +35 -0
- package/docs/file-store.md +19 -0
- package/docs/guest.md +1 -1
- package/docs/json-store.md +5 -0
- package/docs/native-helper.md +10 -3
- package/docs/native.md +8 -0
- package/docs/output.md +6 -0
- package/docs/path-prefix.md +10 -0
- package/docs/path-suffix-aliases.md +51 -6
- package/docs/permissions.md +13 -0
- package/docs/public-api.md +3 -2
- package/docs/root.md +22 -3
- package/docs/sidecar-lock.md +109 -4
- package/docs/staged-file.md +7 -3
- package/docs/temp.md +20 -3
- package/docs/walk.md +67 -1
- package/docs/writing.md +11 -1
- package/package.json +10 -10
- package/dist/darwin-acl.d.ts +0 -4
- package/dist/darwin-acl.d.ts.map +0 -1
- package/dist/darwin-acl.js +0 -24
|
@@ -55,9 +55,11 @@ import path from "node:path";
|
|
|
55
55
|
import { inspectDirectoryIdentity } from "./directory-guard.js";
|
|
56
56
|
import { FsSafeError } from "./errors.js";
|
|
57
57
|
import { runPinnedWriteWindows } from "./native-pinned-write-windows.js";
|
|
58
|
+
import { capturePolicyAwareWindowsParent } from "./native-policy-parent-windows.js";
|
|
58
59
|
import { openNativeParentAdmission, openNativeRootAdmission } from "./native-parent-admission.js";
|
|
59
60
|
import { assertNativeStaging, writeNativeStage } from "./native-staged-file.js";
|
|
60
61
|
import { captureNativeFdClose } from "./native-binding.js";
|
|
62
|
+
import { NativePolicyDirectoryMismatch } from "./native-policy-directory-observation.js";
|
|
61
63
|
import { assertPolicyStagedDirectoryCurrent, assertStagedDirectoryCurrent, describePolicyStagedDirectory, describeStagedDirectory, refreshPolicyStagedDirectoryObservation, } from "./staged-directory.js";
|
|
62
64
|
import { inspectFileIdentitySync } from "./strict-file-identity.js";
|
|
63
65
|
import { realpathSync } from "./realpath.js";
|
|
@@ -118,8 +120,8 @@ async function describePosixParent(parentFd, pathname) {
|
|
|
118
120
|
observation: checkedMutationDirectory(parentPath, directory.realPath, parentPathStat),
|
|
119
121
|
};
|
|
120
122
|
}
|
|
121
|
-
function describePolicyPosixParent(parentFd, pathname) {
|
|
122
|
-
const policyDirectory = describePolicyStagedDirectory(parentFd, pathname);
|
|
123
|
+
function describePolicyPosixParent(binding, parentFd, pathname) {
|
|
124
|
+
const policyDirectory = describePolicyStagedDirectory(parentFd, pathname, binding);
|
|
123
125
|
return {
|
|
124
126
|
parentFd,
|
|
125
127
|
parentPath: policyDirectory.directory.realPath,
|
|
@@ -131,11 +133,12 @@ function describePolicyPosixParent(parentFd, pathname) {
|
|
|
131
133
|
}
|
|
132
134
|
function policyParentCaptureDeopt(error) {
|
|
133
135
|
return error instanceof FsSafeError &&
|
|
136
|
+
!(error instanceof NativePolicyDirectoryMismatch) &&
|
|
134
137
|
(error.code === "path-mismatch" || error.code === "not-file");
|
|
135
138
|
}
|
|
136
|
-
function tryDescribePolicyPosixParent(parentFd, pathname) {
|
|
139
|
+
function tryDescribePolicyPosixParent(binding, parentFd, pathname) {
|
|
137
140
|
try {
|
|
138
|
-
return describePolicyPosixParent(parentFd, pathname);
|
|
141
|
+
return describePolicyPosixParent(binding, parentFd, pathname);
|
|
139
142
|
}
|
|
140
143
|
catch (error) {
|
|
141
144
|
if (!policyParentCaptureDeopt(error))
|
|
@@ -149,183 +152,215 @@ function assertPosixParentCurrent(parent) {
|
|
|
149
152
|
: assertStagedDirectoryCurrent(parent.directory);
|
|
150
153
|
}
|
|
151
154
|
async function capturePolicyAwarePosixParent(binding, params, rootFd, directoryFlags) {
|
|
152
|
-
const
|
|
153
|
-
const segments = relativeParentSegments(params.relativeParentPath);
|
|
154
|
-
const parentSpelling = segments.length
|
|
155
|
-
? path.join(params.rootPath, ...segments)
|
|
156
|
-
: params.rootPath;
|
|
157
|
-
const segmentRoute = createPathSegmentRoute(segments);
|
|
158
|
-
const initialTarget = prospectiveTargetPath(params.rootPath, segmentRoute, 0, params.basename);
|
|
159
|
-
let retainedTargetPath = params.mutationAdmission?.beginParentWalk?.();
|
|
160
|
-
if (retainedTargetPath && !sameAbsolutePath(retainedTargetPath, initialTarget)) {
|
|
161
|
-
retainedTargetPath = undefined;
|
|
162
|
-
}
|
|
163
|
-
// Preserve the one-open hot path when the complete parent still exists.
|
|
164
|
-
// Policy is attached only after the opened descriptor is associated with its
|
|
165
|
-
// current canonical pathname, so a contained redirect cannot retain the old
|
|
166
|
-
// preflight authorization.
|
|
167
|
-
let completeParentFd;
|
|
155
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
168
156
|
try {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
157
|
+
const closeFd = captureNativeFdClose(binding);
|
|
158
|
+
const observationDisposers = new Map();
|
|
159
|
+
const disposeObservation = (fd) => {
|
|
160
|
+
const dispose = observationDisposers.get(fd);
|
|
161
|
+
observationDisposers.delete(fd);
|
|
162
|
+
dispose?.();
|
|
163
|
+
};
|
|
164
|
+
const observationScope = __addDisposableResource(env_1, {
|
|
165
|
+
[Symbol.dispose]() { for (const dispose of observationDisposers.values())
|
|
166
|
+
dispose(); },
|
|
167
|
+
}, false);
|
|
168
|
+
const capturePolicyParent = (fd, pathname) => {
|
|
169
|
+
const captured = tryDescribePolicyPosixParent(binding, fd, pathname);
|
|
170
|
+
const dispose = captured?.policyDirectory?.disposeObservation;
|
|
171
|
+
if (dispose)
|
|
172
|
+
observationDisposers.set(fd, dispose);
|
|
173
|
+
return captured;
|
|
174
|
+
};
|
|
175
|
+
const segments = relativeParentSegments(params.relativeParentPath);
|
|
176
|
+
const parentSpelling = segments.length
|
|
177
|
+
? path.join(params.rootPath, ...segments)
|
|
178
|
+
: params.rootPath;
|
|
179
|
+
const segmentRoute = createPathSegmentRoute(segments);
|
|
180
|
+
const initialTarget = prospectiveTargetPath(params.rootPath, segmentRoute, 0, params.basename);
|
|
181
|
+
let retainedTargetPath = params.mutationAdmission?.beginParentWalk?.();
|
|
182
|
+
if (retainedTargetPath && !sameAbsolutePath(retainedTargetPath, initialTarget)) {
|
|
183
|
+
retainedTargetPath = undefined;
|
|
184
|
+
}
|
|
185
|
+
// Preserve the one-open hot path when the complete parent still exists.
|
|
186
|
+
// Policy is attached only after the opened descriptor is associated with its
|
|
187
|
+
// current canonical pathname, so a contained redirect cannot retain the old
|
|
188
|
+
// preflight authorization.
|
|
189
|
+
let completeParentFd;
|
|
177
190
|
try {
|
|
178
|
-
|
|
179
|
-
? tryDescribePolicyPosixParent(parentFd, parentSpelling)
|
|
180
|
-
: undefined;
|
|
181
|
-
if (!admitted) {
|
|
182
|
-
retainedTargetPath = undefined;
|
|
183
|
-
admitted = await describePosixParent(parentFd, parentSpelling);
|
|
184
|
-
}
|
|
185
|
-
const targetPath = path.join(admitted.parentPath, params.basename);
|
|
186
|
-
await authorizePinnedMutation(params, {
|
|
187
|
-
targetPath,
|
|
188
|
-
mutationPath: targetPath,
|
|
189
|
-
phase: "parent",
|
|
190
|
-
});
|
|
191
|
-
assertPosixParentCurrent(admitted);
|
|
192
|
-
return admitted;
|
|
191
|
+
completeParentFd = binding.openBeneath(rootFd, params.relativeParentPath, directoryFlags).fd;
|
|
193
192
|
}
|
|
194
193
|
catch (error) {
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
if (error?.code !== "ENOENT" || !params.mkdir)
|
|
195
|
+
throw error;
|
|
197
196
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// existing prefix. Walk missing-parent cases one direct child at a time so
|
|
201
|
-
// every existing/opened object is authorized and every mkdir is authorized
|
|
202
|
-
// before dispatch. O_NOFOLLOW makes a concurrently introduced link fail
|
|
203
|
-
// before the next component can be created through it.
|
|
204
|
-
const secureDirectoryFlags = directoryFlags | (fsSync.constants.O_NOFOLLOW ?? 0);
|
|
205
|
-
let currentFd = rootFd;
|
|
206
|
-
let currentOwnedFd;
|
|
207
|
-
let currentPath = params.rootPath;
|
|
208
|
-
let current = retainedTargetPath
|
|
209
|
-
? tryDescribePolicyPosixParent(rootFd, currentPath)
|
|
210
|
-
: undefined;
|
|
211
|
-
if (!current) {
|
|
212
|
-
retainedTargetPath = undefined;
|
|
213
|
-
current = await describePosixParent(rootFd, currentPath);
|
|
214
|
-
}
|
|
215
|
-
try {
|
|
216
|
-
await authorizePinnedMutation(params, {
|
|
217
|
-
targetPath: retainedTargetPath ?? initialTarget,
|
|
218
|
-
mutationPath: retainedTargetPath ?? initialTarget,
|
|
219
|
-
phase: "parent",
|
|
220
|
-
});
|
|
221
|
-
assertPosixParentCurrent(current);
|
|
222
|
-
for (let index = 0; index < segments.length; index += 1) {
|
|
223
|
-
const segment = segments[index];
|
|
224
|
-
const childPath = path.join(currentPath, segment);
|
|
225
|
-
let childFd;
|
|
226
|
-
let createdByMkdir = false;
|
|
227
|
-
let createReceipt;
|
|
197
|
+
if (completeParentFd !== undefined) {
|
|
198
|
+
const parentFd = completeParentFd;
|
|
228
199
|
try {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (!
|
|
233
|
-
|
|
200
|
+
let admitted = retainedTargetPath
|
|
201
|
+
? capturePolicyParent(parentFd, parentSpelling)
|
|
202
|
+
: undefined;
|
|
203
|
+
if (!admitted) {
|
|
204
|
+
retainedTargetPath = undefined;
|
|
205
|
+
admitted = await describePosixParent(parentFd, parentSpelling);
|
|
234
206
|
}
|
|
235
|
-
const targetPath =
|
|
236
|
-
|
|
207
|
+
const targetPath = path.join(admitted.parentPath, params.basename);
|
|
208
|
+
await authorizePinnedMutation(params, {
|
|
237
209
|
targetPath,
|
|
238
|
-
mutationPath:
|
|
239
|
-
phase: "parent
|
|
210
|
+
mutationPath: targetPath,
|
|
211
|
+
phase: "parent",
|
|
240
212
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
213
|
+
assertPosixParentCurrent(admitted);
|
|
214
|
+
return admitted;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
disposeObservation(parentFd);
|
|
218
|
+
closeFd(parentFd);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// The canonical preflight spelling has no intentional symlinks in its
|
|
223
|
+
// existing prefix. Walk missing-parent cases one direct child at a time so
|
|
224
|
+
// every existing/opened object is authorized and every mkdir is authorized
|
|
225
|
+
// before dispatch. O_NOFOLLOW makes a concurrently introduced link fail
|
|
226
|
+
// before the next component can be created through it.
|
|
227
|
+
const secureDirectoryFlags = directoryFlags | (fsSync.constants.O_NOFOLLOW ?? 0);
|
|
228
|
+
let currentFd = rootFd;
|
|
229
|
+
let currentOwnedFd;
|
|
230
|
+
let currentPath = params.rootPath;
|
|
231
|
+
let current = retainedTargetPath
|
|
232
|
+
? capturePolicyParent(rootFd, currentPath)
|
|
233
|
+
: undefined;
|
|
234
|
+
if (!current) {
|
|
235
|
+
retainedTargetPath = undefined;
|
|
236
|
+
current = await describePosixParent(rootFd, currentPath);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
await authorizePinnedMutation(params, {
|
|
240
|
+
targetPath: retainedTargetPath ?? initialTarget,
|
|
241
|
+
mutationPath: retainedTargetPath ?? initialTarget,
|
|
242
|
+
phase: "parent",
|
|
243
|
+
});
|
|
244
|
+
assertPosixParentCurrent(current);
|
|
245
|
+
for (let index = 0; index < segments.length; index += 1) {
|
|
246
|
+
const segment = segments[index];
|
|
247
|
+
const childPath = path.join(currentPath, segment);
|
|
248
|
+
let childFd;
|
|
249
|
+
let createdByMkdir = false;
|
|
250
|
+
let createReceipt;
|
|
250
251
|
try {
|
|
251
252
|
childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
|
|
252
253
|
}
|
|
253
|
-
catch (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (createdByMkdir && createReceipt && params.mutationAdmission?.advanceCreatedDirectory) {
|
|
273
|
-
let evidence;
|
|
254
|
+
catch (openError) {
|
|
255
|
+
if (!isNotFoundPathError(openError)) {
|
|
256
|
+
throw normalizePolicyParentOpenError(openError, params);
|
|
257
|
+
}
|
|
258
|
+
const targetPath = retainedTargetPath ?? prospectiveTargetPath(currentPath, segmentRoute, index, params.basename);
|
|
259
|
+
const request = Object.freeze({
|
|
260
|
+
targetPath,
|
|
261
|
+
mutationPath: childPath,
|
|
262
|
+
phase: "parent-create",
|
|
263
|
+
});
|
|
264
|
+
createReceipt = params.mutationAdmission?.tryAuthorizeAtParent?.(request, current.observation);
|
|
265
|
+
if (!createReceipt) {
|
|
266
|
+
createReceipt = await authorizePinnedMutation(params, request);
|
|
267
|
+
assertPosixParentCurrent(current);
|
|
268
|
+
}
|
|
269
|
+
params.assertBeforeMutation?.();
|
|
270
|
+
// Do not carry pathname freshness across the live authority callback.
|
|
271
|
+
assertPosixParentCurrent(current);
|
|
272
|
+
createdByMkdir = mkdirPolicyChild(binding, currentFd, segment, 0o777);
|
|
274
273
|
try {
|
|
275
|
-
|
|
276
|
-
? refreshPolicyStagedDirectoryObservation(current.policyDirectory)
|
|
277
|
-
: checkedMutationDirectory(currentPath, current.directory.realPath, assertStagedDirectoryCurrent(current.directory));
|
|
278
|
-
evidence = Object.freeze({
|
|
279
|
-
admission: createReceipt,
|
|
280
|
-
parent,
|
|
281
|
-
child: child.observation,
|
|
282
|
-
});
|
|
274
|
+
childFd = binding.openBeneath(currentFd, segment, secureDirectoryFlags).fd;
|
|
283
275
|
}
|
|
284
|
-
catch {
|
|
285
|
-
|
|
276
|
+
catch (createdOpenError) {
|
|
277
|
+
throw normalizePolicyParentOpenError(createdOpenError, params);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
let child;
|
|
281
|
+
try {
|
|
282
|
+
const acceleratedChild = retainedTargetPath
|
|
283
|
+
? capturePolicyParent(childFd, childPath)
|
|
284
|
+
: undefined;
|
|
285
|
+
if (acceleratedChild) {
|
|
286
|
+
child = acceleratedChild;
|
|
286
287
|
}
|
|
287
|
-
|
|
288
|
-
|
|
288
|
+
else {
|
|
289
|
+
retainedTargetPath = undefined;
|
|
290
|
+
child = await describePosixParent(childFd, childPath);
|
|
291
|
+
}
|
|
292
|
+
if (!sameAbsolutePath(child.parentPath, childPath))
|
|
293
|
+
retainedTargetPath = undefined;
|
|
294
|
+
let childAuthorization;
|
|
295
|
+
if (createdByMkdir && createReceipt && params.mutationAdmission?.advanceCreatedDirectory) {
|
|
296
|
+
let evidence;
|
|
297
|
+
try {
|
|
298
|
+
const parent = current.policyDirectory
|
|
299
|
+
? refreshPolicyStagedDirectoryObservation(current.policyDirectory)
|
|
300
|
+
: checkedMutationDirectory(currentPath, current.directory.realPath, assertStagedDirectoryCurrent(current.directory));
|
|
301
|
+
evidence = Object.freeze({
|
|
302
|
+
admission: createReceipt,
|
|
303
|
+
parent,
|
|
304
|
+
child: child.observation,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Leave failed evidence to the full ordered admission below.
|
|
309
|
+
}
|
|
310
|
+
if (evidence) {
|
|
311
|
+
childAuthorization = params.mutationAdmission.advanceCreatedDirectory(evidence);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const targetPath = retainedTargetPath ?? prospectiveTargetPath(child.parentPath, segmentRoute, index + 1, params.basename);
|
|
315
|
+
const request = Object.freeze({
|
|
316
|
+
targetPath,
|
|
317
|
+
mutationPath: targetPath,
|
|
318
|
+
phase: "parent",
|
|
319
|
+
});
|
|
320
|
+
childAuthorization ??= params.mutationAdmission?.tryAuthorizeAtParent?.(request, child.observation);
|
|
321
|
+
if (!childAuthorization) {
|
|
322
|
+
await authorizePinnedMutation(params, request);
|
|
323
|
+
assertPosixParentCurrent(child);
|
|
289
324
|
}
|
|
290
325
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
326
|
+
catch (error) {
|
|
327
|
+
disposeObservation(childFd);
|
|
328
|
+
closeFd(childFd);
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
const previousOwnedFd = currentOwnedFd;
|
|
332
|
+
disposeObservation(current.parentFd);
|
|
333
|
+
currentOwnedFd = child.parentFd;
|
|
334
|
+
currentFd = child.parentFd;
|
|
335
|
+
currentPath = child.parentPath;
|
|
336
|
+
current = child;
|
|
337
|
+
if (previousOwnedFd !== undefined)
|
|
338
|
+
closeFd(previousOwnedFd);
|
|
339
|
+
if (index === segments.length - 1) {
|
|
340
|
+
currentOwnedFd = undefined;
|
|
341
|
+
return child;
|
|
301
342
|
}
|
|
302
343
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
currentPath = child.parentPath;
|
|
311
|
-
current = child;
|
|
312
|
-
if (previousOwnedFd !== undefined)
|
|
313
|
-
closeFd(previousOwnedFd);
|
|
314
|
-
if (index === segments.length - 1) {
|
|
315
|
-
currentOwnedFd = undefined;
|
|
316
|
-
return child;
|
|
344
|
+
// Empty relative parents are handled by the complete-parent fast path.
|
|
345
|
+
throw new FsSafeError("path-mismatch", "native write parent admission did not complete");
|
|
346
|
+
}
|
|
347
|
+
finally {
|
|
348
|
+
if (currentOwnedFd !== undefined) {
|
|
349
|
+
disposeObservation(currentOwnedFd);
|
|
350
|
+
closeFd(currentOwnedFd);
|
|
317
351
|
}
|
|
318
352
|
}
|
|
319
|
-
|
|
320
|
-
|
|
353
|
+
}
|
|
354
|
+
catch (e_1) {
|
|
355
|
+
env_1.error = e_1;
|
|
356
|
+
env_1.hasError = true;
|
|
321
357
|
}
|
|
322
358
|
finally {
|
|
323
|
-
|
|
324
|
-
closeFd(currentOwnedFd);
|
|
359
|
+
__disposeResources(env_1);
|
|
325
360
|
}
|
|
326
361
|
}
|
|
327
362
|
export async function runPinnedWriteNative(binding, params) {
|
|
328
|
-
const
|
|
363
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
329
364
|
try {
|
|
330
365
|
const closeFd = captureNativeFdClose(binding);
|
|
331
366
|
const windows = process.platform === "win32";
|
|
@@ -339,12 +374,12 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
339
374
|
operation: "native write",
|
|
340
375
|
});
|
|
341
376
|
const root = rootAdmission.root;
|
|
342
|
-
const posixRoot = __addDisposableResource(
|
|
377
|
+
const posixRoot = __addDisposableResource(env_2, windows ? undefined : root, true);
|
|
343
378
|
let parentFd;
|
|
344
379
|
let windowsOwnsDirectories = false;
|
|
345
380
|
// Until stage construction takes ownership, even admission failures must close
|
|
346
381
|
// the raw POSIX parent. Disposal preserves both admission and close failures.
|
|
347
|
-
const parentGuard = __addDisposableResource(
|
|
382
|
+
const parentGuard = __addDisposableResource(env_2, {
|
|
348
383
|
[Symbol.dispose]() {
|
|
349
384
|
if (!windows && parentFd !== undefined) {
|
|
350
385
|
closeFd(parentFd);
|
|
@@ -355,14 +390,18 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
355
390
|
let parentPath;
|
|
356
391
|
let directory;
|
|
357
392
|
let parentPathStat;
|
|
358
|
-
let policyParentAdmitted = false;
|
|
359
393
|
if (!windows && params.mutationAdmission) {
|
|
360
394
|
const admitted = await capturePolicyAwarePosixParent(binding, params, root.fd, directoryFlags);
|
|
361
395
|
parentFd = admitted.parentFd;
|
|
362
396
|
parentPath = admitted.parentPath;
|
|
363
397
|
directory = admitted.directory;
|
|
364
398
|
parentPathStat = admitted.parentPathStat;
|
|
365
|
-
|
|
399
|
+
}
|
|
400
|
+
else if (windows && params.mutationAdmission) {
|
|
401
|
+
const admitted = await capturePolicyAwareWindowsParent(binding, params, rootAdmission);
|
|
402
|
+
parentFd = admitted.fd;
|
|
403
|
+
parentPath = admitted.guard.realPath;
|
|
404
|
+
parentPathStat = admitted.guard.stat;
|
|
366
405
|
}
|
|
367
406
|
else {
|
|
368
407
|
if (params.mkdir) {
|
|
@@ -375,15 +414,6 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
375
414
|
directory = admitted.stagedDirectory;
|
|
376
415
|
parentPathStat = admitted.guard.stat;
|
|
377
416
|
}
|
|
378
|
-
if (!policyParentAdmitted && params.mutationAdmission) {
|
|
379
|
-
const targetPath = path.join(parentPath, params.basename);
|
|
380
|
-
await authorizePinnedMutation(params, {
|
|
381
|
-
targetPath,
|
|
382
|
-
mutationPath: targetPath,
|
|
383
|
-
phase: "parent",
|
|
384
|
-
});
|
|
385
|
-
await inspectDirectoryIdentity(parentPath, inspectFileIdentitySync(() => fsSync.fstatSync(parentFd, { bigint: true })));
|
|
386
|
-
}
|
|
387
417
|
const verificationGuard = { dir: parentPath, realPath: parentPath, stat: parentPathStat };
|
|
388
418
|
if (params.overwrite === false) {
|
|
389
419
|
try {
|
|
@@ -423,12 +453,12 @@ export async function runPinnedWriteNative(binding, params) {
|
|
|
423
453
|
}
|
|
424
454
|
}
|
|
425
455
|
}
|
|
426
|
-
catch (
|
|
427
|
-
|
|
428
|
-
|
|
456
|
+
catch (e_2) {
|
|
457
|
+
env_2.error = e_2;
|
|
458
|
+
env_2.hasError = true;
|
|
429
459
|
}
|
|
430
460
|
finally {
|
|
431
|
-
const result_1 = __disposeResources(
|
|
461
|
+
const result_1 = __disposeResources(env_2);
|
|
432
462
|
if (result_1)
|
|
433
463
|
await result_1;
|
|
434
464
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import type { NativeBinding } from "./native-binding.js";
|
|
3
|
+
import type { MutationDirectoryObserver } from "./pinned-mutation-observation.js";
|
|
4
|
+
export declare class NativePolicyDirectoryMismatch extends FsSafeError {
|
|
5
|
+
constructor(cause?: Error);
|
|
6
|
+
}
|
|
7
|
+
export type NativePolicyDirectoryObserver = MutationDirectoryObserver & {
|
|
8
|
+
dispose(): void;
|
|
9
|
+
};
|
|
10
|
+
/** Fresh name/metadata/canonical facts from a caller-owned POSIX descriptor. */
|
|
11
|
+
export declare function nativePolicyDirectoryObserver(binding: NativeBinding | undefined, fd: number, pathname: string): NativePolicyDirectoryObserver | undefined;
|
|
12
|
+
//# sourceMappingURL=native-policy-directory-observation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-policy-directory-observation.d.ts","sourceRoot":"","sources":["../src/native-policy-directory-observation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAElF,qBAAa,6BAA8B,SAAQ,WAAW;IAC5D,YAAY,KAAK,CAAC,EAAE,KAAK,EAExB;CACF;AAED,MAAM,MAAM,6BAA6B,GAAG,yBAAyB,GAAG;IAAE,OAAO,IAAI,IAAI,CAAA;CAAE,CAAC;AAE5F,gFAAgF;AAChF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACf,6BAA6B,GAAG,SAAS,CA2C3C"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { FsSafeError } from "./errors.js";
|
|
3
|
+
import { getFsSafeNativeConfig } from "./native-config.js";
|
|
4
|
+
export class NativePolicyDirectoryMismatch extends FsSafeError {
|
|
5
|
+
constructor(cause) {
|
|
6
|
+
super("path-mismatch", "staging directory pathname changed", { cause });
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/** Fresh name/metadata/canonical facts from a caller-owned POSIX descriptor. */
|
|
10
|
+
export function nativePolicyDirectoryObserver(binding, fd, pathname) {
|
|
11
|
+
if (!binding || process.platform === "win32" || process.versions.bun)
|
|
12
|
+
return undefined;
|
|
13
|
+
const observe = binding.observeDirectoryFd;
|
|
14
|
+
if (typeof observe !== "function")
|
|
15
|
+
return undefined;
|
|
16
|
+
const mode = getFsSafeNativeConfig().mode;
|
|
17
|
+
let disposed = false;
|
|
18
|
+
const inspect = () => {
|
|
19
|
+
if (disposed)
|
|
20
|
+
throw new NativePolicyDirectoryMismatch();
|
|
21
|
+
if (getFsSafeNativeConfig().mode !== mode)
|
|
22
|
+
return undefined;
|
|
23
|
+
let observed;
|
|
24
|
+
try {
|
|
25
|
+
observed = observe.call(binding, fd, pathname);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
if (error?.code === "OBSERVATION_UNAVAILABLE")
|
|
29
|
+
return undefined;
|
|
30
|
+
if (error?.code === "OBSERVATION_REDIRECTED") {
|
|
31
|
+
// A contained redirect requires ordinary selected-object admission;
|
|
32
|
+
// it has not yet supplied inconsistent descriptor/name evidence.
|
|
33
|
+
throw new FsSafeError("path-mismatch", "staging directory pathname redirected");
|
|
34
|
+
}
|
|
35
|
+
if (error?.code === "path-mismatch") {
|
|
36
|
+
throw new NativePolicyDirectoryMismatch(error instanceof Error ? error : undefined);
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
if (getFsSafeNativeConfig().mode !== mode)
|
|
41
|
+
return undefined;
|
|
42
|
+
if (!observed || typeof observed.dev !== "bigint" || observed.dev < 0n || observed.dev > 0xffffffffffffffffn ||
|
|
43
|
+
typeof observed.ino !== "bigint" || observed.ino < 0n || observed.ino > 0xffffffffffffffffn ||
|
|
44
|
+
typeof observed.mode !== "bigint" || observed.mode < 0n || observed.mode > 0xffffffffn ||
|
|
45
|
+
typeof observed.nlink !== "bigint" || observed.nlink < 0n || observed.nlink > 0xffffffffffffffffn ||
|
|
46
|
+
typeof observed.realPath !== "string" || observed.realPath.includes("\0") ||
|
|
47
|
+
!path.isAbsolute(observed.realPath))
|
|
48
|
+
return undefined;
|
|
49
|
+
if ((observed.mode & 61440n) !== 16384n || observed.nlink === 0n ||
|
|
50
|
+
observed.realPath !== pathname) {
|
|
51
|
+
throw new NativePolicyDirectoryMismatch();
|
|
52
|
+
}
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
canonicalPath: observed.realPath,
|
|
55
|
+
identity: Object.freeze({
|
|
56
|
+
dev: observed.dev, ino: observed.ino, mode: observed.mode, nlink: observed.nlink,
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
return Object.assign(inspect, { dispose() { disposed = true; } });
|
|
61
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
import { type AsyncDirectoryGuard } from "./directory-guard.js";
|
|
3
|
+
import { type NativeRootAdmission } from "./native-parent-admission.js";
|
|
4
|
+
import type { NativeBinding } from "./native.js";
|
|
5
|
+
import type { PinnedWriteParams } from "./pinned-write.js";
|
|
6
|
+
import { type MutationDirectoryObservation } from "./pinned-mutation-observation.js";
|
|
7
|
+
type PolicyParent = {
|
|
8
|
+
fd: number;
|
|
9
|
+
guard: AsyncDirectoryGuard<BigIntStats>;
|
|
10
|
+
observation: MutationDirectoryObservation;
|
|
11
|
+
};
|
|
12
|
+
export declare function capturePolicyAwareWindowsParent(binding: NativeBinding, params: PinnedWriteParams, rootAdmission: NativeRootAdmission): Promise<PolicyParent>;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=native-policy-parent-windows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-policy-parent-windows.d.ts","sourceRoot":"","sources":["../src/native-policy-parent-windows.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnG,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,OAAO,KAAK,EAAE,iBAAiB,EAAkG,MAAM,mBAAmB,CAAC;AAC3J,OAAO,EAA4B,KAAK,4BAA4B,EAAE,MAAM,kCAAkC,CAAC;AAI/G,KAAK,YAAY,GAAG;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;IACxC,WAAW,EAAE,4BAA4B,CAAC;CAC3C,CAAC;AAqBF,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,iBAAiB,EACzB,aAAa,EAAE,mBAAmB,GACjC,OAAO,CAAC,YAAY,CAAC,CA2KvB"}
|