@openclaw/fs-safe 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.7 - 2026-07-24
4
+
5
+ ### Features
6
+
7
+ - Add `@openclaw/fs-safe/durability` with identity-pinned directory handles,
8
+ explicit strict sync outcomes, synchronous and best-effort variants, and
9
+ durable nested-directory creation through every new parent edge.
10
+
11
+ ### Security and Correctness
12
+
13
+ - Reject final symlinks, FIFOs, non-directories, canonical-path drift, and
14
+ descriptor/path identity replacement before or after directory sync.
15
+ - Propagate POSIX directory synchronization failures while classifying known
16
+ unsupported Windows directory flushing only after revalidating the target;
17
+ directory-open access failures remain strict.
18
+ - Route sibling-temp, root, and pinned-write best-effort parent synchronization
19
+ through the shared guarded primitive instead of maintaining divergent implementations.
20
+
21
+ ### Docs and Tooling
22
+
23
+ - Document directory receipts, pin lifecycle, platform outcomes, custom
24
+ creation callbacks, and the boundary between filesystem durability and
25
+ application commit protocols.
26
+
27
+ ## 0.4.6 - 2026-07-24
28
+
29
+ ### Highlights
30
+
31
+ - Reject foreign-owned or unverifiable Windows files in secure reads while
32
+ preserving supported trusted local owners and exact extended drive paths.
33
+
34
+ ### Security and Correctness
35
+
36
+ - Report Windows owner SIDs and whether the owner is the current user,
37
+ LocalSystem, or built-in Administrators so credential-bearing executable
38
+ checks can reject foreign-owned paths even when their visible DACL is
39
+ read-only. Secure reads enforce the result and remote filesystems fail
40
+ closed.
41
+ - Read Windows owner and DACL data through the underlying .NET security
42
+ descriptor APIs so verification does not depend on PowerShell security-module
43
+ autoloading.
44
+ - Invoke `icacls.exe` with its supported path-only inspection syntax and use
45
+ the live Windows user/domain environment for named ACE classification, so
46
+ ACL verification works on supported Windows hosts instead of failing on the
47
+ invalid `/sid` argument.
48
+ - Normalize trailing Windows install-root separators with a bounded linear
49
+ scan so library-provided environment maps cannot trigger regex backtracking.
50
+
51
+ ### Docs and Tooling
52
+
53
+ - Add the public repository governance baseline, pinned CodeQL analysis,
54
+ package tarball/import validation, and protected tag-driven npm trusted
55
+ publishing with provenance and changelog-derived GitHub releases.
56
+ - Let tag-driven publishing continue from an expected registry miss into npm
57
+ trusted publishing instead of exiting before the publish attempt.
58
+ - Publish the generated tarball through an explicit relative path so npm treats
59
+ it as a local artifact instead of GitHub repository shorthand.
60
+
3
61
  ## 0.4.5 - 2026-07-20
4
62
 
5
63
  ### Highlights
