@openclaw/fs-safe 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +3 -2
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +12 -1
- package/dist/archive-errors.d.ts +1 -0
- package/dist/archive-errors.d.ts.map +1 -1
- package/dist/archive-errors.js +4 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +6 -3
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +43 -36
- package/dist/archive-tar-meta.d.ts.map +1 -1
- package/dist/archive-tar-meta.js +8 -0
- package/dist/archive-tar-runtime.d.ts +1 -0
- package/dist/archive-tar-runtime.d.ts.map +1 -1
- package/dist/archive-tar-runtime.js +11 -0
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +1 -0
- package/dist/archive-zip-entry.d.ts +6 -1
- package/dist/archive-zip-entry.d.ts.map +1 -1
- package/dist/archive-zip-entry.js +11 -1
- package/dist/archive-zip-integrity.d.ts +2 -1
- package/dist/archive-zip-integrity.d.ts.map +1 -1
- package/dist/archive-zip-integrity.js +12 -2
- package/dist/archive-zip-preflight.d.ts.map +1 -1
- package/dist/archive-zip-preflight.js +8 -2
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +6 -5
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +2 -11
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +31 -16
- 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 +23 -6
- package/dist/private-temp-workspace.d.ts.map +1 -1
- package/dist/private-temp-workspace.js +45 -4
- 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-error.d.ts +2 -0
- package/dist/read-error.d.ts.map +1 -0
- package/dist/read-error.js +11 -0
- 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 +6 -6
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +10 -3
- package/dist/secret-read-async.d.ts.map +1 -1
- package/dist/secret-read-async.js +5 -1
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +9 -8
- package/dist/trash.d.ts.map +1 -1
- package/dist/trash.js +10 -1
- package/docs/advanced.md +6 -0
- package/docs/archive.md +2 -2
- package/docs/errors.md +5 -3
- package/docs/file-store.md +4 -0
- package/docs/secret-file.md +4 -0
- package/docs/temp.md +5 -0
- package/docs/types.md +2 -2
- package/package.json +10 -3
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":"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"}
|
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 |
|
|
@@ -86,7 +84,6 @@ async function openVerifiedLocalFile(filePath, options) {
|
|
|
86
84
|
if (!preOpenStat.isFile() && !preOpenStat.isSymbolicLink()) {
|
|
87
85
|
throw new FsSafeError("not-file", "not a file");
|
|
88
86
|
}
|
|
89
|
-
await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
|
|
90
87
|
}
|
|
91
88
|
catch (err) {
|
|
92
89
|
if (err instanceof FsSafeError) {
|
|
@@ -94,6 +91,9 @@ async function openVerifiedLocalFile(filePath, options) {
|
|
|
94
91
|
}
|
|
95
92
|
// ENOENT and other lstat errors: fall through and let fs.open handle.
|
|
96
93
|
}
|
|
94
|
+
if (preOpenStat) {
|
|
95
|
+
await fsSafeTestHooks?.afterPreOpenLstat?.(filePath);
|
|
96
|
+
}
|
|
97
97
|
let handle;
|
|
98
98
|
try {
|
|
99
99
|
const openFlags = options?.symlinks === "follow-within-root"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,6BAA6B,QAAY,CAAC;AACvD,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;
|
|
1
|
+
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,6BAA6B,QAAY,CAAC;AACvD,eAAO,MAAM,uBAAuB,MAAQ,CAAC;AAC7C,eAAO,MAAM,wBAAwB,MAAQ,CAAC;AAE9C,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAyIF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,CAQR;AAED,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,MAAM,GAAG,SAAS,CAcpB;AAoID,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAkEF,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAKxF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAezF"}
|
package/dist/secret-file.js
CHANGED
|
@@ -102,6 +102,15 @@ function readSecretFileOutcomeSync(filePath, label, options = {}) {
|
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
104
|
try {
|
|
105
|
+
if (!sameFileIdentity(previewStat, opened.stat)) {
|
|
106
|
+
const error = new FsSafeError("path-mismatch", "security validation failed");
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
code: "path-mismatch",
|
|
110
|
+
error,
|
|
111
|
+
message: `Failed to read ${label} file at ${resolvedPath}: ${String(error)}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
105
114
|
const raw = readFileDescriptorBoundedSync(opened.fd, maxBytes).toString("utf8");
|
|
106
115
|
const secret = raw.trim();
|
|
107
116
|
if (!secret) {
|
|
@@ -117,9 +126,7 @@ function readSecretFileOutcomeSync(filePath, label, options = {}) {
|
|
|
117
126
|
const normalized = normalizeSecretReadError(error);
|
|
118
127
|
return {
|
|
119
128
|
ok: false,
|
|
120
|
-
code: error instanceof FsSafeError
|
|
121
|
-
? "too-large"
|
|
122
|
-
: "invalid-path",
|
|
129
|
+
code: error instanceof FsSafeError ? error.code : "read-failed",
|
|
123
130
|
error: normalized,
|
|
124
131
|
message: `Failed to read ${label} file at ${resolvedPath}: ${String(normalized)}`,
|
|
125
132
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"secret-read-async.d.ts","sourceRoot":"","sources":["../src/secret-read-async.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AA0F1B,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7B"}
|
|
@@ -68,7 +68,11 @@ async function readSecretFileOutcome(filePath, label, options) {
|
|
|
68
68
|
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
69
69
|
return {
|
|
70
70
|
ok: false,
|
|
71
|
-
code: error instanceof FsSafeError
|
|
71
|
+
code: error instanceof FsSafeError
|
|
72
|
+
? error.code
|
|
73
|
+
: pathErrorCode(error) === "not-found"
|
|
74
|
+
? "not-found"
|
|
75
|
+
: "read-failed",
|
|
72
76
|
error: normalized,
|
|
73
77
|
message: `Failed to read ${label} file at ${resolvedPath}: ${String(normalized)}`,
|
|
74
78
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"symlink-parents.d.ts","sourceRoot":"","sources":["../src/symlink-parents.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAyBF,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,6BAA6B,GACpC,IAAI,CA6BN"}
|
package/dist/symlink-parents.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { hasNodeErrorCode, isPathRelativeEscape } from "./path.js";
|
|
5
6
|
function resolvePathWalk(params) {
|
|
6
7
|
const root = path.resolve(params.rootDir);
|
|
7
8
|
const target = path.resolve(params.targetPath);
|
|
@@ -26,7 +27,7 @@ export async function assertNoSymlinkParents(params) {
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
let current = walk.root;
|
|
29
|
-
for (const segment of walk.segments) {
|
|
30
|
+
for (const [index, segment] of walk.segments.entries()) {
|
|
30
31
|
current = path.join(current, segment);
|
|
31
32
|
try {
|
|
32
33
|
const stat = await fs.lstat(current);
|
|
@@ -36,12 +37,12 @@ export async function assertNoSymlinkParents(params) {
|
|
|
36
37
|
}
|
|
37
38
|
throw new Error(formatUnsafePath(params, current));
|
|
38
39
|
}
|
|
39
|
-
if (params.requireDirectories && !stat.isDirectory()) {
|
|
40
|
-
throw new
|
|
40
|
+
if ((params.requireDirectories || index < walk.segments.length - 1) && !stat.isDirectory()) {
|
|
41
|
+
throw new FsSafeError("not-file", `${params.messagePrefix ?? "Path"} must traverse directories: ${current}`);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
catch (err) {
|
|
44
|
-
if (
|
|
45
|
+
if (hasNodeErrorCode(err, "ENOENT") && params.allowMissing !== false) {
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
throw err;
|
|
@@ -54,7 +55,7 @@ export function assertNoSymlinkParentsSync(params) {
|
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
let current = walk.root;
|
|
57
|
-
for (const segment of walk.segments) {
|
|
58
|
+
for (const [index, segment] of walk.segments.entries()) {
|
|
58
59
|
current = path.join(current, segment);
|
|
59
60
|
try {
|
|
60
61
|
const stat = fsSync.lstatSync(current);
|
|
@@ -64,8 +65,8 @@ export function assertNoSymlinkParentsSync(params) {
|
|
|
64
65
|
}
|
|
65
66
|
throw new Error(formatUnsafePath(params, current));
|
|
66
67
|
}
|
|
67
|
-
if (params.requireDirectories && !stat.isDirectory()) {
|
|
68
|
-
throw new
|
|
68
|
+
if ((params.requireDirectories || index < walk.segments.length - 1) && !stat.isDirectory()) {
|
|
69
|
+
throw new FsSafeError("not-file", `${params.messagePrefix ?? "Path"} must traverse directories: ${current}`);
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
catch (err) {
|
package/dist/trash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;
|
|
1
|
+
{"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../src/trash.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAkLF,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,CAAC,CAcjB"}
|
package/dist/trash.js
CHANGED
|
@@ -117,6 +117,15 @@ function reserveTrashDestination(trashDir, base, timestamp) {
|
|
|
117
117
|
}
|
|
118
118
|
return resolveContainedPath(container, base);
|
|
119
119
|
}
|
|
120
|
+
function copyTrashTargetSync(target, dest) {
|
|
121
|
+
if (target.stat.isSymbolicLink()) {
|
|
122
|
+
const linkTarget = fs.readlinkSync(target.path);
|
|
123
|
+
assertTrashTargetGuard(target);
|
|
124
|
+
fs.symlinkSync(linkTarget, dest);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
fs.cpSync(target.path, dest, { recursive: true, force: false, errorOnExist: true });
|
|
128
|
+
}
|
|
120
129
|
function movePathToDestination(target, dest) {
|
|
121
130
|
getFsSafeTestHooks()?.beforeTrashMove?.(target.path, dest);
|
|
122
131
|
assertTrashTargetGuard(target);
|
|
@@ -134,7 +143,7 @@ function movePathToDestination(target, dest) {
|
|
|
134
143
|
}
|
|
135
144
|
try {
|
|
136
145
|
assertTrashTargetGuard(target);
|
|
137
|
-
|
|
146
|
+
copyTrashTargetSync(target, dest);
|
|
138
147
|
assertTrashTargetGuard(target);
|
|
139
148
|
guardedRmSync({ target: target.path, recursive: true, force: false, verifyAfter: false });
|
|
140
149
|
return true;
|
package/docs/advanced.md
CHANGED
|
@@ -82,6 +82,12 @@ try {
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
For the symlink-parent guards, `allowMissing` defaults to `true` and permits the
|
|
86
|
+
walk to stop only at an actually absent suffix. When an existing non-directory
|
|
87
|
+
component is followed by another segment, both helpers throw
|
|
88
|
+
`FsSafeError("not-file")` before the platform can expose that state as POSIX
|
|
89
|
+
`ENOTDIR` or Windows `ENOENT`.
|
|
90
|
+
|
|
85
91
|
### Local roots and file URLs
|
|
86
92
|
|
|
87
93
|
| Export | Page | Notes |
|
package/docs/archive.md
CHANGED
|
@@ -148,7 +148,7 @@ codes remain `"destination-not-directory"`, `"destination-symlink"`, and
|
|
|
148
148
|
|
|
149
149
|
## What it defends against
|
|
150
150
|
|
|
151
|
-
- **Path traversal:** entries with `..`, absolute paths, NUL bytes, or Windows drive-relative segments such as `C:secret` and `nested/C:secret` are rejected (`ArchiveSecurityError`).
|
|
151
|
+
- **Path traversal:** entries with `..`, absolute paths, NUL bytes, or Windows drive-relative segments such as `C:secret` and `nested/C:secret` are rejected (`ArchiveSecurityError`). On Windows, path segments containing `:` are also rejected as alternate data stream names before either backend writes to the filesystem.
|
|
152
152
|
- **Symlink/hardlink entries:** rejected by default. Some archives ship symlink/hardlink entries that point outside the destination once resolved; `extractArchive` does not follow them.
|
|
153
153
|
- **Ambiguous output names:** duplicate names and distinct names that collide after `stripComponents`, case normalization, or Unicode normalization are rejected instead of relying on backend- or volume-specific overwrite order.
|
|
154
154
|
- **TOCTOU during merge:** extraction first writes to a private temp dir, then merges into `destDir` using the same boundary checks as `root().write()`. Destination symlink swaps are checked with the selected platform mechanism; non-Linux routes retain the best-effort race window documented in the [security model](security-model.md#containment-guarantees-by-platform).
|
|
@@ -255,7 +255,7 @@ import {
|
|
|
255
255
|
} from "@openclaw/fs-safe/archive";
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
-
- `validateArchiveEntryPath(raw, opts)` — throws `ArchiveSecurityError` for `..`, absolute, NUL-containing, drive-relative, or otherwise unsafe entry paths.
|
|
258
|
+
- `validateArchiveEntryPath(raw, opts)` — throws `ArchiveSecurityError` for `..`, absolute, NUL-containing, drive-relative, or otherwise unsafe entry paths, including alternate data stream names on Windows.
|
|
259
259
|
- `normalizeArchiveEntryPath(raw)` — converts backslashes in the entry path to forward slashes.
|
|
260
260
|
- `stripArchivePath(entryPath, n)` — strip the leading N path components, returning `null` if not enough remain.
|
|
261
261
|
- `resolveArchiveOutputPath({ destDir, entryPath })` — combines the entry path with the destination, after validation.
|
package/docs/errors.md
CHANGED
|
@@ -62,8 +62,8 @@ filesystem boundary violation. Branch on the specific code when the distinction
|
|
|
62
62
|
between those operational outcomes matters.
|
|
63
63
|
|
|
64
64
|
The operational set is exactly `helper-failed`, `helper-unavailable`,
|
|
65
|
-
`not-empty`, `not-found`, `not-removable`, `permission-unverified`, `
|
|
66
|
-
and `unsupported-platform`. Every other current `FsSafeErrorCode`, including
|
|
65
|
+
`not-empty`, `not-found`, `not-removable`, `permission-unverified`, `read-failed`,
|
|
66
|
+
`timeout`, and `unsupported-platform`. Every other current `FsSafeErrorCode`, including
|
|
67
67
|
`store-reentrant-update`, is categorized as `policy`.
|
|
68
68
|
|
|
69
69
|
## Code union
|
|
@@ -87,6 +87,7 @@ type FsSafeErrorCode =
|
|
|
87
87
|
| "path-alias"
|
|
88
88
|
| "path-mismatch"
|
|
89
89
|
| "permission-unverified"
|
|
90
|
+
| "read-failed"
|
|
90
91
|
| "secret-exists"
|
|
91
92
|
| "store-reentrant-update"
|
|
92
93
|
| "symlink"
|
|
@@ -108,7 +109,7 @@ type FsSafeErrorCode =
|
|
|
108
109
|
| `insecure-permissions` | A secure file or path permission check found a mode/ACL that allows broader access than requested. | File or directory is group/world writable/readable; Windows ACL grants broad read. |
|
|
109
110
|
| `invalid-path` | Input was empty, contained NUL, was an unparseable URL, or otherwise unusable. | Caller didn't validate input; input was a network path on Windows, a drive-relative segment in a portable relative path or store key, or a leading drive-relative spelling such as `C:name` used as a Root destination. Existing-object Root lookups retain legal POSIX drive-like names. |
|
|
110
111
|
| `not-empty` | `remove()` on a non-empty directory. | Use `replaceDirectoryAtomic` or remove children first. |
|
|
111
|
-
| `not-file` | Read or copy targeted a non-regular file. | Target was a directory, FIFO, socket, device. |
|
|
112
|
+
| `not-file` | Read or copy targeted a non-regular file, or a path walk found a non-directory ancestor. | Target was a directory, FIFO, socket, device, or an existing file was followed by another segment. |
|
|
112
113
|
| `not-found` | The target does not exist (or its parent does not, with `mkdir: false`). | Typical missing-file case. |
|
|
113
114
|
| `not-owned` | A secure file owner check failed. | File is owned by another UID. |
|
|
114
115
|
| `not-removable` | `remove()` couldn't `unlink`/`rmdir` for a reason other than non-empty. | Permissions, device busy, immutable bit. |
|
|
@@ -116,6 +117,7 @@ type FsSafeErrorCode =
|
|
|
116
117
|
| `path-alias` | A path alias check failed (e.g. canonical-real-path moved out of the root). | Symlink resolution lands outside the root. |
|
|
117
118
|
| `path-mismatch` | Post-open identity check failed: the opened fd does not match the resolved path. | TOCTOU — something else swapped the path between resolve and open. |
|
|
118
119
|
| `permission-unverified` | A secure file check could not verify required permissions. | Windows ACL inspection failed; POSIX ownership/mode was unavailable. |
|
|
120
|
+
| `read-failed` | A validated file could not be read because of an operational filesystem or device failure. | I/O error, media failure, or another runtime read failure; inspect `cause`. |
|
|
119
121
|
| `secret-exists` | `createSecretFileAtomic()` found an existing final path. | First-writer-wins secret creation lost a race or the credential was already initialized. |
|
|
120
122
|
| `store-reentrant-update` | A `JsonStore.update()` callback called `update()` or `updateOr()` for the same canonical store before returning. | Reentrant mutation would deadlock or lose an update; return the complete next value from the outer callback. |
|
|
121
123
|
| `symlink` | Path component is a symlink, policy is `reject`. | Caller followed a symlink they shouldn't have, or `symlinks: "reject"` is set. |
|
package/docs/file-store.md
CHANGED
|
@@ -138,6 +138,10 @@ type FileStoreWriteOptions = {
|
|
|
138
138
|
## Reads
|
|
139
139
|
|
|
140
140
|
`open`, `read`, `readBytes`, `readText`, and `readJson` delegate to a fresh `Root` with `hardlinks: "reject"` and the store's `maxBytes`. Same return shapes as `Root`.
|
|
141
|
+
Async and sync reads both report `not-file` when the key names a directory;
|
|
142
|
+
stable hardlinks and symlinks retain `hardlink` and `symlink`. The `IfExists`
|
|
143
|
+
methods return their nullish result only for `not-found`. Operational filesystem
|
|
144
|
+
read failures use `read-failed` with the Node error retained in `cause`.
|
|
141
145
|
|
|
142
146
|
## `remove(rel)` / `exists(rel)`
|
|
143
147
|
|
package/docs/secret-file.md
CHANGED
|
@@ -89,6 +89,10 @@ credential must also fail on broad permissions or unexpected ownership.
|
|
|
89
89
|
the same pinned-handle validation, byte cap, trimming, error codes, and strict
|
|
90
90
|
versus missing-is-undefined naming semantics.
|
|
91
91
|
|
|
92
|
+
If an already validated descriptor fails while reading, both readers throw an
|
|
93
|
+
operational `FsSafeError` with `code: "read-failed"`; inspect `cause` for the
|
|
94
|
+
underlying Node filesystem code such as `EIO`.
|
|
95
|
+
|
|
92
96
|
Use the async strict reader when a service cannot start safely without the
|
|
93
97
|
credential:
|
|
94
98
|
|
package/docs/temp.md
CHANGED
|
@@ -45,6 +45,11 @@ await runBuild(workspace.dir, inputPath);
|
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
`write` writes at `mode` (default `0o600`); `writeText` and `writeJson` are convenience wrappers for the common scratch-file shapes; `copyIn` ingests an absolute source path through the same atomic-rename machinery as `Root.copyIn`. `read` is a small accessor that reads back any file you wrote into the workspace.
|
|
48
|
+
Both the async and sync `read` methods throw `FsSafeError("not-file")` when the
|
|
49
|
+
named leaf is a directory or another non-regular target, and preserve the
|
|
50
|
+
`not-found`, `hardlink`, or `symlink` code for those stable target states.
|
|
51
|
+
Operational filesystem read failures use `read-failed` with the Node error in
|
|
52
|
+
`cause`.
|
|
48
53
|
|
|
49
54
|
`store` is a `fileStore({ rootDir: workspace.dir, private: true })` handle. Use
|
|
50
55
|
it when you want the richer store surface, including `writeStream`, `exists`,
|
package/docs/types.md
CHANGED
|
@@ -171,14 +171,14 @@ type FsSafeErrorCode =
|
|
|
171
171
|
| "helper-unavailable" | "insecure-permissions" | "invalid-path"
|
|
172
172
|
| "not-empty" | "not-file" | "not-found" | "not-owned"
|
|
173
173
|
| "not-removable" | "outside-workspace" | "path-alias"
|
|
174
|
-
| "path-mismatch" | "permission-unverified" | "secret-exists"
|
|
174
|
+
| "path-mismatch" | "permission-unverified" | "read-failed" | "secret-exists"
|
|
175
175
|
| "store-reentrant-update" | "symlink"
|
|
176
176
|
| "timeout" | "too-large" | "unsupported-platform";
|
|
177
177
|
```
|
|
178
178
|
|
|
179
179
|
Closed union you switch on. See the [Errors](errors.md) reference for what each one means.
|
|
180
180
|
|
|
181
|
-
`FsSafeError.category` is `"policy"` for unsafe input or target state rejected by a safety policy and `"operational"` for routine filesystem outcomes or environment/runtime failures. `not-found`, `not-empty`,
|
|
181
|
+
`FsSafeError.category` is `"policy"` for unsafe input or target state rejected by a safety policy and `"operational"` for routine filesystem outcomes or environment/runtime failures. `not-found`, `not-empty`, `not-removable`, and `read-failed` are operational.
|
|
182
182
|
|
|
183
183
|
## See also
|
|
184
184
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -129,6 +129,8 @@
|
|
|
129
129
|
"prepack": "node scripts/prepack-build.mjs",
|
|
130
130
|
"test": "vitest run",
|
|
131
131
|
"test:coverage": "vitest run --coverage",
|
|
132
|
+
"test:coverage:collect": "vitest run --coverage --coverage.reporter=json --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 --coverage.thresholds.statements=0 --coverage.thresholds.branches=0",
|
|
133
|
+
"test:coverage:merge": "node scripts/merge-coverage.mjs",
|
|
132
134
|
"test:security": "vitest run test/fs-safe.test.ts test/read-boundary-bypass.test.ts test/write-boundary-bypass.test.ts test/additional-boundary-bypass.test.ts test/adversarial-boundary-payloads.test.ts",
|
|
133
135
|
"check": "pnpm lint:file-size && pnpm lint:fs-boundary && pnpm build && pnpm docs:check && pnpm test && node scripts/check-pack.mjs",
|
|
134
136
|
"docs:check": "node scripts/check-doc-examples.mjs",
|
|
@@ -151,12 +153,17 @@
|
|
|
151
153
|
"tar": "7.5.22"
|
|
152
154
|
},
|
|
153
155
|
"devDependencies": {
|
|
156
|
+
"@emnapi/runtime": "2.0.0-alpha.3",
|
|
154
157
|
"@napi-rs/cli": "3.8.2",
|
|
155
|
-
"@types/node": "^26.
|
|
158
|
+
"@types/node": "^26.2.0",
|
|
156
159
|
"@vitest/coverage-v8": "4.1.10",
|
|
160
|
+
"fast-check": "^4.9.0",
|
|
161
|
+
"istanbul-lib-coverage": "3.2.2",
|
|
162
|
+
"istanbul-lib-report": "3.0.1",
|
|
163
|
+
"istanbul-reports": "3.2.0",
|
|
157
164
|
"sigstore": "5.0.0",
|
|
158
165
|
"typescript": "^7.0.2",
|
|
159
|
-
"vite": "8.2.
|
|
166
|
+
"vite": "8.2.1",
|
|
160
167
|
"vitest": "^4.1.10"
|
|
161
168
|
},
|
|
162
169
|
"engines": {
|