@openclaw/openshell-sandbox 2026.9.2 → 2026.9.4
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/dist/index.js +87 -47
- package/node_modules/@openclaw/fs-safe/CHANGELOG.md +700 -0
- package/node_modules/@openclaw/fs-safe/LICENSE +21 -0
- package/node_modules/@openclaw/fs-safe/README.md +564 -0
- package/node_modules/@openclaw/fs-safe/SECURITY.md +45 -0
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.d.ts +33 -0
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/absolute-path.js +307 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/advanced.js +28 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-crc32.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-crc32.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-crc32.js +14 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-deadline.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-deadline.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-deadline.js +103 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-entry.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-entry.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-entry.js +85 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-errors.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-errors.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-errors.js +23 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-gzip-tail.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-gzip-tail.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-gzip-tail.js +74 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.d.ts +19 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-input.js +92 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-kind.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-kind.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-kind.js +82 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-limits.d.ts +57 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-limits.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-limits.js +131 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-merge.js +157 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-native.d.ts +20 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-native.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-native.js +107 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-options.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-options.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-options.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser-errors.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser-errors.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser-errors.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-parser.wasm +0 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts +20 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-plan.js +55 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-policy.d.ts +22 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-policy.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-policy.js +38 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-read.js +225 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts +31 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-staging.js +250 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-inspect.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-inspect.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-inspect.js +59 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-stream.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-stream.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-stream.js +95 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-wasm.d.ts +18 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-wasm.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar-wasm.js +99 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar.d.ts +19 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-tar.js +21 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-admission.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-admission.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-admission.js +60 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-count.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-count.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-count.js +140 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-directory.js +228 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-entry.js +27 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-integrity.js +41 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-names.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-names.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-names.js +100 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive-zip-preflight.js +44 -0
- package/node_modules/@openclaw/fs-safe/dist/archive.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/archive.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/archive.js +275 -0
- package/node_modules/@openclaw/fs-safe/dist/async-lock.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/async-lock.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/async-lock.js +17 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/atomic.js +4 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read-stream.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read-stream.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read-stream.js +21 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/bounded-read.js +106 -0
- package/node_modules/@openclaw/fs-safe/dist/byte-budget.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/byte-budget.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/byte-budget.js +9 -0
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/config.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/config.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/containment.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/containment.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/containment.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/deny-mutations.js +69 -0
- package/node_modules/@openclaw/fs-safe/dist/device-path.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/device-path.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/device-path.js +137 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts +44 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-durability.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-durability.js +299 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts +31 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-guard.js +82 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-node.js +93 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/directory-mode-owner.js +78 -0
- package/node_modules/@openclaw/fs-safe/dist/durability.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/durability.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/durability.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/error-detail.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/error-detail.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/error-detail.js +10 -0
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/errors.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/errors.js +26 -0
- package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts +10 -0
- package/node_modules/@openclaw/fs-safe/dist/file-hash.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-hash.js +73 -0
- package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/file-identity.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-identity.js +34 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.d.ts +35 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock-sync.js +349 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts +24 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-lock.js +46 -0
- package/node_modules/@openclaw/fs-safe/dist/file-observation.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-observation.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-observation.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts +33 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-boundary.js +189 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-limit.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-limit.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-limit.js +8 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-prune.js +95 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store-sync-write.js +127 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts +62 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-store.js +357 -0
- package/node_modules/@openclaw/fs-safe/dist/file-sync.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/file-sync.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/file-sync.js +19 -0
- package/node_modules/@openclaw/fs-safe/dist/filename.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/filename.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/filename.js +91 -0
- package/node_modules/@openclaw/fs-safe/dist/fs.d.ts +13 -0
- package/node_modules/@openclaw/fs-safe/dist/fs.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/fs.js +30 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mkdir.js +87 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mutation.d.ts +37 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mutation.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/guarded-mutation.js +82 -0
- package/node_modules/@openclaw/fs-safe/dist/home-dir.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/home-dir.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/home-dir.js +84 -0
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/index.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/index.js +5 -0
- package/node_modules/@openclaw/fs-safe/dist/install-path.d.ts +20 -0
- package/node_modules/@openclaw/fs-safe/dist/install-path.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/install-path.js +94 -0
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts +35 -0
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-document-store.js +97 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-directory.js +20 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue-ownership.js +292 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts +46 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-durable-queue.js +383 -0
- package/node_modules/@openclaw/fs-safe/dist/json-store.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/json-store.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-store.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/json-stringify.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/json-stringify.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json-stringify.js +7 -0
- package/node_modules/@openclaw/fs-safe/dist/json.d.ts +50 -0
- package/node_modules/@openclaw/fs-safe/dist/json.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/json.js +343 -0
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.d.ts +9 -0
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/local-file-access.js +87 -0
- package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts +26 -0
- package/node_modules/@openclaw/fs-safe/dist/local-roots.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/local-roots.js +135 -0
- package/node_modules/@openclaw/fs-safe/dist/lock-config.d.ts +10 -0
- package/node_modules/@openclaw/fs-safe/dist/lock-config.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/lock-config.js +12 -0
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.d.ts +43 -0
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/move-path-cleanup.js +161 -0
- package/node_modules/@openclaw/fs-safe/dist/move-path.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/move-path.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/move-path.js +334 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts +93 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-binding.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-config.js +71 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-operations.js +132 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write-windows.js +96 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-pinned-write.js +161 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts +31 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native-staged-file.js +362 -0
- package/node_modules/@openclaw/fs-safe/dist/native.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/native.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/native.js +204 -0
- package/node_modules/@openclaw/fs-safe/dist/opened-file-failure.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/opened-file-failure.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/opened-file-failure.js +45 -0
- package/node_modules/@openclaw/fs-safe/dist/opened-realpath.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/opened-realpath.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/opened-realpath.js +92 -0
- package/node_modules/@openclaw/fs-safe/dist/output.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/output.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/output.js +123 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts +31 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/owner-dacl.js +28 -0
- package/node_modules/@openclaw/fs-safe/dist/path-policy.d.ts +25 -0
- package/node_modules/@openclaw/fs-safe/dist/path-policy.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path-policy.js +44 -0
- package/node_modules/@openclaw/fs-safe/dist/path.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/path.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/path.js +136 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts +25 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permission-exec.js +74 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-public.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts +59 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions-windows.js +296 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts +60 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/permissions.js +125 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts +24 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-open.js +74 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-operation.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-operation.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-operation.js +35 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.d.ts +36 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/pinned-write.js +252 -0
- package/node_modules/@openclaw/fs-safe/dist/private-directory.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/private-directory.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/private-directory.js +14 -0
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts +45 -0
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/private-temp-workspace.js +299 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts +28 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file-failure.js +22 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/publish-file.js +366 -0
- package/node_modules/@openclaw/fs-safe/dist/read-open-flags.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/read-open-flags.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/read-open-flags.js +13 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/read-opened-file.js +18 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts +38 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/regular-file.js +391 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-directory.js +42 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts +39 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-fallback.js +331 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts +21 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-copy-source.js +107 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts +51 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-descriptor.js +161 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-rename-policy.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-rename-policy.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-rename-policy.js +30 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts +53 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file-temp-owner.js +376 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts +58 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/replace-file.js +329 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts +38 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-context.js +141 -0
- package/node_modules/@openclaw/fs-safe/dist/root-errors.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/root-errors.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-errors.js +75 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts +45 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-file.js +120 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts +163 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-impl.js +1230 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path-existing.js +68 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path-symlink.d.ts +7 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path-symlink.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path-symlink.js +35 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path.d.ts +40 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-path.js +481 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts +89 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-paths.js +381 -0
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts +41 -0
- package/node_modules/@openclaw/fs-safe/dist/root-walk.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-walk.js +133 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-mode.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-mode.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-mode.js +61 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-verification.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-verification.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root-write-verification.js +97 -0
- package/node_modules/@openclaw/fs-safe/dist/root.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/root.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/root.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/safe-path-segment.js +75 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts +25 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-file.js +329 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-read-async.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-read-async.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-read-async.js +73 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.d.ts +17 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/secret-read-policy.js +40 -0
- package/node_modules/@openclaw/fs-safe/dist/secret.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/secret.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/secret.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts +32 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-file.js +182 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts +27 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/secure-temp-dir.js +160 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.d.ts +15 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-staged-file.js +143 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.d.ts +27 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sibling-temp.js +95 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.d.ts +31 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-acquire.js +336 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts +24 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-handle.js +42 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-policy.js +71 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts +63 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js +305 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-root.d.ts +3 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-root.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-root.js +76 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-types.d.ts +63 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-types.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/sidecar-lock.js +265 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts +16 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-directory.js +60 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-file-types.d.ts +56 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-file-types.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/staged-file-types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/store.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/store.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/store.js +3 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/strict-file-identity.js +51 -0
- package/node_modules/@openclaw/fs-safe/dist/string-coerce.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/string-coerce.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/string-coerce.js +16 -0
- package/node_modules/@openclaw/fs-safe/dist/suppressed-error.d.ts +6 -0
- package/node_modules/@openclaw/fs-safe/dist/suppressed-error.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/suppressed-error.js +15 -0
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.d.ts +12 -0
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/symlink-parents.js +78 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-cleanup.js +87 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-target.d.ts +28 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-target.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-target.js +137 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.d.ts +23 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/temp-workspace-owner.js +320 -0
- package/node_modules/@openclaw/fs-safe/dist/temp.d.ts +4 -0
- package/node_modules/@openclaw/fs-safe/dist/temp.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/temp.js +2 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts +25 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/test-hooks.js +13 -0
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts +14 -0
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/text-atomic.js +14 -0
- package/node_modules/@openclaw/fs-safe/dist/timing.d.ts +8 -0
- package/node_modules/@openclaw/fs-safe/dist/timing.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/timing.js +40 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/trash.js +171 -0
- package/node_modules/@openclaw/fs-safe/dist/types.d.ts +23 -0
- package/node_modules/@openclaw/fs-safe/dist/types.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/types.js +1 -0
- package/node_modules/@openclaw/fs-safe/dist/walk.d.ts +37 -0
- package/node_modules/@openclaw/fs-safe/dist/walk.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/walk.js +207 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-command.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-command.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-command.js +34 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts +32 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/windows-owner.js +126 -0
- package/node_modules/@openclaw/fs-safe/dist/write-open-flags.d.ts +5 -0
- package/node_modules/@openclaw/fs-safe/dist/write-open-flags.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/write-open-flags.js +35 -0
- package/node_modules/@openclaw/fs-safe/dist/write-queue.d.ts +2 -0
- package/node_modules/@openclaw/fs-safe/dist/write-queue.d.ts.map +1 -0
- package/node_modules/@openclaw/fs-safe/dist/write-queue.js +18 -0
- package/node_modules/@openclaw/fs-safe/docs/advanced.md +153 -0
- package/node_modules/@openclaw/fs-safe/docs/archive.md +658 -0
- package/node_modules/@openclaw/fs-safe/docs/assets/readme-banner.jpg +0 -0
- package/node_modules/@openclaw/fs-safe/docs/atomic.md +334 -0
- package/node_modules/@openclaw/fs-safe/docs/config.md +131 -0
- package/node_modules/@openclaw/fs-safe/docs/contributing.md +180 -0
- package/node_modules/@openclaw/fs-safe/docs/durability.md +313 -0
- package/node_modules/@openclaw/fs-safe/docs/errors.md +232 -0
- package/node_modules/@openclaw/fs-safe/docs/file-store.md +258 -0
- package/node_modules/@openclaw/fs-safe/docs/filename.md +96 -0
- package/node_modules/@openclaw/fs-safe/docs/index.md +84 -0
- package/node_modules/@openclaw/fs-safe/docs/install-path.md +170 -0
- package/node_modules/@openclaw/fs-safe/docs/install.md +163 -0
- package/node_modules/@openclaw/fs-safe/docs/json-store.md +221 -0
- package/node_modules/@openclaw/fs-safe/docs/json.md +213 -0
- package/node_modules/@openclaw/fs-safe/docs/local-roots.md +141 -0
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.5.md +196 -0
- package/node_modules/@openclaw/fs-safe/docs/migrating-to-0.6.md +43 -0
- package/node_modules/@openclaw/fs-safe/docs/native-helper.md +118 -0
- package/node_modules/@openclaw/fs-safe/docs/native.md +196 -0
- package/node_modules/@openclaw/fs-safe/docs/output.md +139 -0
- package/node_modules/@openclaw/fs-safe/docs/path-scope.md +159 -0
- package/node_modules/@openclaw/fs-safe/docs/path.md +208 -0
- package/node_modules/@openclaw/fs-safe/docs/permissions.md +200 -0
- package/node_modules/@openclaw/fs-safe/docs/private-file-store.md +54 -0
- package/node_modules/@openclaw/fs-safe/docs/public-api.md +112 -0
- package/node_modules/@openclaw/fs-safe/docs/quickstart.md +154 -0
- package/node_modules/@openclaw/fs-safe/docs/reading.md +183 -0
- package/node_modules/@openclaw/fs-safe/docs/regular-file.md +190 -0
- package/node_modules/@openclaw/fs-safe/docs/root.md +266 -0
- package/node_modules/@openclaw/fs-safe/docs/secret-file.md +273 -0
- package/node_modules/@openclaw/fs-safe/docs/secure-file.md +100 -0
- package/node_modules/@openclaw/fs-safe/docs/security-model.md +188 -0
- package/node_modules/@openclaw/fs-safe/docs/sidecar-lock.md +439 -0
- package/node_modules/@openclaw/fs-safe/docs/staged-file.md +182 -0
- package/node_modules/@openclaw/fs-safe/docs/store.md +94 -0
- package/node_modules/@openclaw/fs-safe/docs/temp.md +476 -0
- package/node_modules/@openclaw/fs-safe/docs/test-hooks.md +96 -0
- package/node_modules/@openclaw/fs-safe/docs/testing.md +189 -0
- package/node_modules/@openclaw/fs-safe/docs/timing.md +121 -0
- package/node_modules/@openclaw/fs-safe/docs/types.md +188 -0
- package/node_modules/@openclaw/fs-safe/docs/walk.md +128 -0
- package/node_modules/@openclaw/fs-safe/docs/writing.md +347 -0
- package/node_modules/@openclaw/fs-safe/package.json +187 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-arm64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-darwin-x64/package.json +29 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-gnu/package.json +32 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-arm64-musl/package.json +32 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-gnu/package.json +32 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-linux-x64-musl/package.json +32 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/fs-safe-native.node +0 -0
- package/node_modules/@openclaw/fs-safe-win32-x64-msvc/package.json +29 -0
- package/node_modules/core-util-is/LICENSE +19 -0
- package/node_modules/core-util-is/README.md +3 -0
- package/node_modules/core-util-is/lib/util.js +107 -0
- package/node_modules/core-util-is/package.json +38 -0
- package/node_modules/immediate/LICENSE.txt +20 -0
- package/node_modules/immediate/README.md +93 -0
- package/node_modules/immediate/dist/immediate.js +75 -0
- package/node_modules/immediate/dist/immediate.min.js +1 -0
- package/node_modules/immediate/lib/browser.js +69 -0
- package/node_modules/immediate/lib/index.js +73 -0
- package/node_modules/immediate/package.json +42 -0
- package/node_modules/inherits/LICENSE +16 -0
- package/node_modules/inherits/README.md +42 -0
- package/node_modules/inherits/inherits.js +9 -0
- package/node_modules/inherits/inherits_browser.js +27 -0
- package/node_modules/inherits/package.json +29 -0
- package/node_modules/isarray/.npmignore +1 -0
- package/node_modules/isarray/.travis.yml +4 -0
- package/node_modules/isarray/Makefile +6 -0
- package/node_modules/isarray/README.md +60 -0
- package/node_modules/isarray/component.json +19 -0
- package/node_modules/isarray/index.js +5 -0
- package/node_modules/isarray/package.json +45 -0
- package/node_modules/isarray/test.js +20 -0
- package/node_modules/jszip/.codeclimate.yml +16 -0
- package/node_modules/jszip/.editorconfig +8 -0
- package/node_modules/jszip/.eslintrc.js +43 -0
- package/node_modules/jszip/.github/workflows/pr.yaml +58 -0
- package/node_modules/jszip/.jekyll-metadata +0 -0
- package/node_modules/jszip/.travis.yml +17 -0
- package/node_modules/jszip/CHANGES.md +204 -0
- package/node_modules/jszip/LICENSE.markdown +651 -0
- package/node_modules/jszip/README.markdown +33 -0
- package/node_modules/jszip/deps.js +37 -0
- package/node_modules/jszip/dist/jszip.js +11577 -0
- package/node_modules/jszip/dist/jszip.min.js +13 -0
- package/node_modules/jszip/graph.svg +601 -0
- package/node_modules/jszip/index.d.ts +330 -0
- package/node_modules/jszip/lib/base64.js +106 -0
- package/node_modules/jszip/lib/compressedObject.js +74 -0
- package/node_modules/jszip/lib/compressions.js +14 -0
- package/node_modules/jszip/lib/crc32.js +77 -0
- package/node_modules/jszip/lib/defaults.js +11 -0
- package/node_modules/jszip/lib/external.js +18 -0
- package/node_modules/jszip/lib/flate.js +85 -0
- package/node_modules/jszip/lib/generate/ZipFileWorker.js +539 -0
- package/node_modules/jszip/lib/generate/index.js +57 -0
- package/node_modules/jszip/lib/index.js +55 -0
- package/node_modules/jszip/lib/license_header.js +11 -0
- package/node_modules/jszip/lib/load.js +88 -0
- package/node_modules/jszip/lib/nodejs/NodejsStreamInputAdapter.js +74 -0
- package/node_modules/jszip/lib/nodejs/NodejsStreamOutputAdapter.js +42 -0
- package/node_modules/jszip/lib/nodejsUtils.js +57 -0
- package/node_modules/jszip/lib/object.js +384 -0
- package/node_modules/jszip/lib/readable-stream-browser.js +10 -0
- package/node_modules/jszip/lib/reader/ArrayReader.js +57 -0
- package/node_modules/jszip/lib/reader/DataReader.js +116 -0
- package/node_modules/jszip/lib/reader/NodeBufferReader.js +19 -0
- package/node_modules/jszip/lib/reader/StringReader.js +38 -0
- package/node_modules/jszip/lib/reader/Uint8ArrayReader.js +22 -0
- package/node_modules/jszip/lib/reader/readerFor.js +28 -0
- package/node_modules/jszip/lib/signature.js +7 -0
- package/node_modules/jszip/lib/stream/ConvertWorker.js +26 -0
- package/node_modules/jszip/lib/stream/Crc32Probe.js +24 -0
- package/node_modules/jszip/lib/stream/DataLengthProbe.js +29 -0
- package/node_modules/jszip/lib/stream/DataWorker.js +116 -0
- package/node_modules/jszip/lib/stream/GenericWorker.js +263 -0
- package/node_modules/jszip/lib/stream/StreamHelper.js +214 -0
- package/node_modules/jszip/lib/support.js +38 -0
- package/node_modules/jszip/lib/utf8.js +275 -0
- package/node_modules/jszip/lib/utils.js +501 -0
- package/node_modules/jszip/lib/zipEntries.js +261 -0
- package/node_modules/jszip/lib/zipEntry.js +293 -0
- package/node_modules/jszip/lib/zipObject.js +133 -0
- package/node_modules/jszip/package.json +67 -0
- package/node_modules/jszip/sponsors.md +21 -0
- package/node_modules/jszip/tsconfig.json +101 -0
- package/node_modules/jszip/vendor/FileSaver.js +247 -0
- package/node_modules/lie/README.md +62 -0
- package/node_modules/lie/dist/lie.js +350 -0
- package/node_modules/lie/dist/lie.min.js +1 -0
- package/node_modules/lie/dist/lie.polyfill.js +358 -0
- package/node_modules/lie/dist/lie.polyfill.min.js +1 -0
- package/node_modules/lie/lib/browser.js +273 -0
- package/node_modules/lie/lib/index.js +298 -0
- package/node_modules/lie/license.md +7 -0
- package/node_modules/lie/lie.d.ts +244 -0
- package/node_modules/lie/package.json +69 -0
- package/node_modules/lie/polyfill.js +4 -0
- package/node_modules/pako/CHANGELOG.md +164 -0
- package/node_modules/pako/LICENSE +21 -0
- package/node_modules/pako/README.md +191 -0
- package/node_modules/pako/dist/pako.js +6818 -0
- package/node_modules/pako/dist/pako.min.js +1 -0
- package/node_modules/pako/dist/pako_deflate.js +3997 -0
- package/node_modules/pako/dist/pako_deflate.min.js +1 -0
- package/node_modules/pako/dist/pako_inflate.js +3300 -0
- package/node_modules/pako/dist/pako_inflate.min.js +1 -0
- package/node_modules/pako/index.js +14 -0
- package/node_modules/pako/lib/deflate.js +400 -0
- package/node_modules/pako/lib/inflate.js +423 -0
- package/node_modules/pako/lib/utils/common.js +105 -0
- package/node_modules/pako/lib/utils/strings.js +187 -0
- package/node_modules/pako/lib/zlib/README +59 -0
- package/node_modules/pako/lib/zlib/adler32.js +51 -0
- package/node_modules/pako/lib/zlib/constants.js +68 -0
- package/node_modules/pako/lib/zlib/crc32.js +59 -0
- package/node_modules/pako/lib/zlib/deflate.js +1874 -0
- package/node_modules/pako/lib/zlib/gzheader.js +58 -0
- package/node_modules/pako/lib/zlib/inffast.js +345 -0
- package/node_modules/pako/lib/zlib/inflate.js +1556 -0
- package/node_modules/pako/lib/zlib/inftrees.js +343 -0
- package/node_modules/pako/lib/zlib/messages.js +32 -0
- package/node_modules/pako/lib/zlib/trees.js +1222 -0
- package/node_modules/pako/lib/zlib/zstream.js +47 -0
- package/node_modules/pako/package.json +44 -0
- package/node_modules/process-nextick-args/index.js +45 -0
- package/node_modules/process-nextick-args/license.md +19 -0
- package/node_modules/process-nextick-args/package.json +25 -0
- package/node_modules/process-nextick-args/readme.md +18 -0
- package/node_modules/readable-stream/.travis.yml +34 -0
- package/node_modules/readable-stream/CONTRIBUTING.md +38 -0
- package/node_modules/readable-stream/GOVERNANCE.md +136 -0
- package/node_modules/readable-stream/LICENSE +47 -0
- package/node_modules/readable-stream/README.md +58 -0
- package/node_modules/readable-stream/doc/wg-meetings/2015-01-30.md +60 -0
- package/node_modules/readable-stream/duplex-browser.js +1 -0
- package/node_modules/readable-stream/duplex.js +1 -0
- package/node_modules/readable-stream/lib/_stream_duplex.js +131 -0
- package/node_modules/readable-stream/lib/_stream_passthrough.js +47 -0
- package/node_modules/readable-stream/lib/_stream_readable.js +1019 -0
- package/node_modules/readable-stream/lib/_stream_transform.js +214 -0
- package/node_modules/readable-stream/lib/_stream_writable.js +685 -0
- package/node_modules/readable-stream/lib/internal/streams/BufferList.js +78 -0
- package/node_modules/readable-stream/lib/internal/streams/destroy.js +84 -0
- package/node_modules/readable-stream/lib/internal/streams/stream-browser.js +1 -0
- package/node_modules/readable-stream/lib/internal/streams/stream.js +1 -0
- package/node_modules/readable-stream/package.json +52 -0
- package/node_modules/readable-stream/passthrough.js +1 -0
- package/node_modules/readable-stream/readable-browser.js +7 -0
- package/node_modules/readable-stream/readable.js +19 -0
- package/node_modules/readable-stream/transform.js +1 -0
- package/node_modules/readable-stream/writable-browser.js +1 -0
- package/node_modules/readable-stream/writable.js +8 -0
- package/node_modules/safe-buffer/LICENSE +21 -0
- package/node_modules/safe-buffer/README.md +584 -0
- package/node_modules/safe-buffer/index.d.ts +187 -0
- package/node_modules/safe-buffer/index.js +62 -0
- package/node_modules/safe-buffer/package.json +37 -0
- package/node_modules/setimmediate/LICENSE.txt +20 -0
- package/node_modules/setimmediate/package.json +30 -0
- package/node_modules/setimmediate/setImmediate.js +186 -0
- package/node_modules/string_decoder/.travis.yml +50 -0
- package/node_modules/string_decoder/LICENSE +48 -0
- package/node_modules/string_decoder/README.md +47 -0
- package/node_modules/string_decoder/lib/string_decoder.js +296 -0
- package/node_modules/string_decoder/package.json +31 -0
- package/node_modules/util-deprecate/History.md +16 -0
- package/node_modules/util-deprecate/LICENSE +24 -0
- package/node_modules/util-deprecate/README.md +53 -0
- package/node_modules/util-deprecate/browser.js +67 -0
- package/node_modules/util-deprecate/node.js +6 -0
- package/node_modules/util-deprecate/package.json +27 -0
- package/openclaw.plugin.json +1 -0
- package/package.json +7 -4
- /package/node_modules/{p-limit/node_modules/yocto-queue → yocto-queue}/index.d.ts +0 -0
- /package/node_modules/{p-limit/node_modules/yocto-queue → yocto-queue}/index.js +0 -0
- /package/node_modules/{p-limit/node_modules/yocto-queue → yocto-queue}/license +0 -0
- /package/node_modules/{p-limit/node_modules/yocto-queue → yocto-queue}/package.json +0 -0
- /package/node_modules/{p-limit/node_modules/yocto-queue → yocto-queue}/readme.md +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Security model
|
|
2
|
+
|
|
3
|
+
`fs-safe` is a library-level guardrail: a capability-style root handle for Node.js code that handles untrusted relative paths. It assumes the calling process already has whatever filesystem permissions it needs and aims to stop trivial path tricks from broadening that authority. It is not a sandbox and does not replace operating-system isolation.
|
|
4
|
+
|
|
5
|
+
The same shape exists in other languages: Go's [`os.Root` / `OpenInRoot`](https://go.dev/blog/osroot) and Rust's [`cap-std`](https://github.com/bytecodealliance/cap-std) both expose a root handle whose operations refuse to escape it. `fs-safe` is the Node-side equivalent: a single `root()` capability that carries the boundary across every read, write, move, and remove, instead of leaving each call site to redo `path.resolve(...).startsWith(...)` and hope.
|
|
6
|
+
|
|
7
|
+
## Affected versions / exposure
|
|
8
|
+
|
|
9
|
+
In published releases through 0.4.7, the exported `resolveRootPath()` and
|
|
10
|
+
`resolveRootPathSync()` helpers validated a lexically normalized path spelling.
|
|
11
|
+
A caller-supplied path traversing an in-root symlink could therefore pass
|
|
12
|
+
validation while resolving outside the root. Version 0.5 fixes this with
|
|
13
|
+
component-wise alias resolution, resolving each alias before applying later
|
|
14
|
+
path components.
|
|
15
|
+
|
|
16
|
+
`root()` handles were **not** affected: their operations have contained this
|
|
17
|
+
case since `5ddca80`. Exposure is limited to consumers that call
|
|
18
|
+
`resolveRootPath()` or `resolveRootPathSync()` directly.
|
|
19
|
+
|
|
20
|
+
## Threat model
|
|
21
|
+
|
|
22
|
+
You hand a `root()` boundary to a piece of code that takes caller-controlled relative paths. The library defends against a caller that:
|
|
23
|
+
|
|
24
|
+
- supplies `..` traversal segments to escape the boundary
|
|
25
|
+
- supplies an absolute path outside a configured root, or to an API whose input
|
|
26
|
+
contract is strictly relative/portable
|
|
27
|
+
- replaces a path component with a symlink between check and use (TOCTOU)
|
|
28
|
+
- replaces the destination directory with a symlink right before a write
|
|
29
|
+
- creates a hardlink that aliases an out-of-tree inode and asks you to read or replace it
|
|
30
|
+
- asks a read/open primitive to target a known unsafe device or process-fd path
|
|
31
|
+
- triggers a partial write that leaves a half-written file at the destination
|
|
32
|
+
- ships an archive with `..` paths, absolute paths, or symlinks pointing outside the destination
|
|
33
|
+
|
|
34
|
+
It does **not** defend against:
|
|
35
|
+
|
|
36
|
+
- a process running with permissions to write anywhere on the filesystem and choosing to ignore the library
|
|
37
|
+
- another process with the same UID racing to mutate the same directory between two separate `fs-safe` calls — the boundary is per-call, not per-session
|
|
38
|
+
- arbitrary traversal across filesystem boundaries, bind mounts, or virtual filesystems beyond the known unsafe read device paths
|
|
39
|
+
- container escape, TOCTOU between fork and exec of helpers, or kernel-level vulnerabilities
|
|
40
|
+
- semantic content checks: file types, archive payload schemas, signature verification
|
|
41
|
+
|
|
42
|
+
If you need full sandboxing, run the worker under reduced privileges (uid, container, seccomp, chroot, jail) and use `fs-safe` inside the sandbox to keep the worker honest about its own workspace.
|
|
43
|
+
|
|
44
|
+
## Defenses, by failure mode
|
|
45
|
+
|
|
46
|
+
### Path traversal and absolute paths
|
|
47
|
+
|
|
48
|
+
Every path is resolved against the canonicalized real path of the root, then checked with `isPathInside`. Alias resolution walks components before applying a later `..`, so a symlink cannot change what that parent segment means after validation. Parent traversal, an absolute spelling, or any alias whose canonical result is outside the root throws `outside-workspace`; absolute spellings that remain inside the root are accepted.
|
|
49
|
+
|
|
50
|
+
### Symlinks (read side)
|
|
51
|
+
|
|
52
|
+
`open()` and `read()` use `fs.open` with `O_NOFOLLOW` on POSIX where available. The library then `fstat`s the open fd and `realpath`s the original input, asserting the two refer to the same inode. A symlink swap that happens between resolve and open will fail at the identity check rather than silently following the new target.
|
|
53
|
+
|
|
54
|
+
Per-call `symlinks: "follow-within-root"` allows symlinks whose final target is still inside the root. The default is `"reject"`.
|
|
55
|
+
|
|
56
|
+
Guarded root reads compare lossless bigint identities from before open, the opened
|
|
57
|
+
descriptor, the input path, and the canonical target; numeric public `Stats`
|
|
58
|
+
receipts are not used as identity evidence. Unknown Windows device/inode values
|
|
59
|
+
receive one re-inspection without reopening the file. A definite mismatch or
|
|
60
|
+
persistent unknown identity rejects with `path-mismatch` before reading bytes.
|
|
61
|
+
Regular-file readers, root-file adapters, and archive input staging use the same
|
|
62
|
+
exact admission policy. `copyIn()` retains the admitted source identity for its
|
|
63
|
+
checks before and after copying, independently of its numeric metadata receipt.
|
|
64
|
+
|
|
65
|
+
### Symlinks (write side)
|
|
66
|
+
|
|
67
|
+
With the native binding loaded, `write()`, `create()`, and `copyIn()` use a
|
|
68
|
+
sibling temp file and create parents and publish the target relative to pinned
|
|
69
|
+
directory descriptors. Replacement uses descriptor-relative rename just like
|
|
70
|
+
no-replace publication, so replacing the parent pathname does not divert the
|
|
71
|
+
mutation.
|
|
72
|
+
|
|
73
|
+
The JavaScript fallback used by `off`, by `auto` when no binding loads, and by
|
|
74
|
+
the explicit `renameIdentity: "verify-content-with-lock"` compatibility policy
|
|
75
|
+
cannot provide that guarantee because Node exposes no `mkdirat` or `renameat`.
|
|
76
|
+
It asserts directory identity around a pathname mutation and detects many
|
|
77
|
+
swaps, but detection occurs after the kernel may already have followed a new
|
|
78
|
+
parent symlink. A same-privilege peer with write access to the parent can
|
|
79
|
+
therefore cause an out-of-root side effect before the operation throws. Use
|
|
80
|
+
native `require` mode when concurrent hostile mutation is in scope.
|
|
81
|
+
|
|
82
|
+
The separate [retained-directory staging lifecycle](staged-file.md) keeps abort
|
|
83
|
+
cleanup anchored to the original directory after a parent or ancestor move.
|
|
84
|
+
It preserves observed substituted temporary entries and never cleans a recorded
|
|
85
|
+
publication. Directory anchoring is not expected-inode/CAS replacement, and
|
|
86
|
+
identity-check-then-unlink is not atomic conditional unlink. The guarantee
|
|
87
|
+
requires the temporary name to remain owned and removal to remain permitted;
|
|
88
|
+
application authorization and cooperative coordination remain necessary.
|
|
89
|
+
|
|
90
|
+
### Hardlink aliasing
|
|
91
|
+
|
|
92
|
+
When `hardlinks: "reject"` is set, reads stat the target and refuse if `nlink > 1`, on the conservative assumption that a hardlinked file might alias an out-of-tree inode. This is defense-in-depth: the link count check is best-effort and platform-dependent. Treat it as a tripwire, not authorization.
|
|
93
|
+
|
|
94
|
+
### TOCTOU between resolve and use
|
|
95
|
+
|
|
96
|
+
`resolve()`, `exists()`, `stat()`, and `list()` are explicitly advisory — they answer a question and return. To act on a path with operation-local identity checks, use `read()`, `open()`, `write()`, `create()`, `copyIn()`, `move()`, or `remove()`. The containment table below states which opens are kernel-atomic and which remain best-effort.
|
|
97
|
+
|
|
98
|
+
A `root()` handle also remembers the canonical root directory identity. Calls fail with `path-mismatch` if that canonical pathname is replaced, including advisory inspection and walking calls, rather than following a replacement root into another tree.
|
|
99
|
+
|
|
100
|
+
### Denied mutations
|
|
101
|
+
|
|
102
|
+
`denyMutations` is an opt-in application policy for `root()` mutation methods. It blocks exact absolute paths with `paths` and whole subtrees with `prefixes`, merging root defaults with per-call entries so a call cannot clear root-level denies. This is not an OS permission boundary: code with access to `node:fs`, a shell, or another process with the same filesystem privileges can bypass it.
|
|
103
|
+
|
|
104
|
+
### Atomic writes
|
|
105
|
+
|
|
106
|
+
`replaceFileAtomic` writes to a sibling temp file in the destination directory, optionally `fsync`s it, optionally `fsync`s the parent directory after rename, and atomically renames over the destination. On failure mid-write, the destination is either the old contents (rename never happened) or the new contents (rename succeeded). There is no half-written intermediate state visible at the destination path unless the caller explicitly enables `copyFallbackOnPermissionError`, whose default `copyFallbackRestore: "none"` contract may leave a partial destination after a failed in-place fallback.
|
|
107
|
+
|
|
108
|
+
Within one process, async writes to the same target are queued so their temp-write/rename phases do not overlap. Cross-process writers still need an external protocol such as the sidecar lock helpers.
|
|
109
|
+
|
|
110
|
+
### Directory durability
|
|
111
|
+
|
|
112
|
+
`pinDirectory()` opens a directory without following its final component on
|
|
113
|
+
POSIX, verifies the descriptor against the pathname identity and canonical
|
|
114
|
+
path, and repeats those checks around synchronization. `ensureDurableDirectory()`
|
|
115
|
+
pins the nearest existing ancestor and each newly created segment before
|
|
116
|
+
synchronizing every new directory edge from the leaf upward.
|
|
117
|
+
|
|
118
|
+
Known Windows directory-flush limitations are returned as an explicit
|
|
119
|
+
`unsupported` outcome. POSIX and other I/O failures propagate from the strict
|
|
120
|
+
API. The separately named best-effort helpers intentionally provide no crash
|
|
121
|
+
durability guarantee.
|
|
122
|
+
|
|
123
|
+
### Archive extraction
|
|
124
|
+
|
|
125
|
+
`extractArchive` first stages into a private temp directory (mode 0700) outside the destination, validates each entry path against `..` and absolute prefixes, refuses link-type entries by default, enforces entry count and byte budgets, and only then merges the staged tree into the destination through the same boundary checks used by direct writes.
|
|
126
|
+
|
|
127
|
+
TAR extraction and bounded reads validate the complete decompressed raw framing
|
|
128
|
+
before parser normalization: directory/link sizes must be zero, EOF must contain
|
|
129
|
+
two consecutive zero blocks, and only zero padding may follow EOF. This rejects
|
|
130
|
+
hidden headers and body/header smuggling consistently across the JavaScript and
|
|
131
|
+
native backends. The meter checks logical member count before consuming bodies,
|
|
132
|
+
even for unrequested or filtered members. Per-entry/cumulative payload budgets
|
|
133
|
+
apply only to entries accepted after strip/filter policy, outside the raw meter.
|
|
134
|
+
A separate absolute decoded cap includes every member body, headers, metadata,
|
|
135
|
+
all padding, and EOF, closing cumulative-metadata and zero-tail bypasses. Native
|
|
136
|
+
extraction/read passes drain through physical EOF before publication or
|
|
137
|
+
returning selected bytes, even when the TAR parser stops earlier. Bounded
|
|
138
|
+
reads use default archive admission limits and keep `maxBytes` scoped to the
|
|
139
|
+
requested output. Metadata retains its per-record bound. See [archive framing](archive.md#raw-tar-framing) for supported
|
|
140
|
+
metadata and compression formats.
|
|
141
|
+
|
|
142
|
+
## What "library-level" means
|
|
143
|
+
|
|
144
|
+
A library cannot revoke its own caller's authority. If your code chooses to bypass `fs-safe` and call `fs.writeFile` directly with the same path, you bypass the defenses too. The contract `fs-safe` enforces is: *every filesystem operation that touches caller-controlled input goes through the boundary*. That contract is yours to keep.
|
|
145
|
+
|
|
146
|
+
The library does not modify or constrain the global Node.js `fs` namespace, and it does not patch the runtime. Other code in the same process retains its normal filesystem authority.
|
|
147
|
+
|
|
148
|
+
## Containment guarantees by platform
|
|
149
|
+
|
|
150
|
+
`openBeneath()` and JavaScript open results report one of two factual containment classes:
|
|
151
|
+
|
|
152
|
+
| Mechanism | Reported containment | Boundary |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| Linux native | `kernel-atomic` | `openat2(RESOLVE_BENEATH | RESOLVE_NO_MAGICLINKS)` resolves and opens under the root in one kernel operation. |
|
|
155
|
+
| macOS native | `best-effort` | macOS 15.4 and newer use `O_RESOLVE_BENEATH` first; older kernels use the guarded `openat(O_NOFOLLOW)` component walk. Both verify the opened descriptor with `F_GETPATH`. |
|
|
156
|
+
| Windows native | `best-effort` | Handle-relative `NtCreateFile` rejects reparse points, but this package does not claim a Linux-style atomic beneath guarantee. |
|
|
157
|
+
| JavaScript fallback | `best-effort` | Canonical checks, no-follow opens where Node exposes them, and post-open identity checks form a check-then-use sequence. |
|
|
158
|
+
|
|
159
|
+
The macOS `F_GETPATH` verification is an escape detector, not a race-atomic guarantee. A hostile same-UID process can rename a directory after `O_RESOLVE_BENEATH` or the manual walk and race the post-open sample or a later descriptor-relative mutation. The native result therefore remains `best-effort` on macOS even when the kernel flag is available. No policy decision is attached to these labels; callers can inspect the fact and decide what their own threat model requires.
|
|
160
|
+
|
|
161
|
+
The public `OpenResult`, `ReadResult`, and `WritableOpenResult` expose `containment`. Those root APIs currently report `best-effort`; direct native `openBeneath()` reports the platform value above. No-replace publication uses `renameat2(RENAME_NOREPLACE)` on Linux, `renameatx_np(RENAME_EXCL)` on macOS, and `FileRenameInfoEx` with replacement disabled on Windows, but those separate mutation semantics do not upgrade an open result's containment label.
|
|
162
|
+
|
|
163
|
+
## Limitations to keep in mind
|
|
164
|
+
|
|
165
|
+
| Limitation | What it means |
|
|
166
|
+
|---|---|
|
|
167
|
+
| Not ambient authority removal | Code that can import `node:fs` can still bypass the handle. Keep caller-controlled path operations behind `root()` by convention, review, and tests. |
|
|
168
|
+
| Absolute paths are escape hatches | APIs that accept or return absolute paths exist for audit, ingest, and advanced composition. Prefer root-relative names in normal application flow. |
|
|
169
|
+
| Not a mount boundary | `root()` keeps path traversal inside the directory tree and blocks known unsafe read device paths, but it does not make bind mounts or virtual filesystems safe to expose wholesale. |
|
|
170
|
+
| Per-call, not per-session | Another process with the same privileges can still mutate the tree between calls, and best-effort mechanisms retain documented same-call race windows. Use one verb method to minimize the window and inspect its reported containment class. |
|
|
171
|
+
| JavaScript mutations are detection-based | Without the native binding, Node pathname mutations retain a check-to-syscall race. A writable parent can be swapped so a create, rename, or removal affects an out-of-root path before the fallback detects identity drift. |
|
|
172
|
+
| Hardlink rejection is best-effort | Link-count checks depend on platform metadata. Treat `hardlinks: "reject"` as a tripwire, not an authorization primitive. |
|
|
173
|
+
| Mode bits are not a full policy engine | `replaceFileAtomic` and secret-file helpers set requested modes, but you should still set umask and inspect modes when policy requires it. |
|
|
174
|
+
| Archive extraction is path safety, not content safety | Unsafe entry paths and links are rejected; malicious payload contents remain your application layer's problem. |
|
|
175
|
+
| Native package unavailable | `helper-unavailable` falls back in `auto` mode and fails closed in `require` mode for native-backed operations. Guarded JavaScript atomicity and identity checks remain. |
|
|
176
|
+
| FUSE mounts with rename-unstable inode numbers | Some FUSE mounts (rclone is a confirmed example) do not preserve source inode identity at the rename destination. The explicit `renameIdentity: "verify-content-with-lock"` compatibility mode verifies content under a cooperative lock for that boundary only; subsequent path identity checks and the default remain strict. See [Writing](writing.md) for the weaker opt-in contract. |
|
|
177
|
+
|
|
178
|
+
## Recommended deployment shape
|
|
179
|
+
|
|
180
|
+
- Run worker code under a dedicated UID with the smallest filesystem privileges that still allow the worker to do its job.
|
|
181
|
+
- Mount the workspace directory writable; mount everything else read-only or not at all.
|
|
182
|
+
- Use `fs-safe`'s `root()` for that workspace.
|
|
183
|
+
- For credentials, use [secret files](secret-file.md) (mode 0600 in mode-0700 dirs) rather than the workspace.
|
|
184
|
+
- For scratch space, use a [private temp workspace](temp.md) — don't reuse the workspace root.
|
|
185
|
+
|
|
186
|
+
## Reporting issues
|
|
187
|
+
|
|
188
|
+
Suspected security issues belong in private disclosure first. See [`SECURITY.md`](https://github.com/openclaw/fs-safe/blob/main/SECURITY.md) in the repo for the current contact path.
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# File lock
|
|
2
|
+
|
|
3
|
+
`acquireFileLock()` and `withFileLock()` provide a cross-process file lock with retry and process-exit cleanup. The lock is implemented as a sidecar file (e.g. `state.json` ↔ `state.json.lock`) — only one acquirer can create the sidecar with `O_CREAT | O_EXCL` at a time.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { acquireFileLock } from "@openclaw/fs-safe/file-lock";
|
|
7
|
+
|
|
8
|
+
const handle = await acquireFileLock("/var/lib/app/state.json", {
|
|
9
|
+
managerKey: "snapshot",
|
|
10
|
+
staleMs: 5 * 60_000,
|
|
11
|
+
payload: async () => ({ pid: process.pid, host: os.hostname() }),
|
|
12
|
+
});
|
|
13
|
+
try {
|
|
14
|
+
// ...exclusive work on /var/lib/app/state.json...
|
|
15
|
+
} finally {
|
|
16
|
+
await handle.release();
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Why sidecar?
|
|
21
|
+
|
|
22
|
+
The lock file sits next to the protected resource. If a process crashes mid-lock, the next acquirer notices the held entry, inspects its payload (PID, host, acquired-at timestamp), and decides — via `shouldReclaim` (defaulting to "is the lock older than `staleMs`?") — whether it should keep waiting or fail.
|
|
23
|
+
|
|
24
|
+
On natural event-loop shutdown, a globally deduplicated `process.on("beforeExit")` handler attempts asynchronous cleanup of held Root-backed locks through their retained Root capability and ownership receipt. The synchronous `process.on("exit")` handler provides last-chance cleanup for raw locks and reclaim guards. Changed sidecars and failed Root cleanup remain in place; cleanup does not keep retrying during shutdown unless another acquisition re-arms it. Locks acquired with `retainOnExit: true` are exempt from both handlers: their sidecar stays in place after exit and is governed only by the caller's own stale policy. Because exit handlers are globally deduplicated across package copies, `retainOnExit` fails closed with `helper-unavailable` if an older copy that cannot honor it registered the handlers first.
|
|
25
|
+
|
|
26
|
+
Always release locks in a `finally` block. Application-managed graceful shutdown can await `release()` or `manager.drain()` before terminating. Explicit `process.exit()`, uncaught failures, crashes, default signal handling, and fatal termination (including `SIGKILL`) do not reliably run asynchronous Root cleanup and may leave sidecars. Recover only after an application-owned liveness policy proves the holder cannot still be writing.
|
|
27
|
+
|
|
28
|
+
Each new sidecar also carries an internal random ownership token encoded as JSON trailing whitespace. `JSON.parse()` and every payload callback still see exactly the caller-provided object. Only the process that successfully created the sidecar keeps that token as release authority; merely reading token-shaped bytes from disk does not enable this mode. Release compares the in-memory token and exact serialized bytes, and requires the pathname to remain a regular file, instead of requiring an opened descriptor and pathname lookup to report the same inode identity. This preserves ownership checks on filesystems such as Docker Desktop VirtioFS where those two views can legitimately differ. Sidecars created by older releases have no token and retain the legacy identity-plus-content check.
|
|
29
|
+
|
|
30
|
+
The raw sidecar bytes are not a canonical JSON representation: tools that trim or rewrite the trailing whitespace invalidate the ownership token, so release leaves the changed sidecar in place and fails closed. The token distinguishes cooperating acquisitions; it is not a secret and does not make pathname compare-and-remove atomic against a hostile process that can replace files outside the lock protocol.
|
|
31
|
+
|
|
32
|
+
`release()` propagates an I/O failure that prevents deletion of an unchanged, owned sidecar; it never reports successful cleanup while leaving that lock behind. The handle and manager retain the exact cleanup receipt after a failure, so the same handle can retry `release()` and `manager.drain()` can retry retained cleanup. A changed sidecar remains an ownership mismatch rather than a deletion failure and is left untouched. If both a `withFileLock()` callback and release fail, the release error is the primary `SuppressedError.error` and the callback failure remains available as `SuppressedError.suppressed`. Failed acquisition cleanup uses the same shape, with the cleanup error primary and the acquisition failure suppressed. On Node runtimes without the global `SuppressedError` constructor, fs-safe returns the equivalent `Error` shape with the same name and properties.
|
|
33
|
+
|
|
34
|
+
## API
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
function acquireFileLock<TPayload>(
|
|
38
|
+
targetPath: string,
|
|
39
|
+
options: FileLockAcquireOptions<TPayload>,
|
|
40
|
+
): Promise<FileLockHandle>;
|
|
41
|
+
|
|
42
|
+
function withFileLock<T, TPayload>(
|
|
43
|
+
targetPath: string,
|
|
44
|
+
options: FileLockAcquireOptions<TPayload>,
|
|
45
|
+
fn: () => Promise<T>,
|
|
46
|
+
): Promise<T>;
|
|
47
|
+
|
|
48
|
+
function createFileLockManager(key: string): FileLockManager;
|
|
49
|
+
|
|
50
|
+
function acquireFileLockSync<TPayload>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>): FileLockSyncHandle;
|
|
51
|
+
function withFileLockSync<T, TPayload>(targetPath: string, options: FileLockSyncAcquireOptions<TPayload>, fn: () => T): T;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`managerKey` is an optional identifier used to keep state isolated across multiple lock domains in the same process. Use distinct keys for distinct domains (`"snapshot"`, `"compact"`, `"build"`). If omitted, fs-safe derives one from the target path.
|
|
55
|
+
|
|
56
|
+
## Acquire options
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
type FileLockAcquireOptions<TPayload extends Record<string, unknown>> = {
|
|
60
|
+
managerKey?: string; // optional in-process manager namespace
|
|
61
|
+
lockPath?: string; // override; defaults to `${targetPath}.lock`
|
|
62
|
+
staleMs?: number; // default 30_000
|
|
63
|
+
timeoutMs?: number; // overall acquire deadline; default unbounded
|
|
64
|
+
retry?: FileLockRetryOptions;
|
|
65
|
+
staleRecovery?: "fail-closed" | "remove-if-unchanged"; // default "fail-closed"
|
|
66
|
+
reentrantOwner?: string; // logical holder identity for owner-scoped nesting
|
|
67
|
+
payload: () => TPayload | Promise<TPayload>;
|
|
68
|
+
shouldReclaim?: (params: {
|
|
69
|
+
lockPath: string;
|
|
70
|
+
normalizedTargetPath: string;
|
|
71
|
+
payload: Record<string, unknown> | null;
|
|
72
|
+
staleMs: number;
|
|
73
|
+
nowMs: number;
|
|
74
|
+
heldByThisProcess: boolean;
|
|
75
|
+
}) => boolean | Promise<boolean>;
|
|
76
|
+
shouldRemoveStaleLock?: (snapshot: {
|
|
77
|
+
lockPath: string;
|
|
78
|
+
normalizedTargetPath: string;
|
|
79
|
+
raw: string;
|
|
80
|
+
payload: Record<string, unknown> | null;
|
|
81
|
+
}) => boolean | Promise<boolean>;
|
|
82
|
+
metadata?: Record<string, unknown>; // attached to heldEntries() output for diagnostics
|
|
83
|
+
parsePayload?: (raw: string) => unknown;
|
|
84
|
+
lockRoot?: Root;
|
|
85
|
+
retainOnExit?: boolean; // keep the sidecar across process exit (default false)
|
|
86
|
+
onCompromised?: (info: { lockPath: string; normalizedTargetPath: string }) => void;
|
|
87
|
+
compromiseCheckIntervalMs?: number;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type FileLockRetryOptions = {
|
|
91
|
+
retries?: number; // number of retry attempts after the first failure
|
|
92
|
+
factor?: number; // exponential backoff factor (default 1: constant delay)
|
|
93
|
+
minTimeout?: number; // initial delay (ms)
|
|
94
|
+
maxTimeout?: number; // delay cap (ms)
|
|
95
|
+
randomize?: boolean; // jitter
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`payload` is a function so you can re-evaluate it on each retry (e.g. timestamp, PID).
|
|
100
|
+
|
|
101
|
+
The complete serialized sidecar must fit within 1 MiB (1,048,576 UTF-8 bytes),
|
|
102
|
+
including pretty-printed JSON, newlines, and the internal ownership token's
|
|
103
|
+
trailing whitespace. The limit counts bytes, not string characters. Oversized
|
|
104
|
+
payloads reject with `FsSafeError` code `too-large` before sidecar creation or
|
|
105
|
+
acquisition, without retrying serialization or reclaiming an existing sidecar.
|
|
106
|
+
This bound applies to raw and Root-backed locks, both async and sync.
|
|
107
|
+
|
|
108
|
+
Errors thrown by `payload`, its JSON serialization (including `toJSON`), or
|
|
109
|
+
`parsePayload` propagate unchanged without retrying the callback. Rethrowing an
|
|
110
|
+
error saved from an earlier filesystem operation does not grant retry authority.
|
|
111
|
+
Retry counts must be non-negative safe integers. Retry factors and delays must be finite and non-negative, and when both delay bounds are provided `minTimeout` cannot exceed `maxTimeout`. `timeoutMs` accepts a finite non-negative deadline or positive infinity for no deadline; invalid numeric values reject before filesystem acquisition starts.
|
|
112
|
+
Both async and sync locks enforce retry counts and deadlines independently: an
|
|
113
|
+
explicit `retry.retries` still applies with `timeoutMs: Infinity`, and zero allows
|
|
114
|
+
only the initial attempt. After process defaults are applied, an omitted retry
|
|
115
|
+
count means unlimited retries, and an omitted or infinite timeout means no
|
|
116
|
+
deadline. With neither budget bounded, contention can wait indefinitely.
|
|
117
|
+
`parsePayload` replaces JSON parsing for legacy or custom sidecars. Its `unknown`
|
|
118
|
+
result is passed to `shouldReclaim` and `shouldRemoveStaleLock`, allowing PID,
|
|
119
|
+
process-start, argv, or role schemas to remain application-owned.
|
|
120
|
+
|
|
121
|
+
On Windows, a pathed `EPERM` from creating or opening the lock file can be a
|
|
122
|
+
short teardown race after another holder unlinks it. Both async and sync locks
|
|
123
|
+
retry that specific open denial at most eight times per acquisition, within the
|
|
124
|
+
caller's retry/deadline budget. A parent-directory denial, a callback/read/stat
|
|
125
|
+
failure, or exhaustion of either budget surfaces the original error; a denied
|
|
126
|
+
open is not converted to `file_lock_timeout`. Root-backed async creation uses
|
|
127
|
+
this same policy for the Windows fallback's exclusive-open denial, captured
|
|
128
|
+
within that individual create call. A generic `Root.create()` error or an error
|
|
129
|
+
replayed from an earlier call is not exclusive-open evidence. Retrying always
|
|
130
|
+
requires fresh exclusive creation and grants no ownership or removal authority.
|
|
131
|
+
|
|
132
|
+
## Owner-scoped reentrancy
|
|
133
|
+
|
|
134
|
+
Version 0.5 removes the unsound process-scoped `allowReentrant` boolean and
|
|
135
|
+
replaces it with `reentrantOwner`. When a manager already holds the canonical
|
|
136
|
+
target path, another acquisition reuses that sidecar only when both acquisitions
|
|
137
|
+
provide the same owner string. Each acquisition gets an idempotent release
|
|
138
|
+
handle; the sidecar remains until the last reference is released. A different or
|
|
139
|
+
missing owner waits under the normal contention, retry, and timeout policy. A
|
|
140
|
+
known live in-process holder is never stale-reclaimed by its own manager.
|
|
141
|
+
|
|
142
|
+
This supports logical session writers that may reach one file through real and
|
|
143
|
+
symlinked parent paths:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const managerKey = "session-write-locks";
|
|
147
|
+
const reentrantOwner = `session:${sessionId}:operation:${operationId}`;
|
|
148
|
+
|
|
149
|
+
const outer = await acquireFileLock(realSessionPath, {
|
|
150
|
+
managerKey,
|
|
151
|
+
reentrantOwner,
|
|
152
|
+
staleMs: 60_000,
|
|
153
|
+
payload: () => ({ pid: process.pid, operationId }),
|
|
154
|
+
});
|
|
155
|
+
const nested = await acquireFileLock(symlinkedSessionPath, {
|
|
156
|
+
managerKey,
|
|
157
|
+
reentrantOwner,
|
|
158
|
+
staleMs: 60_000,
|
|
159
|
+
payload: () => ({ pid: process.pid, operationId }),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await nested.release(); // sidecar remains for outer
|
|
163
|
+
await outer.release(); // final reference removes it
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The manager domain and canonical target path are part of the identity, so
|
|
167
|
+
aliased paths must use the same `managerKey`. The owner key must identify one
|
|
168
|
+
logical holder or call chain. **Never use a process-wide or other shared constant
|
|
169
|
+
for unrelated tasks**: doing so would admit concurrent work to the same critical
|
|
170
|
+
section and recreate the lost-update bug that removed `allowReentrant`.
|
|
171
|
+
|
|
172
|
+
Omit `reentrantOwner` for ordinary acquisitions. `jsonStore` does so and keeps
|
|
173
|
+
its separate canonical-path mutation queue. The synchronous APIs implement the
|
|
174
|
+
same owner/refcount rules; a mismatched synchronous acquisition blocks the
|
|
175
|
+
calling thread according to its retry and timeout options.
|
|
176
|
+
|
|
177
|
+
Pass `lockRoot` to place sidecar create, read, verification, and removal behind
|
|
178
|
+
an existing `Root` capability. `lockPath` must resolve inside that root.
|
|
179
|
+
Identity-conditioned removal remains the only release and reclaim deletion
|
|
180
|
+
path.
|
|
181
|
+
|
|
182
|
+
Async Root-backed acquisition normalizes the target's parent without creating
|
|
183
|
+
it, checking the retained Root before and after normalization. A deleted or
|
|
184
|
+
replaced Root fails before payload execution or held-entry reuse. Missing lock
|
|
185
|
+
subdirectories are still created through `Root.create`, never by target-key
|
|
186
|
+
normalization. The target is an arbitration key and may be outside the Root
|
|
187
|
+
when an explicit in-root `lockPath` is supplied; normalization does not follow a
|
|
188
|
+
target-leaf symlink. Non-Root acquisition retains its existing parent-creation
|
|
189
|
+
behavior.
|
|
190
|
+
|
|
191
|
+
An owner can finish releasing while another async acquirer inspects its record.
|
|
192
|
+
Create-only Root writes do not open an existing record merely to inherit its
|
|
193
|
+
mode. Once a pathname sample and opened descriptor agree, a failed acquisition
|
|
194
|
+
snapshot can be discarded only when the original descriptor has exact identity,
|
|
195
|
+
was not observed with multiple links, and proves it was unlinked (`nlink === 0`).
|
|
196
|
+
This includes Windows resolver
|
|
197
|
+
`EPERM`/`EBADF` failures, with evidence captured at the failing operation before
|
|
198
|
+
closing the descriptor. The canonical in-root ancestor chain and Root are
|
|
199
|
+
rechecked; permitted in-root parent symlinks are resolved before those checks.
|
|
200
|
+
|
|
201
|
+
A contending waiter may also encounter a new holder between its pre-open
|
|
202
|
+
pathname inspection and opening the file. It may discard that stale observation
|
|
203
|
+
only when the old sample and opened descriptor have different, strictly known
|
|
204
|
+
regular-file identities, neither was observed with multiple links, and the
|
|
205
|
+
opened descriptor and complete canonical ancestry pass reinspection. This does
|
|
206
|
+
not prove the old pathname sample was unlinked rather than moved. The new
|
|
207
|
+
holder's payload is not read or adopted. Post-create admission never opts into
|
|
208
|
+
this pre-open-change policy.
|
|
209
|
+
|
|
210
|
+
Discarding an acquisition observation is not proof that the pathname is absent:
|
|
211
|
+
another owner may already have created the next record. Every discarded
|
|
212
|
+
observation consumes the normal retry/deadline budget and requires fresh
|
|
213
|
+
exclusive creation. It supplies no release, reclaim, or held-lock authority.
|
|
214
|
+
Generic `Root.open()` and held-owner/reclaim reads still reject failed opens.
|
|
215
|
+
Moving an already-matched pinned descriptor without unlinking it, unknown or
|
|
216
|
+
inexact identities, retargeted ancestors, and unrelated filesystem or caller
|
|
217
|
+
errors fail closed. Failure receipts belong only to the current Root observation,
|
|
218
|
+
including during nested or concurrent acquisitions; historical error identity
|
|
219
|
+
is not changed-file, unlink, or open-denial evidence.
|
|
220
|
+
|
|
221
|
+
After creating a record, the async Root-backed acquirer checks the reopened
|
|
222
|
+
bytes against its exact serialized payload and ownership token. A replacement
|
|
223
|
+
is never adopted; a descriptor observed unlinked at the end of admission is
|
|
224
|
+
never registered as held. Failed admission cleanup retains the original creator
|
|
225
|
+
receipt, so it cannot remove a replacement using a later stat alone. Native
|
|
226
|
+
mode changes the create mechanism, not these Root-backed admission checks.
|
|
227
|
+
Non-Root and synchronous snapshots retain their descriptor/read/path checks
|
|
228
|
+
and do not use the Root opened-path resolver.
|
|
229
|
+
|
|
230
|
+
## Release handle
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
type FileLockHandle = {
|
|
234
|
+
lockPath: string;
|
|
235
|
+
normalizedTargetPath: string;
|
|
236
|
+
verifyStillHeld: () => Promise<boolean>;
|
|
237
|
+
release: () => Promise<void>;
|
|
238
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`verifyStillHeld()` compares the current sidecar with the ownership snapshot
|
|
243
|
+
captured at acquisition. Set `compromiseCheckIntervalMs` together with
|
|
244
|
+
`onCompromised` for a cheap periodic check; the callback fires once after the
|
|
245
|
+
sidecar no longer matches or after a verification I/O failure. This is
|
|
246
|
+
detection, not revocation of work already in progress.
|
|
247
|
+
|
|
248
|
+
## Synchronous locks
|
|
249
|
+
|
|
250
|
+
`acquireFileLockSync()` and `withFileLockSync()` mirror filesystem arbitration,
|
|
251
|
+
retry, payload parsing, stale policy, owner-scoped reentrancy, guarded
|
|
252
|
+
identity-conditioned reclaim, verification, and compromise monitoring. They do
|
|
253
|
+
not use the async manager queue or support async callbacks. Retry waits block
|
|
254
|
+
the calling thread; use the async API in request-serving code. The sync
|
|
255
|
+
compromise interval treats a thrown verification I/O error as a lost lock and
|
|
256
|
+
invokes `onCompromised` once, matching the asynchronous `.catch(() => false)`
|
|
257
|
+
contract. An explicit `verifyStillHeld()` call still propagates that I/O error.
|
|
258
|
+
|
|
259
|
+
Windows synchronous lock parents use the same canonical path spelling as
|
|
260
|
+
`root()`, including short-name expansion. With `lockRoot`, a failed parent
|
|
261
|
+
canonicalization or an out-of-root parent still rejects. Missing-path observations from snapshot
|
|
262
|
+
`lstat`/`open`, and identity-mismatched snapshots, consume the normal retry and
|
|
263
|
+
deadline budget. Errors from descriptor reads/stats or parsing are not treated
|
|
264
|
+
as missing snapshots, even when their code is `ENOENT`. Held verification,
|
|
265
|
+
release, and reclaim do not retry open denials.
|
|
266
|
+
|
|
267
|
+
Both synchronous helpers consume the [process-wide lock defaults](config.md#configurefssafelocks-config).
|
|
268
|
+
A synchronous retry sleep is clamped to the remaining finite deadline, so a long or jittered backoff cannot extend the configured timeout or block forever.
|
|
269
|
+
Per-call options take precedence, including zero values; a per-call `retry`
|
|
270
|
+
object replaces the entire configured retry object. A configured
|
|
271
|
+
`staleRecovery: "remove-if-unchanged"` still needs per-call
|
|
272
|
+
`shouldRemoveStaleLock` approval, and per-call `staleRecovery: "fail-closed"`
|
|
273
|
+
disables recovery even when the process configuration opts in.
|
|
274
|
+
|
|
275
|
+
Always release in a `finally`:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import { acquireFileLockSync } from "@openclaw/fs-safe/file-lock";
|
|
279
|
+
|
|
280
|
+
const handle = acquireFileLockSync("/var/lib/app/schema.json", {
|
|
281
|
+
staleMs: 60_000,
|
|
282
|
+
timeoutMs: 5_000,
|
|
283
|
+
retry: { retries: 20, minTimeout: 25, maxTimeout: 250 },
|
|
284
|
+
payload: () => ({ pid: process.pid, operation: "schema-migration" }),
|
|
285
|
+
});
|
|
286
|
+
try {
|
|
287
|
+
if (!handle.verifyStillHeld()) throw new Error("migration lock was replaced");
|
|
288
|
+
migrateSchemaSynchronously();
|
|
289
|
+
} finally {
|
|
290
|
+
handle.release();
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The sync payload, reclaim, and parsing callbacks must also be synchronous. This
|
|
295
|
+
shape is appropriate for a short boot migration; it is a poor fit for a server
|
|
296
|
+
request because retry backoff uses a blocking wait.
|
|
297
|
+
|
|
298
|
+
If termination skips the relevant cleanup handler or cleanup fails, the sidecar remains. In particular, `process.exit()` skips asynchronous Root cleanup; await explicit release or drain during application-managed graceful shutdown. Once `staleMs` elapses (or your `shouldReclaim` returns true), acquisition fails closed by default instead of deleting by path.
|
|
299
|
+
|
|
300
|
+
## `withFileLock` — common shape made one-liner
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const result = await withFileLock(
|
|
304
|
+
"/var/lib/app/state.json",
|
|
305
|
+
{
|
|
306
|
+
managerKey: "compact",
|
|
307
|
+
staleMs: 30_000,
|
|
308
|
+
payload: () => ({ pid: process.pid, what: "compact" }),
|
|
309
|
+
},
|
|
310
|
+
async () => {
|
|
311
|
+
return await runCompaction();
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Acquires, runs `fn`, releases regardless of success/failure. Returns the result of `fn`.
|
|
317
|
+
|
|
318
|
+
## Long-lived managers
|
|
319
|
+
|
|
320
|
+
Most callers should use `acquireFileLock()` or `withFileLock()`. Use `createFileLockManager(key)` only when a long-lived service needs diagnostics or lifecycle control over locks it currently holds:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
const locks = createFileLockManager("session-writes");
|
|
324
|
+
const handle = await locks.acquire(sessionPath, {
|
|
325
|
+
staleMs: 60_000,
|
|
326
|
+
payload: () => ({ pid: process.pid }),
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
for (const held of locks.heldEntries()) {
|
|
330
|
+
console.log(held.lockPath, held.acquiredAt);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await handle.release();
|
|
334
|
+
await locks.drain();
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Stale policy: `shouldReclaim`
|
|
338
|
+
|
|
339
|
+
The default policy treats locks whose valid `createdAt` is older than `staleMs`
|
|
340
|
+
as stale. A valid current or future timestamp remains authoritative under
|
|
341
|
+
filesystem clock skew; only absent or malformed timestamps fall back to the
|
|
342
|
+
sidecar `mtime`. Pass a custom callback when you want a richer notion of "is the
|
|
343
|
+
holder still alive":
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import { kill } from "node:process";
|
|
347
|
+
|
|
348
|
+
const handle = await acquireFileLock(targetPath, {
|
|
349
|
+
staleMs: 60_000,
|
|
350
|
+
payload: () => ({ pid: process.pid }),
|
|
351
|
+
shouldReclaim: ({ payload, nowMs, staleMs }) => {
|
|
352
|
+
if (!payload) return true;
|
|
353
|
+
const pid = Number(payload.pid);
|
|
354
|
+
if (!Number.isFinite(pid)) return true;
|
|
355
|
+
try {
|
|
356
|
+
kill(pid, 0);
|
|
357
|
+
return false; // process still alive — keep waiting
|
|
358
|
+
} catch {
|
|
359
|
+
return true; // process gone — fail closed for recovery
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
`heldByThisProcess` is true when this manager already holds the lock. A `true` result marks the observed sidecar as stale; `staleRecovery` then decides whether acquisition fails closed or attempts caller-approved removal.
|
|
366
|
+
|
|
367
|
+
## Stale recovery: guarded `remove-if-unchanged`
|
|
368
|
+
|
|
369
|
+
The default `staleRecovery: "fail-closed"` never removes third-party sidecars. Use `staleRecovery: "remove-if-unchanged"` only when your app has a reliable owner-liveness policy and can prove a stale owner cannot still be writing.
|
|
370
|
+
|
|
371
|
+
Opt-in recovery creates an exclusive `<lockPath>.reclaim` directory before the final snapshot check and unlink. Every compliant acquirer waits while that guard exists, so two reclaimers cannot perform the check/unlink race that could delete a fresh replacement lock. If another acquirer creates the replacement during the handoff, its exclusive create wins and the reclaimer leaves it untouched.
|
|
372
|
+
|
|
373
|
+
`shouldRemoveStaleLock` receives the exact lock snapshot that fs-safe inspected. The callback must approve that owner as definitely stale. If the callback is missing, returns false, or the file changed, acquisition fails closed or keeps retrying according to the normal retry policy.
|
|
374
|
+
|
|
375
|
+
A process killed during the short reclaim section can leave the empty `.reclaim` directory behind. That ambiguous state intentionally fails closed; remove the guard only under external authority that excludes every competing lock acquirer.
|
|
376
|
+
|
|
377
|
+
## What sidecar locks defend against
|
|
378
|
+
|
|
379
|
+
- **Two processes writing the same file at once.** `acquire` serializes the critical section.
|
|
380
|
+
- **Accidentally deleting a fresh lock during stale recovery.** Opt-in removal is serialized by the reclaim guard and rechecks the approved snapshot before unlinking.
|
|
381
|
+
- **Race between simultaneous acquire attempts.** `O_CREAT | O_EXCL` ensures one wins.
|
|
382
|
+
|
|
383
|
+
## What they do **not** defend against
|
|
384
|
+
|
|
385
|
+
- **Misbehaving holders that ignore the lock.** Locks are advisory — only callers that go through `acquire` are bound.
|
|
386
|
+
- **Unapproved stale lock deletion.** If a process crashes, use the payload and your own liveness policy before opting into guarded recovery.
|
|
387
|
+
- **Multi-host coordination over network filesystems.** Behavior depends on the underlying filesystem's `O_EXCL` semantics; treat as best-effort.
|
|
388
|
+
|
|
389
|
+
## Common patterns
|
|
390
|
+
|
|
391
|
+
### Compact under lock
|
|
392
|
+
|
|
393
|
+
```ts
|
|
394
|
+
await withFileLock(
|
|
395
|
+
"/var/lib/app/db.sqlite",
|
|
396
|
+
{
|
|
397
|
+
staleMs: 30_000,
|
|
398
|
+
payload: () => ({ pid: process.pid, what: "compact" }),
|
|
399
|
+
},
|
|
400
|
+
async () => {
|
|
401
|
+
await runCompaction();
|
|
402
|
+
},
|
|
403
|
+
);
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Try once, give up if held
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
try {
|
|
410
|
+
await withFileLock(
|
|
411
|
+
targetPath,
|
|
412
|
+
{ staleMs: 30_000, retry: { retries: 0 }, payload: () => ({ pid: process.pid }) },
|
|
413
|
+
async () => await work(),
|
|
414
|
+
);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
console.log("another process is doing this; skipping");
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Wait politely with backoff
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
await withFileLock(
|
|
424
|
+
targetPath,
|
|
425
|
+
{
|
|
426
|
+
staleMs: 60_000,
|
|
427
|
+
timeoutMs: 30_000,
|
|
428
|
+
retry: { retries: 30, minTimeout: 100, maxTimeout: 5_000, factor: 1.7, randomize: true },
|
|
429
|
+
payload: () => ({ pid: process.pid }),
|
|
430
|
+
},
|
|
431
|
+
async () => await work(),
|
|
432
|
+
);
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## See also
|
|
436
|
+
|
|
437
|
+
- [Atomic writes](atomic.md) — single-writer atomicity that often replaces the need for a lock entirely.
|
|
438
|
+
- `createAsyncLock` from `@openclaw/fs-safe/advanced` — in-process serialization for a single Node process.
|
|
439
|
+
- [Migrating to 0.5](migrating-to-0.5.md) — choosing sync versus async lock APIs.
|