package/README.md CHANGED
@@ -26,7 +26,7 @@ Full docs and reference at **[fs-safe.io](https://fs-safe.io)**.
26
26
 
27
27
  ## Contents
28
28
 
29
- [Why this exists](#why-this-exists) · [Not a sandbox](#not-a-sandbox) · [Install](#install) · [Quick start](#quick-start) · [Reading](#reading) · [Subpaths](#subpaths) · [Failure semantics](#failure-semantics-in-the-name) · [Atomic writes](#atomic-writes) · [External outputs](#external-outputs) · [Stores](#stores) · [Secure absolute reads](#secure-absolute-file-reads) · [Walking](#directory-walking) · [Archive extraction](#archive-extraction) · [Path scopes](#advanced-path-scopes) · [Errors](#errors) · [Safety model](#safety-model) · [Limitations](#limitations)
29
+ [Why this exists](#why-this-exists) · [Not a sandbox](#not-a-sandbox) · [Install](#install) · [Quick start](#quick-start) · [Reading](#reading) · [Subpaths](#subpaths) · [Failure semantics](#failure-semantics-in-the-name) · [Directory durability](#directory-durability) · [Atomic writes](#atomic-writes) · [External outputs](#external-outputs) · [Stores](#stores) · [Secure absolute reads](#secure-absolute-file-reads) · [Walking](#directory-walking) · [Archive extraction](#archive-extraction) · [Path scopes](#advanced-path-scopes) · [Errors](#errors) · [Safety model](#safety-model) · [Limitations](#limitations)
30
30
 
31
31
  ## Why this exists
32
32
 
@@ -189,6 +189,7 @@ that OpenClaw needs to compose higher-level APIs are grouped under
189
189
  | `@openclaw/fs-safe/store` | `fileStore`, `fileStoreSync`, and `jsonStore` |
190
190
  | `@openclaw/fs-safe/secret` | strict and try-style secret file read/write helpers |
191
191
  | `@openclaw/fs-safe/atomic` | `replaceFileAtomic`, `replaceFileAtomicSync`, `replaceDirectoryAtomic`, `movePathWithCopyFallback` |
192
+ | `@openclaw/fs-safe/durability` | pinned directory identities, strict and best-effort directory sync, durable nested-directory creation |
192
193
  | `@openclaw/fs-safe/temp` | `tempWorkspace`, `tempWorkspaceSync`, `withTempWorkspace`, `resolveSecureTempRoot` |
193
194
  | `@openclaw/fs-safe/secure-file` | fd-pinned absolute file reads with owner, mode, ACL, trusted-dir, size, and timeout checks |
194
195
  | `@openclaw/fs-safe/file-lock` | `acquireFileLock`, `withFileLock`, `createFileLockManager`, and related lock types |
@@ -216,6 +217,35 @@ performs the root-bounded open and JSON object validation in one step. Use
216
217
  `readRootStructuredFileSync()` when the parser lives outside fs-safe, such as
217
218
  JSON5-backed plugin manifests.
218
219
 
220
+ ## Directory durability
221
+
222
+ ```ts
223
+ import { ensureDurableDirectory, pinDirectory } from "@openclaw/fs-safe/durability";
224
+
225
+ const receipt = await ensureDurableDirectory({
226
+ directoryPath: "/srv/backups/sqlite",
227
+ mode: 0o700,
228
+ });
229
+ const pinned = await pinDirectory(receipt);
230
+ try {
231
+ await publishSnapshot();
232
+ const outcome = await pinned.sync();
233
+ // `unsupported` is explicit on platforms without directory flushing.
234
+ console.log(outcome.status);
235
+ } finally {
236
+ await pinned.close();
237
+ }
238
+ ```
239
+
240
+ The durability subpath pins a directory descriptor to its pathname identity,
241
+ detects symlink/FIFO/replacement races, and synchronizes every new parent edge
242
+ when creating a nested directory. Strict sync propagates real I/O failures and
243
+ reports known Windows directory-flush limitations explicitly. Separate
244
+ best-effort helpers preserve operations that do not promise crash durability.
245
+
246
+ See [Directory durability](docs/durability.md) for the receipt, pin lifecycle,
247
+ creation callback, and platform contract.
248
+
219
249
  ## Atomic writes
220
250
 
221
251
  `replaceFileAtomic()` writes a sibling temp file, optionally fsyncs it, and renames it over the destination. Mode preservation, rename retry / copy fallback on `EPERM`, parent-directory fsync, and a `beforeRename` hook for backup or observer flows are all opt-in. `movePathWithCopyFallback()` stages cross-device moves before commit and removes only the copied source entries, so concurrent source additions or replacements are preserved.
package/SECURITY.md CHANGED
@@ -1,12 +1,45 @@
1
1
  # Security Policy
2
2
 
3
- `@openclaw/fs-safe` is a filesystem-safety library, so potential boundary bypasses should be reported privately first.
3
+ If you believe you found a security issue in `@openclaw/fs-safe`, report it
4
+ privately.
4
5
 
5
- Email security reports to Peter Steinberger at `steipete@gmail.com` with:
6
+ ## Reporting
6
7
 
7
- - affected version or commit
8
- - platform and filesystem details
9
- - minimal reproduction steps
10
- - expected impact
8
+ Open a private report through
9
+ [GitHub Security Advisories](https://github.com/openclaw/fs-safe/security/advisories/new)
10
+ or email `security@openclaw.ai`.
11
11
 
12
- Please do not open a public issue for traversal, symlink, hardlink, archive extraction, or credential-file bugs until we have coordinated disclosure.
12
+ Include:
13
+
14
+ 1. affected version or commit
15
+ 2. Node.js version, operating system, filesystem, and mount details
16
+ 3. minimal reproduction
17
+ 4. demonstrated impact
18
+ 5. suggested remediation, if known
19
+
20
+ Do not open a public issue until maintainers have coordinated disclosure.
21
+
22
+ ## Scope
23
+
24
+ Security issues in scope generally include:
25
+
26
+ - traversal or root-confinement bypasses
27
+ - symlink, hardlink, rename, or identity-swap races that cross a documented
28
+ filesystem boundary
29
+ - archive extraction outside the intended destination
30
+ - unsafe secret-file, permission, ownership, or temporary-file behavior
31
+ - package or release-pipeline compromise affecting the published package
32
+
33
+ Reports must demonstrate a concrete boundary bypass or impact. Applications
34
+ remain responsible for operating-system isolation, authorization, and choosing
35
+ trusted root directories unless this package documents and fails to enforce a
36
+ specific invariant.
37
+
38
+ ## Operational Guidance
39
+
40
+ - Keep `@openclaw/fs-safe`, Node.js, and optional archive dependencies current.
41
+ - Treat pathnames and archive contents as untrusted.
42
+ - Use OS-level sandboxing when the threat model includes a hostile process.
43
+ - Pin and review dependency updates before publishing.
44
+
45
+ There is currently no paid bug bounty program.
@@ -0,0 +1,44 @@
1
+ import { type Stats } from "node:fs";
2
+ import { type FileIdentityStat } from "./file-identity.js";
3
+ export type DirectorySyncOutcome = {
4
+ status: "synced";
5
+ } | {
6
+ status: "unsupported";
7
+ code?: string;
8
+ };
9
+ export type DirectoryReceipt = {
10
+ path: string;
11
+ realPath: string;
12
+ identity: Stats;
13
+ };
14
+ export type DurableDirectoryReceipt = DirectoryReceipt & {
15
+ parentSync: DirectorySyncOutcome | {
16
+ status: "not-needed";
17
+ };
18
+ };
19
+ export type PinnedDirectory = {
20
+ readonly receipt: DirectoryReceipt;
21
+ assertCurrent(): Promise<void>;
22
+ sync(): Promise<DirectorySyncOutcome>;
23
+ close(): Promise<void>;
24
+ };
25
+ export type EnsureDurableDirectoryOptions = {
26
+ directoryPath: string;
27
+ label?: string;
28
+ mode?: number;
29
+ expectedExistingIdentity?: FileIdentityStat;
30
+ create?: (directoryPath: string) => Promise<void>;
31
+ };
32
+ export declare function pinDirectory(directory: string | DirectoryReceipt, options?: {
33
+ label?: string;
34
+ }): Promise<PinnedDirectory>;
35
+ export declare function syncDirectory(directory: string | DirectoryReceipt, options?: {
36
+ label?: string;
37
+ }): Promise<DirectorySyncOutcome>;
38
+ export declare function syncDirectorySync(directory: string | DirectoryReceipt, options?: {
39
+ label?: string;
40
+ }): DirectorySyncOutcome;
41
+ export declare function syncDirectoryBestEffort(directoryPath: string): Promise<void>;
42
+ export declare function syncDirectoryBestEffortSync(directoryPath: string): void;
43
+ export declare function ensureDurableDirectory(options: EnsureDurableDirectoryOptions): Promise<DurableDirectoryReceipt>;
44
+ //# sourceMappingURL=directory-durability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-durability.d.ts","sourceRoot":"","sources":["../src/directory-durability.ts"],"names":[],"mappings":"AAAA,OAAe,EAAE,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAM7C,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,MAAM,oBAAoB,GAC5B;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACvD,UAAU,EAAE,oBAAoB,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;CAC7D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wBAAwB,CAAC,EAAE,gBAAgB,CAAC;IAC5C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAgKF,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,eAAe,CAAC,CAa1B;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,GAAG,gBAAgB,EACpC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,oBAAoB,CAuCtB;AAED,wBAAsB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAMvE;AAuBD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,uBAAuB,CAAC,CA8ElC"}
@@ -0,0 +1,299 @@
1
+ import fsSync, {} from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { ensureAbsoluteDirectory } from "./absolute-path.js";
5
+ import { FsSafeError } from "./errors.js";
6
+ import { sameFileIdentity } from "./file-identity.js";
7
+ function directoryOpenFlags() {
8
+ if (process.platform === "win32") {
9
+ return "r";
10
+ }
11
+ return (fsSync.constants.O_RDONLY |
12
+ fsSync.constants.O_DIRECTORY |
13
+ fsSync.constants.O_NOFOLLOW |
14
+ fsSync.constants.O_NONBLOCK);
15
+ }
16
+ function isWindowsDirectorySyncUnsupported(error) {
17
+ if (process.platform !== "win32") {
18
+ return false;
19
+ }
20
+ const code = error.code;
21
+ return (code === "EACCES" ||
22
+ code === "EINVAL" ||
23
+ code === "EISDIR" ||
24
+ code === "ENOSYS" ||
25
+ code === "ENOTSUP" ||
26
+ code === "EPERM");
27
+ }
28
+ function isWindowsDirectoryOpenUnsupported(error) {
29
+ if (process.platform !== "win32") {
30
+ return false;
31
+ }
32
+ const code = error.code;
33
+ return code === "EINVAL" || code === "EISDIR" || code === "ENOSYS" || code === "ENOTSUP";
34
+ }
35
+ function unsupportedOutcome(error) {
36
+ const code = error.code;
37
+ return code ? { status: "unsupported", code } : { status: "unsupported" };
38
+ }
39
+ function assertDirectory(identity, pathname, label) {
40
+ if (identity.isSymbolicLink() || !identity.isDirectory()) {
41
+ throw new FsSafeError("not-file", `${label} must be a real directory: ${pathname}`);
42
+ }
43
+ }
44
+ async function createDirectoryReceipt(directoryPath, label) {
45
+ const resolvedPath = path.resolve(directoryPath);
46
+ const identity = await fs.lstat(resolvedPath);
47
+ assertDirectory(identity, resolvedPath, label);
48
+ return {
49
+ path: resolvedPath,
50
+ realPath: await fs.realpath(resolvedPath),
51
+ identity,
52
+ };
53
+ }
54
+ function createDirectoryReceiptSync(directoryPath, label) {
55
+ const resolvedPath = path.resolve(directoryPath);
56
+ const identity = fsSync.lstatSync(resolvedPath);
57
+ assertDirectory(identity, resolvedPath, label);
58
+ return {
59
+ path: resolvedPath,
60
+ realPath: fsSync.realpathSync(resolvedPath),
61
+ identity,
62
+ };
63
+ }
64
+ async function assertDirectoryReceiptCurrent(receipt, label) {
65
+ const currentIdentity = await fs.lstat(receipt.path);
66
+ assertDirectory(currentIdentity, receipt.path, label);
67
+ if (!sameFileIdentity(receipt.identity, currentIdentity) ||
68
+ (await fs.realpath(receipt.path)) !== receipt.realPath) {
69
+ throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
70
+ }
71
+ }
72
+ function assertDirectoryReceiptCurrentSync(receipt, label) {
73
+ const currentIdentity = fsSync.lstatSync(receipt.path);
74
+ assertDirectory(currentIdentity, receipt.path, label);
75
+ if (!sameFileIdentity(receipt.identity, currentIdentity) ||
76
+ fsSync.realpathSync(receipt.path) !== receipt.realPath) {
77
+ throw new FsSafeError("path-mismatch", `${label} changed during durable directory operation: ${receipt.path}`);
78
+ }
79
+ }
80
+ async function assertOpenDirectoryCurrent(handle, receipt, label) {
81
+ const openedIdentity = await handle.stat();
82
+ assertDirectory(openedIdentity, receipt.path, label);
83
+ if (!sameFileIdentity(receipt.identity, openedIdentity)) {
84
+ throw new FsSafeError("path-mismatch", `${label} handle changed during directory sync: ${receipt.path}`);
85
+ }
86
+ await assertDirectoryReceiptCurrent(receipt, label);
87
+ }
88
+ class PinnedDirectoryImpl {
89
+ receipt;
90
+ #handle;
91
+ #label;
92
+ #closed = false;
93
+ constructor(handle, receipt, label) {
94
+ this.#handle = handle;
95
+ this.receipt = receipt;
96
+ this.#label = label;
97
+ }
98
+ async assertCurrent() {
99
+ if (this.#closed) {
100
+ throw new FsSafeError("helper-failed", `${this.#label} pin is already closed`);
101
+ }
102
+ await assertOpenDirectoryCurrent(this.#handle, this.receipt, this.#label);
103
+ }
104
+ async sync() {
105
+ await this.assertCurrent();
106
+ try {
107
+ await this.#handle.sync();
108
+ }
109
+ catch (error) {
110
+ if (!isWindowsDirectorySyncUnsupported(error)) {
111
+ throw error;
112
+ }
113
+ await this.assertCurrent();
114
+ return unsupportedOutcome(error);
115
+ }
116
+ await this.assertCurrent();
117
+ return { status: "synced" };
118
+ }
119
+ async close() {
120
+ if (this.#closed) {
121
+ return;
122
+ }
123
+ this.#closed = true;
124
+ await this.#handle.close();
125
+ }
126
+ }
127
+ export async function pinDirectory(directory, options = {}) {
128
+ const label = options.label ?? "directory";
129
+ const receipt = typeof directory === "string" ? await createDirectoryReceipt(directory, label) : directory;
130
+ await assertDirectoryReceiptCurrent(receipt, label);
131
+ const handle = await fs.open(receipt.path, directoryOpenFlags());
132
+ try {
133
+ await assertOpenDirectoryCurrent(handle, receipt, label);
134
+ return new PinnedDirectoryImpl(handle, receipt, label);
135
+ }
136
+ catch (error) {
137
+ await handle.close().catch(() => undefined);
138
+ throw error;
139
+ }
140
+ }
141
+ export async function syncDirectory(directory, options = {}) {
142
+ const label = options.label ?? "directory";
143
+ const receipt = typeof directory === "string" ? await createDirectoryReceipt(directory, label) : directory;
144
+ let pinned;
145
+ try {
146
+ pinned = await pinDirectory(receipt, { label });
147
+ }
148
+ catch (error) {
149
+ if (!isWindowsDirectoryOpenUnsupported(error)) {
150
+ throw error;
151
+ }
152
+ await assertDirectoryReceiptCurrent(receipt, label);
153
+ return unsupportedOutcome(error);
154
+ }
155
+ try {
156
+ return await pinned.sync();
157
+ }
158
+ finally {
159
+ await pinned.close();
160
+ }
161
+ }
162
+ export function syncDirectorySync(directory, options = {}) {
163
+ const label = options.label ?? "directory";
164
+ const receipt = typeof directory === "string" ? createDirectoryReceiptSync(directory, label) : directory;
165
+ assertDirectoryReceiptCurrentSync(receipt, label);
166
+ let descriptor;
167
+ try {
168
+ descriptor = fsSync.openSync(receipt.path, directoryOpenFlags());
169
+ }
170
+ catch (error) {
171
+ if (!isWindowsDirectoryOpenUnsupported(error)) {
172
+ throw error;
173
+ }
174
+ assertDirectoryReceiptCurrentSync(receipt, label);
175
+ return unsupportedOutcome(error);
176
+ }
177
+ try {
178
+ const openedIdentity = fsSync.fstatSync(descriptor);
179
+ assertDirectory(openedIdentity, receipt.path, label);
180
+ if (!sameFileIdentity(receipt.identity, openedIdentity)) {
181
+ throw new FsSafeError("path-mismatch", `${label} handle changed during directory sync: ${receipt.path}`);
182
+ }
183
+ assertDirectoryReceiptCurrentSync(receipt, label);
184
+ try {
185
+ fsSync.fsyncSync(descriptor);
186
+ }
187
+ catch (error) {
188
+ if (!isWindowsDirectorySyncUnsupported(error)) {
189
+ throw error;
190
+ }
191
+ assertDirectoryReceiptCurrentSync(receipt, label);
192
+ return unsupportedOutcome(error);
193
+ }
194
+ assertDirectoryReceiptCurrentSync(receipt, label);
195
+ return { status: "synced" };
196
+ }
197
+ finally {
198
+ fsSync.closeSync(descriptor);
199
+ }
200
+ }
201
+ export async function syncDirectoryBestEffort(directoryPath) {
202
+ await syncDirectory(directoryPath).catch(() => undefined);
203
+ }
204
+ export function syncDirectoryBestEffortSync(directoryPath) {
205
+ try {
206
+ syncDirectorySync(directoryPath);
207
+ }
208
+ catch {
209
+ // Compatibility helper for operations whose primary write may remain usable.
210
+ }
211
+ }
212
+ async function findExistingAncestorReceipt(targetPath, label) {
213
+ let currentPath = path.resolve(targetPath);
214
+ while (true) {
215
+ try {
216
+ return await createDirectoryReceipt(currentPath, label);
217
+ }
218
+ catch (error) {
219
+ if (error.code !== "ENOENT") {
220
+ throw error;
221
+ }
222
+ }
223
+ const parentPath = path.dirname(currentPath);
224
+ if (parentPath === currentPath) {
225
+ throw new FsSafeError("not-found", `${label} has no existing directory ancestor`);
226
+ }
227
+ currentPath = parentPath;
228
+ }
229
+ }
230
+ export async function ensureDurableDirectory(options) {
231
+ const directoryPath = path.resolve(options.directoryPath);
232
+ const label = options.label ?? "directory";
233
+ const ancestorReceipt = await findExistingAncestorReceipt(directoryPath, label);
234
+ const targetExists = ancestorReceipt.path === directoryPath;
235
+ if (options.expectedExistingIdentity &&
236
+ (!targetExists || !sameFileIdentity(options.expectedExistingIdentity, ancestorReceipt.identity))) {
237
+ throw new FsSafeError("path-mismatch", `${label} changed before durable directory pinning: ${directoryPath}`);
238
+ }
239
+ const ancestor = await pinDirectory(ancestorReceipt, { label });
240
+ const pinnedDirectories = [ancestor];
241
+ try {
242
+ await ancestor.assertCurrent();
243
+ if (!targetExists) {
244
+ if (options.create) {
245
+ await options.create(directoryPath);
246
+ }
247
+ else {
248
+ const created = await ensureAbsoluteDirectory(directoryPath, {
249
+ mode: options.mode,
250
+ scopeLabel: label,
251
+ });
252
+ if (!created.ok) {
253
+ throw created.error;
254
+ }
255
+ }
256
+ }
257
+ await ancestor.assertCurrent();
258
+ let currentPath = ancestor.receipt.path;
259
+ for (const segment of path
260
+ .relative(ancestor.receipt.path, directoryPath)
261
+ .split(path.sep)
262
+ .filter(Boolean)) {
263
+ currentPath = path.join(currentPath, segment);
264
+ pinnedDirectories.push(await pinDirectory(currentPath, { label }));
265
+ }
266
+ let parentSync = { status: "not-needed" };
267
+ for (let index = pinnedDirectories.length - 1; index > 0; index -= 1) {
268
+ const parent = pinnedDirectories[index - 1];
269
+ const child = pinnedDirectories[index];
270
+ if (!parent || !child) {
271
+ throw new FsSafeError("helper-failed", `${label} directory pin chain is incomplete`);
272
+ }
273
+ await child.assertCurrent();
274
+ try {
275
+ const outcome = await parent.sync();
276
+ if (outcome.status === "unsupported") {
277
+ parentSync = outcome;
278
+ }
279
+ else if (parentSync.status === "not-needed") {
280
+ parentSync = outcome;
281
+ }
282
+ }
283
+ catch (error) {
284
+ throw new FsSafeError("helper-failed", `${label} could not sync created directory edge ${child.receipt.path} through ${parent.receipt.path}`, { cause: error });
285
+ }
286
+ await child.assertCurrent();
287
+ }
288
+ const finalReceipt = pinnedDirectories.at(-1)?.receipt;
289
+ if (!finalReceipt) {
290
+ throw new FsSafeError("helper-failed", `${label} directory receipt is missing`);
291
+ }
292
+ await ancestor.assertCurrent();
293
+ await assertDirectoryReceiptCurrent(finalReceipt, label);
294
+ return { ...finalReceipt, parentSync };
295
+ }
296
+ finally {
297
+ await Promise.all(pinnedDirectories.toReversed().map(async (directory) => directory.close()));
298
+ }
299
+ }
@@ -0,0 +1,2 @@
1
+ export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, type DirectoryReceipt, type DirectorySyncOutcome, type DurableDirectoryReceipt, type EnsureDurableDirectoryOptions, type PinnedDirectory, } from "./directory-durability.js";
2
+ //# sourceMappingURL=durability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durability.d.ts","sourceRoot":"","sources":["../src/durability.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,uBAAuB,EACvB,2BAA2B,EAC3B,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1 @@
1
+ export { ensureDurableDirectory, pinDirectory, syncDirectory, syncDirectoryBestEffort, syncDirectoryBestEffortSync, syncDirectorySync, } from "./directory-durability.js";
package/dist/fsync.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare function syncDirectoryBestEffort(dirPath: string): Promise<void>;
1
+ export { syncDirectoryBestEffort } from "./directory-durability.js";
2
2
  //# sourceMappingURL=fsync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fsync.d.ts","sourceRoot":"","sources":["../src/fsync.ts"],"names":[],"mappings":"AAIA,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB5E"}
1
+ {"version":3,"file":"fsync.d.ts","sourceRoot":"","sources":["../src/fsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/fsync.js CHANGED
@@ -1,21 +1 @@
1
- import fsSync from "node:fs";
2
- import fs from "node:fs/promises";
3
- export async function syncDirectoryBestEffort(dirPath) {
4
- if (process.platform === "win32") {
5
- return;
6
- }
7
- let handle;
8
- try {
9
- const flags = fsSync.constants.O_RDONLY |
10
- ("O_DIRECTORY" in fsSync.constants ? fsSync.constants.O_DIRECTORY : 0) |
11
- ("O_NOFOLLOW" in fsSync.constants ? fsSync.constants.O_NOFOLLOW : 0);
12
- handle = await fs.open(dirPath, flags);
13
- await handle.sync();
14
- }
15
- catch {
16
- // Some filesystems reject directory handles; keep the write usable there.
17
- }
18
- finally {
19
- await handle?.close().catch(() => undefined);
20
- }
21
- }
1
+ export { syncDirectoryBestEffort } from "./directory-durability.js";
@@ -1 +1 @@
1
- {"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"AAWA,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAMT;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAIjF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBzD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMxE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
1
+ {"version":3,"file":"local-file-access.d.ts","sourceRoot":"","sources":["../src/local-file-access.ts"],"names":[],"mappings":"AAWA,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAeT;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAET;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAS,GAAG,IAAI,CAIjF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmBzD;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMxE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgB3E"}
@@ -14,7 +14,15 @@ export function isWindowsNetworkPath(filePath, platform = process.platform) {
14
14
  return false;
15
15
  }
16
16
  const normalized = filePath.replace(/\//g, "\\");
17
- return normalized.startsWith("\\\\?\\UNC\\") || normalized.startsWith("\\\\");
17
+ const extendedDrive = normalized.length >= 7 &&
18
+ normalized.startsWith("\\\\?\\") &&
19
+ /^[a-z]$/i.test(normalized[4] ?? "") &&
20
+ normalized[5] === ":" &&
21
+ normalized[6] === "\\";
22
+ if (extendedDrive) {
23
+ return false;
24
+ }
25
+ return normalized.startsWith("\\\\");
18
26
  }
19
27
  export function isWindowsDriveLetterPath(filePath, platform = process.platform) {
20
28
  return platform === "win32" && /^[A-Za-z]:[\\/]/.test(filePath);
@@ -13,6 +13,12 @@ export type PermissionCheck = {
13
13
  groupWritable: boolean;
14
14
  worldReadable: boolean;
15
15
  groupReadable: boolean;
16
+ /** Canonical Windows owner SID when the owner query succeeds. */
17
+ ownerSid?: string;
18
+ /** Whether the Windows owner is the current user, LocalSystem, or Administrators. */
19
+ ownerTrusted?: boolean;
20
+ /** Owner-query failure detail when Windows ownership could not be verified. */
21
+ ownerError?: string;
16
22
  aclSummary?: string;
17
23
  error?: string;
18
24
  };
@@ -32,6 +38,8 @@ export type SafeStatResult = {
32
38
  };
33
39
  export type WindowsAclEntry = {
34
40
  principal: string;
41
+ /** Canonical principal SID when resolved from Windows. */
42
+ sid?: string;
35
43
  rights: string[];
36
44
  rawRights: string;
37
45
  canRead: boolean;
@@ -75,6 +83,9 @@ export declare function summarizeWindowsAcl(entries: WindowsAclEntry[], env?: No
75
83
  export declare function inspectWindowsAcl(targetPath: string, opts?: {
76
84
  env?: NodeJS.ProcessEnv;
77
85
  exec?: PermissionExec;
86
+ currentUserSid?: string;
87
+ principalSids?: Record<string, string>;
88
+ principalTranslationFailed?: boolean;
78
89
  }): Promise<WindowsAclSummary>;
79
90
  export declare function formatWindowsAclSummary(summary: WindowsAclSummary): string;
80
91
  export declare function formatIcaclsResetCommand(targetPath: string, opts: IcaclsResetCommandOptions): string;
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}