@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 +58 -0
- package/README.md +31 -1
- package/SECURITY.md +40 -7
- package/dist/directory-durability.d.ts +44 -0
- package/dist/directory-durability.d.ts.map +1 -0
- package/dist/directory-durability.js +299 -0
- package/dist/durability.d.ts +2 -0
- package/dist/durability.d.ts.map +1 -0
- package/dist/durability.js +1 -0
- package/dist/fsync.d.ts +1 -1
- package/dist/fsync.d.ts.map +1 -1
- package/dist/fsync.js +1 -21
- package/dist/local-file-access.d.ts.map +1 -1
- package/dist/local-file-access.js +9 -1
- package/dist/permissions.d.ts +11 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +56 -55
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +3 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +1 -13
- package/dist/windows-command.d.ts +2 -0
- package/dist/windows-command.d.ts.map +1 -0
- package/dist/windows-command.js +34 -0
- package/dist/windows-owner.d.ts +29 -0
- package/dist/windows-owner.d.ts.map +1 -0
- package/dist/windows-owner.js +118 -0
- package/docs/advanced.md +1 -1
- package/docs/contributing.md +19 -4
- package/docs/durability.md +89 -0
- package/docs/index.md +1 -0
- package/docs/permissions.md +4 -1
- package/docs/security-model.md +13 -0
- package/package.json +26 -3
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
|
-
|
|
3
|
+
If you believe you found a security issue in `@openclaw/fs-safe`, report it
|
|
4
|
+
privately.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## Reporting
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
|
1
|
+
export { syncDirectoryBestEffort } from "./directory-durability.js";
|
|
2
2
|
//# sourceMappingURL=fsync.d.ts.map
|
package/dist/fsync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fsync.d.ts","sourceRoot":"","sources":["../src/fsync.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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);
|
package/dist/permissions.d.ts
CHANGED
|
@@ -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":"
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
|