@openclaw/fs-safe 0.5.3 → 0.5.5
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 +21 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +2 -11
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +11 -9
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +2 -10
- package/dist/native/darwin-arm64/fs-safe-native.node +0 -0
- package/dist/native/darwin-x64/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-arm64-musl/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-gnu/fs-safe-native.node +0 -0
- package/dist/native/linux-x64-musl/fs-safe-native.node +0 -0
- package/dist/native/win32-x64-msvc/fs-safe-native.node +0 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +2 -2
- package/dist/publish-file-failure.d.ts +1 -0
- package/dist/publish-file-failure.d.ts.map +1 -1
- package/dist/publish-file-failure.js +2 -1
- package/dist/publish-file.d.ts.map +1 -1
- package/dist/publish-file.js +49 -36
- package/dist/read-open-flags.d.ts +8 -0
- package/dist/read-open-flags.d.ts.map +1 -0
- package/dist/read-open-flags.js +13 -0
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +3 -8
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +3 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.5 - 2026-08-12
|
|
4
|
+
|
|
5
|
+
### Security and Correctness
|
|
6
|
+
|
|
7
|
+
- Prevent nondeterministic Windows read failures with `path-mismatch` when path-based stat reports zero file identity through libuv's FindFirstFile fallback under antivirus or indexer contention; zero Windows inodes are now treated as unknown identity like zero device serials.
|
|
8
|
+
|
|
9
|
+
## 0.5.4 - 2026-08-10
|
|
10
|
+
|
|
11
|
+
**Highlight:** two security fixes in the publication path. If you run fs-safe on
|
|
12
|
+
Windows, the identity fix is the one that matters — rounded file indexes could
|
|
13
|
+
previously let a replaced path pass as the original.
|
|
14
|
+
|
|
15
|
+
### Security and Correctness
|
|
16
|
+
|
|
17
|
+
- Compare exact bigint filesystem identities during exclusive publication and rollback cleanup. Windows rounds file indexes, so a replaced source or target path could previously masquerade as the original — and, worse, authorize deletion of an attacker's replacement. Identity comparisons are now exact.
|
|
18
|
+
- Open pinned and standalone regular-file reads nonblocking on POSIX before validating the descriptor type, so a raced FIFO or device cannot stall the worker. The same read-open flags are now shared by root reads, hashing, and move-copy fallbacks, so the guarantee holds on every path rather than just the one that was fixed.
|
|
19
|
+
|
|
20
|
+
### Dependencies and maintenance
|
|
21
|
+
|
|
22
|
+
- Refresh the napi toolchain (napi 3.12.1, napi-derive 3.6.3, napi-build 2.4.1) and the Rust and Node development dependency graph.
|
|
23
|
+
|
|
3
24
|
## 0.5.3 - 2026-08-08
|
|
4
25
|
|
|
5
26
|
### Security and Correctness
|
package/dist/file-hash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-hash.d.ts","sourceRoot":"","sources":["../src/file-hash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAInD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGnE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAElD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,MAAM,GAAE,aAAa,GAAG,SAA8B,GACrD,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AA2CD,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAElF"}
|
package/dist/file-hash.js
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import fsSync from "node:fs";
|
|
3
2
|
import fs from "node:fs/promises";
|
|
4
3
|
import { FsSafeError } from "./errors.js";
|
|
5
4
|
import { sameFileIdentity } from "./file-identity.js";
|
|
6
5
|
import { getNativeBinding } from "./native.js";
|
|
7
|
-
|
|
8
|
-
return (fsSync.constants.O_RDONLY |
|
|
9
|
-
(process.platform !== "win32" && typeof fsSync.constants.O_NOFOLLOW === "number"
|
|
10
|
-
? fsSync.constants.O_NOFOLLOW
|
|
11
|
-
: 0) |
|
|
12
|
-
(process.platform !== "win32" && typeof fsSync.constants.O_NONBLOCK === "number"
|
|
13
|
-
? fsSync.constants.O_NONBLOCK
|
|
14
|
-
: 0));
|
|
15
|
-
}
|
|
6
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
16
7
|
export async function hashFileHandle(handle, native = getNativeBinding()) {
|
|
17
8
|
const stat = await handle.stat();
|
|
18
9
|
if (!stat.isFile()) {
|
|
@@ -43,7 +34,7 @@ async function hashPath(filePath) {
|
|
|
43
34
|
}
|
|
44
35
|
let handle;
|
|
45
36
|
try {
|
|
46
|
-
handle = await fs.open(filePath,
|
|
37
|
+
handle = await fs.open(filePath, resolveReadOpenFlags());
|
|
47
38
|
}
|
|
48
39
|
catch (error) {
|
|
49
40
|
if (error?.code === "ELOOP") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-identity.d.ts","sourceRoot":"","sources":["../src/file-identity.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC;
|
|
1
|
+
{"version":3,"file":"file-identity.d.ts","sourceRoot":"","sources":["../src/file-identity.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB,CAAC;AAsBF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,MAAM,CAGlF;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,gBAAgB,EACtB,KAAK,EAAE,gBAAgB,EACvB,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAQT"}
|
package/dist/file-identity.js
CHANGED
|
@@ -5,18 +5,20 @@ function isZero(value) {
|
|
|
5
5
|
function sameStatValue(left, right) {
|
|
6
6
|
return typeof left === typeof right ? left === right : BigInt(left) === BigInt(right);
|
|
7
7
|
}
|
|
8
|
+
function isStatValueProvablyDifferent(left, right, platform) {
|
|
9
|
+
if (sameStatValue(left, right)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return platform !== "win32" || (!isZero(left) && !isZero(right));
|
|
13
|
+
}
|
|
8
14
|
export function sha256Hex(data, encoding) {
|
|
9
15
|
const buffer = typeof data === "string" ? Buffer.from(data, encoding ?? "utf8") : data;
|
|
10
16
|
return createHash("sha256").update(buffer).digest("hex");
|
|
11
17
|
}
|
|
12
18
|
export function sameFileIdentity(left, right, platform = process.platform) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (sameStatValue(left.dev, right.dev)) {
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
return platform === "win32" && (isZero(left.dev) || isZero(right.dev));
|
|
19
|
+
// When Windows cannot open a path for stat, libuv's FindFirstFile fallback reports dev=0 and
|
|
20
|
+
// ino=0. Treating either unknown value as a mismatch caused nondeterministic path-mismatch
|
|
21
|
+
// failures on legitimate reads under antivirus or indexer contention.
|
|
22
|
+
return (!isStatValueProvablyDifferent(left.dev, right.dev, platform) &&
|
|
23
|
+
!isStatValueProvablyDifferent(left.ino, right.ino, platform));
|
|
22
24
|
}
|
package/dist/move-path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"move-path.d.ts","sourceRoot":"","sources":["../src/move-path.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,+BAA+B,GAAG;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACrC,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,KAAK,sBAAsB,GAAG,cAAc,GAAG,uBAAuB,CAAC;AAEvE,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,OAAO,EACd,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,sBAAsB,GAAG,SAAS,CASpC;AAiVD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,+BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CA2Df"}
|
package/dist/move-path.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { FsSafeError } from "./errors.js";
|
|
6
6
|
import { guardedRename } from "./guarded-mutation.js";
|
|
7
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
7
8
|
import { registerTempPathForExit } from "./temp-cleanup.js";
|
|
8
9
|
export function moveCopyFallbackReasonForRenameError(error, platform = process.platform) {
|
|
9
10
|
const code = error?.code;
|
|
@@ -94,15 +95,6 @@ async function assertSourceStillMatches(sourcePath, identity) {
|
|
|
94
95
|
throw sourceChangedError(sourcePath);
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
|
-
function regularReadFlags() {
|
|
98
|
-
return (fsConstants.O_RDONLY |
|
|
99
|
-
(typeof fsConstants.O_NOFOLLOW === "number" && process.platform !== "win32"
|
|
100
|
-
? fsConstants.O_NOFOLLOW
|
|
101
|
-
: 0) |
|
|
102
|
-
(typeof fsConstants.O_NONBLOCK === "number" && process.platform !== "win32"
|
|
103
|
-
? fsConstants.O_NONBLOCK
|
|
104
|
-
: 0));
|
|
105
|
-
}
|
|
106
98
|
async function chmodDirectoryPinned(directoryPath, mode) {
|
|
107
99
|
if (process.platform === "win32") {
|
|
108
100
|
// Node cannot portably open a Windows directory for descriptor-bound
|
|
@@ -129,7 +121,7 @@ async function copyRegularFilePinned(params) {
|
|
|
129
121
|
let destinationCreated = false;
|
|
130
122
|
let sourceHandle;
|
|
131
123
|
try {
|
|
132
|
-
sourceHandle = await fs.open(params.from,
|
|
124
|
+
sourceHandle = await fs.open(params.from, resolveReadOpenFlags());
|
|
133
125
|
}
|
|
134
126
|
catch (error) {
|
|
135
127
|
const code = error?.code;
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-open.d.ts","sourceRoot":"","sources":["../src/pinned-open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"pinned-open.d.ts","sourceRoot":"","sources":["../src/pinned-open.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAMzB,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI,CAAC;AAEvE,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAA;CAAE,GACtD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,2BAA2B,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAExE,MAAM,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW,CAAC;AAE7D,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,OAAO,EAAE,EACT,WAAW,GAAG,WAAW,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,WAAW,CACpF,CAAC;AAYF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,yBAAyB,CAAC;IACxC,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,GAAG,oBAAoB,CA+EvB"}
|
package/dist/pinned-open.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import { isUnsafeDeviceReadPath } from "./device-path.js";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { sameFileIdentity as hasSameFileIdentity } from "./file-identity.js";
|
|
5
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
5
6
|
function isExpectedPathError(error) {
|
|
6
7
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
7
8
|
return code === "ENOENT" || code === "ENOTDIR" || code === "ELOOP";
|
|
@@ -12,8 +13,7 @@ function sameFileIdentity(left, right) {
|
|
|
12
13
|
export function openPinnedFileSync(params) {
|
|
13
14
|
const ioFs = params.ioFs ?? fs;
|
|
14
15
|
const allowedType = params.allowedType ?? "file";
|
|
15
|
-
const openReadFlags = ioFs.constants
|
|
16
|
-
(typeof ioFs.constants.O_NOFOLLOW === "number" ? ioFs.constants.O_NOFOLLOW : 0);
|
|
16
|
+
const openReadFlags = resolveReadOpenFlags({ constants: ioFs.constants });
|
|
17
17
|
let fd = null;
|
|
18
18
|
try {
|
|
19
19
|
if (isUnsafeDeviceReadPath(params.filePath)) {
|
|
@@ -18,6 +18,7 @@ export type PublishFailureState = {
|
|
|
18
18
|
phase: PublishFileExclusiveFailurePhase;
|
|
19
19
|
targetCreated: boolean;
|
|
20
20
|
targetIdentity?: FileIdentityStat;
|
|
21
|
+
targetCleanupIdentity?: FileIdentityStat;
|
|
21
22
|
preserveTarget: boolean;
|
|
22
23
|
directorySync?: PublishFileExclusiveDirectorySyncFailure;
|
|
23
24
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-file-failure.d.ts","sourceRoot":"","sources":["../src/publish-file-failure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,MAAM,qCAAqC,GAAG,UAAU,GAAG,UAAU,CAAC;AAC5E,MAAM,MAAM,wCAAwC,GAAG;IACrD,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,gCAAgC,GACxC,aAAa,GACb,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,eAAe,CAAC;AACpB,MAAM,MAAM,2BAA2B,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAC9E,MAAM,MAAM,kCAAkC,GAAG;IAC/C,KAAK,EAAE,gCAAgC,CAAC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,OAAO,EAAE,2BAA2B,CAAC;IACrC,aAAa,CAAC,EAAE,wCAAwC,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,gCAAgC,CAAC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,wCAAwC,CAAC;CAC1D,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,EAC1B,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gCAAgC,GACtC,IAAI,
|
|
1
|
+
{"version":3,"file":"publish-file-failure.d.ts","sourceRoot":"","sources":["../src/publish-file-failure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,MAAM,qCAAqC,GAAG,UAAU,GAAG,UAAU,CAAC;AAC5E,MAAM,MAAM,wCAAwC,GAAG;IACrD,MAAM,EAAE,QAAQ,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,gCAAgC,GACxC,aAAa,GACb,aAAa,GACb,gBAAgB,GAChB,iBAAiB,GACjB,iBAAiB,GACjB,eAAe,GACf,eAAe,CAAC;AACpB,MAAM,MAAM,2BAA2B,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;AAC9E,MAAM,MAAM,kCAAkC,GAAG;IAC/C,KAAK,EAAE,gCAAgC,CAAC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,OAAO,EAAE,2BAA2B,CAAC;IACrC,aAAa,CAAC,EAAE,wCAAwC,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,gCAAgC,CAAC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,qBAAqB,CAAC,EAAE,gBAAgB,CAAC;IACzC,cAAc,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,wCAAwC,CAAC;CAC1D,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,mBAAmB,EAC1B,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gCAAgC,GACtC,IAAI,CAKN;AAED,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,2BAA2B,GACnC,WAAW,CAcb;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,OAAO,GACb,wCAAwC,CAG1C"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
2
|
export function rememberCreatedTarget(state, identity, phase) {
|
|
3
3
|
state.targetCreated = true;
|
|
4
|
-
state.targetIdentity = { dev: identity.dev, ino: identity.ino };
|
|
4
|
+
state.targetIdentity = { dev: Number(identity.dev), ino: Number(identity.ino) };
|
|
5
|
+
state.targetCleanupIdentity = { dev: identity.dev, ino: identity.ino };
|
|
5
6
|
state.phase = phase;
|
|
6
7
|
}
|
|
7
8
|
export function publicationFailure(error, state, cleanup) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,
|
|
1
|
+
{"version":3,"file":"publish-file.d.ts","sourceRoot":"","sources":["../src/publish-file.ts"],"names":[],"mappings":"AACA,OAAe,EAAoB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAI/D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EAC1B,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG7E,OAAO,EAML,KAAK,qCAAqC,EAC3C,MAAM,2BAA2B,CAAC;AAGnC,YAAY,EACV,2BAA2B,EAC3B,wCAAwC,EACxC,kCAAkC,EAClC,gCAAgC,EAChC,qCAAqC,GACtC,MAAM,2BAA2B,CAAC;AAEnC,MAAM,MAAM,4BAA4B,GACpC,cAAc,GACd,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;IAC3D,QAAQ,EAAE,KAAK,CAAC;IAChB,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAkBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE/D;AA2ND,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB,CAAC,EAAE,gBAAgB,CAAC;IAC1C,QAAQ,EAAE,4BAA4B,CAAC;IACvC,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,aAAa,CAAC,EAAE,gBAAgB,CAAC;CAClC,GAAG,OAAO,CAAC,0BAA0B,CAAC,CA4MtC"}
|
package/dist/publish-file.js
CHANGED
|
@@ -38,12 +38,12 @@ function directoryOpenFlags() {
|
|
|
38
38
|
return (fsSync.constants.O_RDONLY |
|
|
39
39
|
(typeof fsSync.constants.O_DIRECTORY === "number" ? fsSync.constants.O_DIRECTORY : 0));
|
|
40
40
|
}
|
|
41
|
-
async function openNativeParent(
|
|
41
|
+
async function openNativeParent(filePath) {
|
|
42
42
|
const parentPath = path.dirname(filePath);
|
|
43
43
|
const handle = await fs.open(parentPath, directoryOpenFlags());
|
|
44
44
|
try {
|
|
45
|
-
const pathname = await fs.lstat(parentPath);
|
|
46
|
-
const opened =
|
|
45
|
+
const pathname = await fs.lstat(parentPath, { bigint: true });
|
|
46
|
+
const opened = await handle.stat({ bigint: true });
|
|
47
47
|
if (pathname.isSymbolicLink() || !sameFileIdentity(pathname, opened)) {
|
|
48
48
|
throw new FsSafeError("path-mismatch", "publication parent changed while opening");
|
|
49
49
|
}
|
|
@@ -55,8 +55,10 @@ async function openNativeParent(binding, filePath) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
async function assertPinnedSourceCurrent(params) {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// Windows file indexes can exceed Number.MAX_SAFE_INTEGER, so publication
|
|
59
|
+
// fences must compare bigint stats instead of rounded numeric identities.
|
|
60
|
+
const opened = await params.handle.stat({ bigint: true });
|
|
61
|
+
const current = await fs.lstat(params.sourcePath, { bigint: true });
|
|
60
62
|
if (!opened.isFile() ||
|
|
61
63
|
current.isSymbolicLink() ||
|
|
62
64
|
!current.isFile() ||
|
|
@@ -90,13 +92,13 @@ async function copyPinnedSource(params) {
|
|
|
90
92
|
throw error;
|
|
91
93
|
}
|
|
92
94
|
let target;
|
|
93
|
-
const createdIdentity =
|
|
95
|
+
const createdIdentity = fsSync.fstatSync(nativeFd, { bigint: true });
|
|
94
96
|
rememberCreatedTarget(params.failure, createdIdentity, "copy-verify");
|
|
95
97
|
try {
|
|
96
98
|
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("exclusive-copy", params.targetPath, createdIdentity);
|
|
97
|
-
const identity = await fs.lstat(params.targetPath);
|
|
99
|
+
const identity = await fs.lstat(params.targetPath, { bigint: true });
|
|
98
100
|
target = await fs.open(params.targetPath, sourceOpenFlags());
|
|
99
|
-
const opened = await target.stat();
|
|
101
|
+
const opened = await target.stat({ bigint: true });
|
|
100
102
|
if (identity.isSymbolicLink() ||
|
|
101
103
|
!identity.isFile() ||
|
|
102
104
|
!sameFileIdentity(createdIdentity, identity) ||
|
|
@@ -108,7 +110,7 @@ async function copyPinnedSource(params) {
|
|
|
108
110
|
nativeFd = undefined;
|
|
109
111
|
return {
|
|
110
112
|
handle: target,
|
|
111
|
-
|
|
113
|
+
exactIdentity: opened,
|
|
112
114
|
digest: hashed.digest,
|
|
113
115
|
bytes: hashed.bytes,
|
|
114
116
|
};
|
|
@@ -124,10 +126,10 @@ async function copyPinnedSource(params) {
|
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
const target = await fs.open(params.targetPath, "wx+", 0o600);
|
|
127
|
-
const
|
|
128
|
-
rememberCreatedTarget(params.failure,
|
|
129
|
+
const exactIdentity = await target.stat({ bigint: true });
|
|
130
|
+
rememberCreatedTarget(params.failure, exactIdentity, "copy-verify");
|
|
129
131
|
try {
|
|
130
|
-
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("exclusive-copy", params.targetPath,
|
|
132
|
+
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("exclusive-copy", params.targetPath, exactIdentity);
|
|
131
133
|
const hash = createHash("sha256");
|
|
132
134
|
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
133
135
|
let position = 0;
|
|
@@ -148,7 +150,7 @@ async function copyPinnedSource(params) {
|
|
|
148
150
|
position += bytesRead;
|
|
149
151
|
}
|
|
150
152
|
await target.sync();
|
|
151
|
-
return { handle: target,
|
|
153
|
+
return { handle: target, exactIdentity, digest: hash.digest("hex"), bytes: position };
|
|
152
154
|
}
|
|
153
155
|
catch (error) {
|
|
154
156
|
await target.close().catch(() => undefined);
|
|
@@ -160,7 +162,7 @@ async function removeCreatedTargetIfUnchanged(targetPath, identity) {
|
|
|
160
162
|
return "unknown";
|
|
161
163
|
}
|
|
162
164
|
try {
|
|
163
|
-
const current = await fs.lstat(targetPath);
|
|
165
|
+
const current = await fs.lstat(targetPath, { bigint: true });
|
|
164
166
|
if (!current.isSymbolicLink() && sameFileIdentity(current, identity)) {
|
|
165
167
|
await fs.rm(targetPath);
|
|
166
168
|
return "removed";
|
|
@@ -209,31 +211,38 @@ export async function publishFileExclusive(params) {
|
|
|
209
211
|
label: "publication parent",
|
|
210
212
|
});
|
|
211
213
|
const sourceIdentity = await source.stat();
|
|
212
|
-
|
|
214
|
+
const sourceExactIdentity = await source.stat({ bigint: true });
|
|
215
|
+
const sourcePathExactIdentity = await fs.lstat(sourcePath, { bigint: true });
|
|
216
|
+
if (sourcePathExactIdentity.isSymbolicLink() ||
|
|
217
|
+
!sourcePathExactIdentity.isFile() ||
|
|
218
|
+
!sameFileIdentity(sourcePathExactIdentity, sourceExactIdentity) ||
|
|
213
219
|
(params.expectedSourceIdentity &&
|
|
214
|
-
!sameFileIdentity(params.expectedSourceIdentity,
|
|
220
|
+
!sameFileIdentity(params.expectedSourceIdentity, typeof params.expectedSourceIdentity.dev === "bigint" ||
|
|
221
|
+
typeof params.expectedSourceIdentity.ino === "bigint"
|
|
222
|
+
? sourceExactIdentity
|
|
223
|
+
: sourceIdentity))) {
|
|
215
224
|
throw new FsSafeError("path-mismatch", "publication source identity did not match");
|
|
216
225
|
}
|
|
217
226
|
await parent.assertCurrent();
|
|
218
|
-
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity:
|
|
227
|
+
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity: sourceExactIdentity });
|
|
219
228
|
const native = params.strategy === "rename-noreplace"
|
|
220
229
|
? requireNativeBinding()
|
|
221
230
|
: getNativeBinding();
|
|
222
231
|
if (native) {
|
|
223
|
-
sourceNativeParent = await openNativeParent(
|
|
224
|
-
targetNativeParent = await openNativeParent(
|
|
232
|
+
sourceNativeParent = await openNativeParent(sourcePath);
|
|
233
|
+
targetNativeParent = await openNativeParent(targetPath);
|
|
225
234
|
}
|
|
226
235
|
if (params.strategy === "rename-noreplace") {
|
|
227
236
|
const binding = requireNativeBinding();
|
|
228
237
|
binding.renameNoReplace(sourceNativeParent.handle.fd, sourceNativeParent.basename, targetNativeParent.handle.fd, targetNativeParent.basename);
|
|
229
|
-
rememberCreatedTarget(failure,
|
|
238
|
+
rememberCreatedTarget(failure, sourceExactIdentity, "rename-verify");
|
|
230
239
|
// A failed post-rename fence must not delete the only remaining name.
|
|
231
240
|
failure.preserveTarget = true;
|
|
232
|
-
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("rename-noreplace", targetPath,
|
|
233
|
-
const
|
|
234
|
-
if (
|
|
235
|
-
!
|
|
236
|
-
!sameFileIdentity(
|
|
241
|
+
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("rename-noreplace", targetPath, sourceExactIdentity);
|
|
242
|
+
const targetExactIdentity = await fs.lstat(targetPath, { bigint: true });
|
|
243
|
+
if (targetExactIdentity.isSymbolicLink() ||
|
|
244
|
+
!targetExactIdentity.isFile() ||
|
|
245
|
+
!sameFileIdentity(targetExactIdentity, sourceExactIdentity)) {
|
|
237
246
|
throw new FsSafeError("path-mismatch", "no-replace publication target changed");
|
|
238
247
|
}
|
|
239
248
|
try {
|
|
@@ -246,6 +255,7 @@ export async function publishFileExclusive(params) {
|
|
|
246
255
|
}
|
|
247
256
|
}
|
|
248
257
|
syncNativeFileBestEffort(sourceNativeParent.handle.fd);
|
|
258
|
+
const targetIdentity = await fs.lstat(targetPath);
|
|
249
259
|
return {
|
|
250
260
|
method: "rename-noreplace",
|
|
251
261
|
identity: targetIdentity,
|
|
@@ -264,15 +274,16 @@ export async function publishFileExclusive(params) {
|
|
|
264
274
|
else {
|
|
265
275
|
await fs.link(sourcePath, targetPath);
|
|
266
276
|
}
|
|
267
|
-
rememberCreatedTarget(failure,
|
|
268
|
-
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("hardlink", targetPath,
|
|
269
|
-
const
|
|
270
|
-
if (
|
|
271
|
-
!
|
|
272
|
-
!sameFileIdentity(
|
|
277
|
+
rememberCreatedTarget(failure, sourceExactIdentity, "hardlink-verify");
|
|
278
|
+
await getFsSafeTestHooks()?.afterPublishTargetCreated?.("hardlink", targetPath, sourceExactIdentity);
|
|
279
|
+
const targetExactIdentity = await fs.lstat(targetPath, { bigint: true });
|
|
280
|
+
if (targetExactIdentity.isSymbolicLink() ||
|
|
281
|
+
!targetExactIdentity.isFile() ||
|
|
282
|
+
!sameFileIdentity(targetExactIdentity, sourceExactIdentity)) {
|
|
273
283
|
throw new FsSafeError("path-mismatch", "hardlink publication target changed");
|
|
274
284
|
}
|
|
275
|
-
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity:
|
|
285
|
+
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity: sourceExactIdentity });
|
|
286
|
+
const targetIdentity = await fs.lstat(targetPath);
|
|
276
287
|
return {
|
|
277
288
|
method: "hardlink",
|
|
278
289
|
identity: targetIdentity,
|
|
@@ -303,19 +314,21 @@ export async function publishFileExclusive(params) {
|
|
|
303
314
|
failure,
|
|
304
315
|
});
|
|
305
316
|
target = copied.handle;
|
|
306
|
-
targetIdentity =
|
|
317
|
+
targetIdentity = await target.stat();
|
|
307
318
|
const targetPathStat = await fs.lstat(targetPath);
|
|
319
|
+
const targetPathExactStat = await fs.lstat(targetPath, { bigint: true });
|
|
308
320
|
const copiedBack = await hashFileHandle(target, native);
|
|
309
321
|
const sourceAfter = await hashFileHandle(source, native);
|
|
310
322
|
if (targetPathStat.isSymbolicLink() ||
|
|
311
|
-
|
|
323
|
+
targetPathExactStat.isSymbolicLink() ||
|
|
324
|
+
!sameFileIdentity(targetPathExactStat, copied.exactIdentity) ||
|
|
312
325
|
copiedBack.bytes !== copied.bytes ||
|
|
313
326
|
copiedBack.digest !== copied.digest ||
|
|
314
327
|
sourceAfter.bytes !== copied.bytes ||
|
|
315
328
|
sourceAfter.digest !== copied.digest) {
|
|
316
329
|
throw new FsSafeError("path-mismatch", "exclusive publication copy failed content fencing");
|
|
317
330
|
}
|
|
318
|
-
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity:
|
|
331
|
+
await assertPinnedSourceCurrent({ sourcePath, handle: source, identity: sourceExactIdentity });
|
|
319
332
|
const directorySync = await syncPublishedParent({
|
|
320
333
|
parent,
|
|
321
334
|
failure,
|
|
@@ -339,7 +352,7 @@ export async function publishFileExclusive(params) {
|
|
|
339
352
|
const preserveSyncFailure = failure.phase === "directory-sync" && params.onSyncFailure === "preserve";
|
|
340
353
|
const cleanup = failure.preserveTarget || preserveSyncFailure
|
|
341
354
|
? "preserved"
|
|
342
|
-
: await removeCreatedTargetIfUnchanged(targetPath, failure.
|
|
355
|
+
: await removeCreatedTargetIfUnchanged(targetPath, failure.targetCleanupIdentity);
|
|
343
356
|
throw publicationFailure(error, failure, cleanup);
|
|
344
357
|
}
|
|
345
358
|
finally {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
type ReadOpenFlagConstants = Pick<typeof fs.constants, "O_RDONLY"> & Partial<Pick<typeof fs.constants, "O_NOFOLLOW" | "O_NONBLOCK">>;
|
|
3
|
+
export declare function resolveReadOpenFlags(options?: {
|
|
4
|
+
constants?: ReadOpenFlagConstants;
|
|
5
|
+
followSymlinks?: boolean;
|
|
6
|
+
}): number;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=read-open-flags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-open-flags.d.ts","sourceRoot":"","sources":["../src/read-open-flags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAEzB,KAAK,qBAAqB,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,GAChE,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,SAAS,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC;AAElE,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAC7C,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,GAAG,MAAM,CAaT"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
export function resolveReadOpenFlags(options) {
|
|
3
|
+
const constants = options?.constants ?? fs.constants;
|
|
4
|
+
const noFollow = process.platform !== "win32" &&
|
|
5
|
+
options?.followSymlinks !== true &&
|
|
6
|
+
typeof constants.O_NOFOLLOW === "number"
|
|
7
|
+
? constants.O_NOFOLLOW
|
|
8
|
+
: 0;
|
|
9
|
+
const nonBlocking = process.platform !== "win32" && typeof constants.O_NONBLOCK === "number"
|
|
10
|
+
? constants.O_NONBLOCK
|
|
11
|
+
: 0;
|
|
12
|
+
return constants.O_RDONLY | noFollow | nonBlocking;
|
|
13
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"regular-file.d.ts","sourceRoot":"","sources":["../src/regular-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,MAAM,MAAM,SAAS,CAAC;AAY7B,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAExF,KAAK,8BAA8B,GAAG,IAAI,CACxC,OAAO,MAAM,CAAC,SAAS,EACvB,UAAU,GAAG,SAAS,GAAG,UAAU,CACpC,GACC,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;AAEvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,CAAC;AAEF,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,8BAAiD,GAC3D,MAAM,CAQR;AAaD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CActF;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAc3E;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAyD3C;AA6DD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,CAAC;CACb,CA6BA;AAmBD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDxF;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,CA2D7E"}
|
package/dist/regular-file.js
CHANGED
|
@@ -6,6 +6,7 @@ import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { sameFileIdentity } from "./file-identity.js";
|
|
8
8
|
import { isNotFoundPathError } from "./path.js";
|
|
9
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
9
10
|
import { assertNoSymlinkParents, assertNoSymlinkParentsSync } from "./symlink-parents.js";
|
|
10
11
|
export function resolveRegularFileAppendFlags(constants = fsSync.constants) {
|
|
11
12
|
const noFollow = constants.O_NOFOLLOW;
|
|
@@ -14,12 +15,6 @@ export function resolveRegularFileAppendFlags(constants = fsSync.constants) {
|
|
|
14
15
|
constants.O_WRONLY |
|
|
15
16
|
(typeof noFollow === "number" ? noFollow : 0));
|
|
16
17
|
}
|
|
17
|
-
function resolveRegularFileReadFlags() {
|
|
18
|
-
return (fsSync.constants.O_RDONLY |
|
|
19
|
-
(typeof fsSync.constants.O_NOFOLLOW === "number" && process.platform !== "win32"
|
|
20
|
-
? fsSync.constants.O_NOFOLLOW
|
|
21
|
-
: 0));
|
|
22
|
-
}
|
|
23
18
|
function regularFileTooLargeError(filePath, maxBytes, cause) {
|
|
24
19
|
return new FsSafeError("too-large", `File exceeds ${maxBytes} bytes: ${filePath}`, { cause });
|
|
25
20
|
}
|
|
@@ -72,7 +67,7 @@ export async function readRegularFile(params) {
|
|
|
72
67
|
}
|
|
73
68
|
let handle;
|
|
74
69
|
try {
|
|
75
|
-
handle = await fs.open(params.filePath,
|
|
70
|
+
handle = await fs.open(params.filePath, resolveReadOpenFlags());
|
|
76
71
|
}
|
|
77
72
|
catch (err) {
|
|
78
73
|
if (isNotFoundPathError(err)) {
|
|
@@ -180,7 +175,7 @@ export function readRegularFileSync(params) {
|
|
|
180
175
|
}
|
|
181
176
|
let fd;
|
|
182
177
|
try {
|
|
183
|
-
fd = fsSync.openSync(params.filePath,
|
|
178
|
+
fd = fsSync.openSync(params.filePath, resolveReadOpenFlags());
|
|
184
179
|
}
|
|
185
180
|
catch (error) {
|
|
186
181
|
if (isNotFoundPathError(error)) {
|
package/dist/root-impl.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAO7D,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,KAAK,oBAAoB,EAG1B,MAAM,mBAAmB,CAAC;AAY3B,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAO7D,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,KAAK,oBAAoB,EAG1B,MAAM,mBAAmB,CAAC;AAY3B,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAyB9E,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAY,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIpF,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,kCAAkC,EAAE,MAAM,sBAAsB,CAAC;AAC1E,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,oBAAoB,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,oBAAoB,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,CAAC;AAChD,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE/D,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,kBAAkB,CAAC;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAChC,YAAY,EACZ,WAAW,GAAG,UAAU,GAAG,iBAAiB,GAAG,UAAU,CAC1D,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AAEhE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,gBAAgB,CAAC,GAAG;IACzG,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG;IAC7F,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC,GAAG;IAClG,eAAe,CAAC,EAAE,cAAc,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG;IACpD,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;AACpE,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,oBAAoB,EAAE,WAAW,CAAC,CAAC;AAE5E,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG;IACjD,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG;IAClE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;AACpE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAC;AA6BnE,eAAO,MAAM,sBAAsB,QAAmB,CAAC;AA+IvD,MAAM,WAAW,IAAI;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAEhC,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3E,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5E,QAAQ,CACN,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAE,GACxD,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAClB,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAE,GACxD,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/E,MAAM,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE,YAAY,CACV,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAC/B,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,UAAU,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,KAAK,CACH,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,CACJ,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,SAAS,CACP,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,UAAU,CACR,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3F,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,KAAK,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACnF,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,aAAa,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAClF,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,qBAAqB,CAAC,aAAa,CAAC,CAAC;CAC5F;AAyRD,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,YAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAEf;AAqFD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAOtB;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAG3F;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,oBAAoB,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC"}
|
package/dist/root-impl.js
CHANGED
|
@@ -16,6 +16,7 @@ import { validatePinnedOperationPayload } from "./pinned-operation.js";
|
|
|
16
16
|
import { assertNoPathAliasEscape, PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
17
17
|
import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isPathInside, isSymlinkOpenError, } from "./path.js";
|
|
18
18
|
import { readOpenedFileSafely } from "./read-opened-file.js";
|
|
19
|
+
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
19
20
|
import { resolveRootPath } from "./root-path.js";
|
|
20
21
|
import { assertRootIdentityCurrent, assertValidRootDestinationPath, assertValidRootRelativePath, ensureTrailingSep, expandRelativePathWithHome, resolvePathInRoot, resolveRootContext, } from "./root-context.js";
|
|
21
22
|
import { fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, normalizePinnedWriteError, normalizeRemoveGuardError, normalizeRemovePathError, outsideWorkspaceError, } from "./root-errors.js";
|
|
@@ -31,11 +32,8 @@ function logWarn(message) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
const SUPPORTS_NOFOLLOW = process.platform !== "win32" && "O_NOFOLLOW" in fsConstants;
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
(SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0) |
|
|
37
|
-
NONBLOCK_OPEN_FLAG;
|
|
38
|
-
const OPEN_READ_FOLLOW_FLAGS = fsConstants.O_RDONLY | NONBLOCK_OPEN_FLAG;
|
|
35
|
+
const OPEN_READ_FLAGS = resolveReadOpenFlags();
|
|
36
|
+
const OPEN_READ_FOLLOW_FLAGS = resolveReadOpenFlags({ followSymlinks: true });
|
|
39
37
|
const OPEN_WRITE_EXISTING_FLAGS = fsConstants.O_WRONLY | (SUPPORTS_NOFOLLOW ? fsConstants.O_NOFOLLOW : 0);
|
|
40
38
|
const OPEN_WRITE_CREATE_FLAGS = fsConstants.O_WRONLY |
|
|
41
39
|
fsConstants.O_CREAT |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -155,7 +155,7 @@
|
|
|
155
155
|
"devDependencies": {
|
|
156
156
|
"@emnapi/runtime": "2.0.0-alpha.3",
|
|
157
157
|
"@napi-rs/cli": "3.8.2",
|
|
158
|
-
"@types/node": "^26.
|
|
158
|
+
"@types/node": "^26.2.0",
|
|
159
159
|
"@vitest/coverage-v8": "4.1.10",
|
|
160
160
|
"fast-check": "^4.9.0",
|
|
161
161
|
"istanbul-lib-coverage": "3.2.2",
|