@openclaw/fs-safe 0.8.3 → 0.8.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 +10 -0
- package/dist/absolute-path.d.ts.map +1 -1
- package/dist/absolute-path.js +8 -7
- package/dist/archive-native.d.ts +1 -0
- package/dist/archive-native.d.ts.map +1 -1
- package/dist/archive-native.js +15 -68
- package/dist/archive-plan.d.ts +20 -0
- package/dist/archive-plan.d.ts.map +1 -0
- package/dist/archive-plan.js +55 -0
- package/dist/archive-tar-inspect.d.ts +7 -0
- package/dist/archive-tar-inspect.d.ts.map +1 -0
- package/dist/archive-tar-inspect.js +59 -0
- package/dist/archive-tar.d.ts +3 -1
- package/dist/archive-tar.d.ts.map +1 -1
- package/dist/archive-tar.js +12 -49
- package/dist/archive.d.ts +1 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +20 -36
- package/dist/bounded-read.d.ts.map +1 -1
- package/dist/bounded-read.js +31 -4
- package/dist/directory-durability.js +5 -5
- package/dist/directory-guard.d.ts.map +1 -1
- package/dist/directory-guard.js +5 -6
- package/dist/file-store-boundary.js +1 -1
- package/dist/file-store-prune.js +17 -8
- package/dist/file-store.js +2 -2
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +5 -4
- package/dist/native-binding.d.ts +2 -1
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/native-pinned-write-windows.js +6 -3
- package/dist/native-pinned-write.js +3 -3
- package/dist/native-staged-file.d.ts +2 -2
- package/dist/native-staged-file.d.ts.map +1 -1
- package/dist/native-staged-file.js +12 -8
- package/dist/opened-realpath.d.ts.map +1 -1
- package/dist/opened-realpath.js +10 -9
- package/dist/path-policy.js +2 -2
- package/dist/pinned-write.d.ts +1 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +15 -11
- package/dist/regular-file.js +8 -8
- package/dist/replace-file-descriptor.d.ts.map +1 -1
- package/dist/replace-file-descriptor.js +8 -4
- package/dist/replace-file-temp-owner.d.ts.map +1 -1
- package/dist/replace-file-temp-owner.js +13 -7
- package/dist/root-context.js +5 -5
- package/dist/root-impl.d.ts +2 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +35 -26
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +2 -3
- package/dist/root-path-symlink.d.ts.map +1 -1
- package/dist/root-path-symlink.js +2 -3
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +3 -4
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +16 -15
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +5 -4
- package/dist/root-write-verification.js +6 -6
- package/dist/secret-file.js +2 -2
- package/dist/strict-file-identity.d.ts +1 -1
- package/dist/strict-file-identity.d.ts.map +1 -1
- package/dist/symlink-parents.d.ts.map +1 -1
- package/dist/symlink-parents.js +1 -2
- package/docs/archive.md +57 -0
- package/docs/reading.md +2 -0
- package/docs/root.md +10 -1
- package/docs/types.md +2 -1
- package/docs/writing.md +26 -3
- package/package.json +8 -8
package/dist/root-path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-path.d.ts","sourceRoot":"","sources":["../src/root-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-path.d.ts","sourceRoot":"","sources":["../src/root-path.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,kCAAkC,EAAE,MAAM,yBAAyB,CAAC;AAE7E,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG;IAChC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,wBAAwB;aACnC,MAAM;;;;aAIN,YAAY;;;;CAIJ,CAAC;AAEX,KAAK,qBAAqB,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,KAAK,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,oBAAoB,CAAC;CAC5B,CAAC;AAEF,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAsCD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,qBAAqB,GAAG,gBAAgB,CAMnF"}
|
package/dist/root-path.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import fsp from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { formatErrorDetail, shortPath } from "./error-detail.js";
|
|
5
4
|
import { FsSafeError } from "./errors.js";
|
|
@@ -234,7 +233,7 @@ async function resolveRootPathLexicalAsync(params) {
|
|
|
234
233
|
state.lexicalCursor = path.join(state.lexicalCursor, segment);
|
|
235
234
|
let stat;
|
|
236
235
|
try {
|
|
237
|
-
stat =
|
|
236
|
+
stat = fs.lstatSync(state.lexicalCursor);
|
|
238
237
|
}
|
|
239
238
|
catch (error) {
|
|
240
239
|
if (handleLexicalLstatFailure(context, error, idx))
|
|
@@ -423,8 +422,8 @@ function buildResolvedRootPath(params) {
|
|
|
423
422
|
async function getPathKind(absolutePath, preserveFinalSymlink) {
|
|
424
423
|
try {
|
|
425
424
|
const stat = preserveFinalSymlink
|
|
426
|
-
?
|
|
427
|
-
:
|
|
425
|
+
? fs.lstatSync(absolutePath)
|
|
426
|
+
: fs.statSync(absolutePath);
|
|
428
427
|
return { exists: true, kind: toResolvedKind(stat) };
|
|
429
428
|
}
|
|
430
429
|
catch (error) {
|
package/dist/root-paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"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"}
|
package/dist/root-paths.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { formatErrorDetail } from "./error-detail.js";
|
|
@@ -16,7 +17,7 @@ function pathStaysWithinRoot(rootDir, candidatePath) {
|
|
|
16
17
|
}
|
|
17
18
|
async function resolveRealPathIfExists(targetPath) {
|
|
18
19
|
try {
|
|
19
|
-
return
|
|
20
|
+
return fsSync.realpathSync.native(targetPath);
|
|
20
21
|
}
|
|
21
22
|
catch {
|
|
22
23
|
return undefined;
|
|
@@ -24,11 +25,11 @@ async function resolveRealPathIfExists(targetPath) {
|
|
|
24
25
|
}
|
|
25
26
|
async function resolveTrustedRootRealPath(rootDir) {
|
|
26
27
|
try {
|
|
27
|
-
const rootLstat =
|
|
28
|
+
const rootLstat = fsSync.lstatSync(rootDir);
|
|
28
29
|
if (!rootLstat.isDirectory() || rootLstat.isSymbolicLink()) {
|
|
29
30
|
return undefined;
|
|
30
31
|
}
|
|
31
|
-
return
|
|
32
|
+
return fsSync.realpathSync.native(rootDir);
|
|
32
33
|
}
|
|
33
34
|
catch {
|
|
34
35
|
return undefined;
|
|
@@ -36,7 +37,7 @@ async function resolveTrustedRootRealPath(rootDir) {
|
|
|
36
37
|
}
|
|
37
38
|
async function validateCanonicalPathWithinRoot(params) {
|
|
38
39
|
try {
|
|
39
|
-
const candidateLstat =
|
|
40
|
+
const candidateLstat = fsSync.lstatSync(params.candidatePath);
|
|
40
41
|
if (candidateLstat.isSymbolicLink()) {
|
|
41
42
|
return "invalid";
|
|
42
43
|
}
|
|
@@ -49,7 +50,7 @@ async function validateCanonicalPathWithinRoot(params) {
|
|
|
49
50
|
if (params.expect === "file" && candidateLstat.nlink > 1) {
|
|
50
51
|
return "invalid";
|
|
51
52
|
}
|
|
52
|
-
const candidateRealPath =
|
|
53
|
+
const candidateRealPath = fsSync.realpathSync.native(params.candidatePath);
|
|
53
54
|
return isPathInside(params.rootRealPath, candidateRealPath) ? "ok" : "invalid";
|
|
54
55
|
}
|
|
55
56
|
catch (err) {
|
|
@@ -109,7 +110,7 @@ async function resolveNearestExistingPath(targetPath) {
|
|
|
109
110
|
let current = path.resolve(targetPath);
|
|
110
111
|
while (true) {
|
|
111
112
|
try {
|
|
112
|
-
|
|
113
|
+
fsSync.lstatSync(current);
|
|
113
114
|
return current;
|
|
114
115
|
}
|
|
115
116
|
catch (err) {
|
|
@@ -133,7 +134,7 @@ async function assertNoSymlinkSegments(params) {
|
|
|
133
134
|
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
134
135
|
current = path.join(current, segment);
|
|
135
136
|
try {
|
|
136
|
-
const stat =
|
|
137
|
+
const stat = fsSync.lstatSync(current);
|
|
137
138
|
if (stat.isSymbolicLink()) {
|
|
138
139
|
throw new FsSafeError("symlink", `Invalid path: must not traverse symlinks within ${params.scopeLabel}`);
|
|
139
140
|
}
|
|
@@ -163,14 +164,14 @@ export async function ensureDirectoryWithinRoot(params) {
|
|
|
163
164
|
return lexical;
|
|
164
165
|
const rootDir = path.resolve(params.rootDir);
|
|
165
166
|
const targetPath = lexical.path;
|
|
166
|
-
const rootStat =
|
|
167
|
+
const rootStat = fsSync.lstatSync(rootDir);
|
|
167
168
|
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
168
169
|
return invalidPath(scopeLabel);
|
|
169
170
|
}
|
|
170
171
|
await assertNoSymlinkSegments({ rootDir, targetPath, scopeLabel });
|
|
171
|
-
const rootReal =
|
|
172
|
+
const rootReal = fsSync.realpathSync.native(rootDir);
|
|
172
173
|
const nearestExistingPath = await resolveNearestExistingPath(targetPath);
|
|
173
|
-
const nearestExistingReal =
|
|
174
|
+
const nearestExistingReal = fsSync.realpathSync.native(nearestExistingPath);
|
|
174
175
|
if (!isPathInside(rootReal, nearestExistingReal)) {
|
|
175
176
|
return invalidPath(scopeLabel);
|
|
176
177
|
}
|
|
@@ -180,7 +181,7 @@ export async function ensureDirectoryWithinRoot(params) {
|
|
|
180
181
|
current = path.join(current, segment);
|
|
181
182
|
while (true) {
|
|
182
183
|
try {
|
|
183
|
-
const stat =
|
|
184
|
+
const stat = fsSync.lstatSync(current);
|
|
184
185
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
185
186
|
return invalidPath(scopeLabel);
|
|
186
187
|
}
|
|
@@ -201,12 +202,12 @@ export async function ensureDirectoryWithinRoot(params) {
|
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
|
-
const currentReal =
|
|
205
|
+
const currentReal = fsSync.realpathSync.native(current);
|
|
205
206
|
if (!isPathInside(rootReal, currentReal)) {
|
|
206
207
|
return invalidPath(scopeLabel);
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
|
-
const targetReal =
|
|
210
|
+
const targetReal = fsSync.realpathSync.native(targetPath);
|
|
210
211
|
if (!isPathInside(rootReal, targetReal)) {
|
|
211
212
|
return invalidPath(scopeLabel);
|
|
212
213
|
}
|
|
@@ -308,7 +309,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
|
|
|
308
309
|
return lexicalPathResult;
|
|
309
310
|
}
|
|
310
311
|
try {
|
|
311
|
-
const resolvedExistingPath =
|
|
312
|
+
const resolvedExistingPath = fsSync.realpathSync.native(raw);
|
|
312
313
|
const relativePath = path.relative(rootRealPath, resolvedExistingPath);
|
|
313
314
|
if (!isInRoot(relativePath)) {
|
|
314
315
|
return lexicalPathResult;
|
|
@@ -350,7 +351,7 @@ async function resolveCheckedPathsWithinRoot(params, allowMissingFallback) {
|
|
|
350
351
|
scopeLabel: params.scopeLabel,
|
|
351
352
|
});
|
|
352
353
|
const existingPath = await resolveNearestExistingPath(pathResult.fallbackPath);
|
|
353
|
-
const existingRealPath =
|
|
354
|
+
const existingRealPath = fsSync.realpathSync.native(existingPath);
|
|
354
355
|
if (!isPathInside(rootRealPath, existingRealPath)) {
|
|
355
356
|
return invalidPath(params.scopeLabel);
|
|
356
357
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-write-mode.d.ts","sourceRoot":"","sources":["../src/root-write-mode.ts"],"names":[],"mappings":"AAUA,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwClB"}
|
package/dist/root-write-mode.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import { FsSafeError } from "./errors.js";
|
|
3
4
|
import { sameFileIdentity } from "./file-identity.js";
|
|
@@ -8,7 +9,7 @@ import { inspectFileIdentity } from "./strict-file-identity.js";
|
|
|
8
9
|
// The caller has already resolved and guarded this write target.
|
|
9
10
|
export async function inheritWriteTargetMode(params) {
|
|
10
11
|
try {
|
|
11
|
-
const existing = await inspectFileIdentity(() =>
|
|
12
|
+
const existing = await inspectFileIdentity(() => fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
12
13
|
if (existing.isSymbolicLink())
|
|
13
14
|
throw new FsSafeError("path-alias", "path alias escape blocked");
|
|
14
15
|
if (!existing.isFile())
|
|
@@ -22,7 +23,7 @@ export async function inheritWriteTargetMode(params) {
|
|
|
22
23
|
const handle = await fs.open(params.targetPath, resolveReadOpenFlags());
|
|
23
24
|
try {
|
|
24
25
|
// Bind admission to the inode whose metadata is inherited.
|
|
25
|
-
if (!sameFileIdentity(
|
|
26
|
+
if (!sameFileIdentity(fsSync.fstatSync(handle.fd, { bigint: true }), existing)) {
|
|
26
27
|
throw new FsSafeError("path-mismatch", "write target changed during mode inheritance");
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -32,11 +33,11 @@ export async function inheritWriteTargetMode(params) {
|
|
|
32
33
|
try {
|
|
33
34
|
// A parent can change after guarded resolution. Do not inherit metadata
|
|
34
35
|
// from an outside inode, even if the parent is restored before publication.
|
|
35
|
-
const realPath =
|
|
36
|
+
const realPath = fsSync.realpathSync.native(params.targetPath);
|
|
36
37
|
if (!isPathInside(params.rootWithSep, realPath))
|
|
37
38
|
throw outsideWorkspaceError();
|
|
38
39
|
await inspectFileIdentity(async () => {
|
|
39
|
-
const current =
|
|
40
|
+
const current = fsSync.statSync(realPath, { bigint: true });
|
|
40
41
|
if (!current.isFile())
|
|
41
42
|
throw new FsSafeError("not-file", "not a file");
|
|
42
43
|
if (current.nlink > 1n)
|
|
@@ -46,16 +46,16 @@ export async function verifyAtomicWriteResult(params) {
|
|
|
46
46
|
try {
|
|
47
47
|
// This descriptor remains owned by the writer, even when final mode forbids opens.
|
|
48
48
|
const stat = assertDescriptor();
|
|
49
|
-
assertPath(
|
|
49
|
+
assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
50
50
|
const { realPath } = await resolveOpenedFileRealPathForFd(params.fd, stat, params.targetPath);
|
|
51
|
-
assertPath(
|
|
51
|
+
assertPath(fsSync.statSync(realPath, { bigint: true }));
|
|
52
52
|
if (!isPathInside(params.root.rootWithSep, realPath)) {
|
|
53
53
|
throw outsideWorkspaceError();
|
|
54
54
|
}
|
|
55
55
|
await assertAsyncDirectoryGuard(params.parentGuard);
|
|
56
56
|
await assertRootIdentityCurrent(params.root);
|
|
57
57
|
// Recheck after canonical resolution and directory checks, including late links.
|
|
58
|
-
assertPath(
|
|
58
|
+
assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
59
59
|
assertDescriptor();
|
|
60
60
|
if (needsPathOpen) {
|
|
61
61
|
// A retained fd cannot prove that an opaque Windows pathname still names it.
|
|
@@ -73,15 +73,15 @@ export async function verifyAtomicWriteResult(params) {
|
|
|
73
73
|
});
|
|
74
74
|
try {
|
|
75
75
|
const reopenedStat = assertDescriptor(opened.fd);
|
|
76
|
-
assertPath(
|
|
76
|
+
assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
77
77
|
const { realPath: reopenedPath } = await resolveOpenedFileRealPathForFd(opened.fd, reopenedStat, params.targetPath);
|
|
78
|
-
assertPath(
|
|
78
|
+
assertPath(fsSync.statSync(reopenedPath, { bigint: true }));
|
|
79
79
|
if (!isPathInside(params.root.rootWithSep, reopenedPath)) {
|
|
80
80
|
throw outsideWorkspaceError();
|
|
81
81
|
}
|
|
82
82
|
await assertAsyncDirectoryGuard(params.parentGuard);
|
|
83
83
|
await assertRootIdentityCurrent(params.root);
|
|
84
|
-
assertPath(
|
|
84
|
+
assertPath(fsSync.lstatSync(params.targetPath, { bigint: true }));
|
|
85
85
|
assertDescriptor(opened.fd);
|
|
86
86
|
}
|
|
87
87
|
finally {
|
package/dist/secret-file.js
CHANGED
|
@@ -140,7 +140,7 @@ async function enforcePrivateDirectoryMode(params) {
|
|
|
140
140
|
}
|
|
141
141
|
async function inspectPrivateDirectory(directory, kind) {
|
|
142
142
|
return await inspectFileIdentity(async () => {
|
|
143
|
-
const stat =
|
|
143
|
+
const stat = fs.lstatSync(directory, { bigint: true });
|
|
144
144
|
if (stat.isSymbolicLink()) {
|
|
145
145
|
throw new Error(`Private secret ${kind} ${directory} must not be a symlink.`);
|
|
146
146
|
}
|
|
@@ -260,7 +260,7 @@ export async function prepareSecretFileWrite(params) {
|
|
|
260
260
|
async function materializeSecretFileAtomic(params, createOnly) {
|
|
261
261
|
const { mode, rootGuard, parentGuard, fileName, finalFilePath } = await prepareSecretFileWrite(params);
|
|
262
262
|
try {
|
|
263
|
-
const stat =
|
|
263
|
+
const stat = fs.lstatSync(finalFilePath);
|
|
264
264
|
if (createOnly) {
|
|
265
265
|
throw new FsSafeError("secret-exists", `Private secret file ${finalFilePath} already exists.`);
|
|
266
266
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BigIntStats } from "node:fs";
|
|
2
2
|
type ExactFileIdentity = Pick<BigIntStats, "dev" | "ino">;
|
|
3
|
-
export declare function inspectFileIdentity<T extends ExactFileIdentity>(inspect: () => Promise<T>, expected?: ExactFileIdentity, platform?: NodeJS.Platform): Promise<T>;
|
|
3
|
+
export declare function inspectFileIdentity<T extends ExactFileIdentity>(inspect: () => T | Promise<T>, expected?: ExactFileIdentity, platform?: NodeJS.Platform): Promise<T>;
|
|
4
4
|
export declare function inspectFileIdentitySync<T extends ExactFileIdentity>(inspect: () => T, expected?: ExactFileIdentity, platform?: NodeJS.Platform): T;
|
|
5
5
|
export {};
|
|
6
6
|
//# sourceMappingURL=strict-file-identity.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA+B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"strict-file-identity.d.ts","sourceRoot":"","sources":["../src/strict-file-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAI3C,KAAK,iBAAiB,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,KAAK,CAAC,CAAC;AA+B1D,wBAAsB,mBAAmB,CAAC,CAAC,SAAS,iBAAiB,EACnE,OAAO,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC7B,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,iBAAiB,EACjE,OAAO,EAAE,MAAM,CAAC,EAChB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,CAAC,CAOH"}
|
|
@@ -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":"AAKA,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,5 +1,4 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { FsSafeError } from "./errors.js";
|
|
5
4
|
import { hasNodeErrorCode, isPathRelativeEscape } from "./path.js";
|
|
@@ -30,7 +29,7 @@ export async function assertNoSymlinkParents(params) {
|
|
|
30
29
|
for (const [index, segment] of walk.segments.entries()) {
|
|
31
30
|
current = path.join(current, segment);
|
|
32
31
|
try {
|
|
33
|
-
const stat =
|
|
32
|
+
const stat = fsSync.lstatSync(current);
|
|
34
33
|
if (stat.isSymbolicLink()) {
|
|
35
34
|
if (params.allowRootChildSymlink && path.dirname(current) === walk.root) {
|
|
36
35
|
continue;
|
package/docs/archive.md
CHANGED
|
@@ -422,6 +422,63 @@ Normal link/filter policy still governs the described member. Canonical
|
|
|
422
422
|
pre-strip filter paths, decoded-stream ceilings, and physical EOF checks apply
|
|
423
423
|
to plain/gzip TAR and native zstd/bzip2 alike.
|
|
424
424
|
|
|
425
|
+
## `inspectTarArchive`
|
|
426
|
+
|
|
427
|
+
Inspect accepted TAR members without creating an extracted tree. This operation
|
|
428
|
+
uses the same complete Rust/WASM admission and TypeScript extraction planner as
|
|
429
|
+
`extractArchive`, with zero stripping. It detects plain TAR or gzip from the
|
|
430
|
+
input bytes; ZIP, zstd, and bzip2 are not part of this inspection API.
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
import { inspectTarArchive } from "@openclaw/fs-safe/archive";
|
|
434
|
+
|
|
435
|
+
const entries = await inspectTarArchive({
|
|
436
|
+
archivePath: "/srv/uploads/tree.tar.gz",
|
|
437
|
+
timeoutMs: 30_000,
|
|
438
|
+
limits: {
|
|
439
|
+
maxArchiveBytes: 16 * 1024 * 1024,
|
|
440
|
+
maxEntries: 5_000,
|
|
441
|
+
maxEntryBytes: 16 * 1024 * 1024,
|
|
442
|
+
maxExtractedBytes: 64 * 1024 * 1024,
|
|
443
|
+
},
|
|
444
|
+
entryFilter: ({ kind }) => kind === "file" || kind === "directory" ? "extract" : "skip",
|
|
445
|
+
onFiltered: "reject-archive",
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
`InspectTarArchiveOptions` accepts `archivePath`, `timeoutMs`, `limits`,
|
|
450
|
+
`entryFilter`, and `onFiltered`, with the same defaults and error classes as
|
|
451
|
+
extraction. The result is a frozen array of frozen `InspectedTarEntry` records:
|
|
452
|
+
`{ path: string; kind: "file" | "directory"; size: number }`, in archive order.
|
|
453
|
+
`size` is the effective declared payload length. `path` is extraction's
|
|
454
|
+
canonical pre-strip identity: case, Unicode spelling, BOM, and embedded LF are
|
|
455
|
+
preserved, while separators and dot components follow the existing archive path
|
|
456
|
+
contract. No human-readable tar listing or escape decoding is involved.
|
|
457
|
+
|
|
458
|
+
Full framing, gzip integrity, EOF, metadata, decoded-byte, and manifest-budget
|
|
459
|
+
validation finishes before the caller's filter runs. The shared planner then
|
|
460
|
+
applies traversal, collision, depth, blocked-type, and accepted-payload limits.
|
|
461
|
+
A failure returns no partial result. Filter callbacks are decisions, not admission
|
|
462
|
+
receipts: later policy, collision, or budget checks can still reject the archive.
|
|
463
|
+
Only the resolved Promise/result is authorization-worthy; do not perform
|
|
464
|
+
irreversible actions from a filter callback.
|
|
465
|
+
|
|
466
|
+
Root-only records count toward entry limits but produce no result; PAX/GNU metadata headers are not members. Only accepted
|
|
467
|
+
file/directory members appear, not implicit parent directories. Unsupported
|
|
468
|
+
records follow extraction's omission policy unless the filter rejects them, as
|
|
469
|
+
in the example. AppleDouble records encoded as ordinary files are ordinary
|
|
470
|
+
members, not hidden metadata.
|
|
471
|
+
|
|
472
|
+
Inspection pins and privately stages its input, then cleans up that copy. It
|
|
473
|
+
neither creates destination paths nor tests destination permissions or platform
|
|
474
|
+
filename restrictions. Its result is evidence about those inspected bytes, not
|
|
475
|
+
an extraction capability or a promise that a later file at `archivePath` is
|
|
476
|
+
unchanged. Callers making authorization decisions must retain the same private
|
|
477
|
+
immutable archive or verify byte identity before extracting with matching
|
|
478
|
+
filter/limit settings. Extraction always performs its own admission and guarded
|
|
479
|
+
publication. Native `off`, `auto`, and `require` retain their existing selection
|
|
480
|
+
and availability semantics; inspection does not fall back after native failure.
|
|
481
|
+
|
|
425
482
|
## `resolveArchiveKind`
|
|
426
483
|
|
|
427
484
|
```ts
|
package/docs/reading.md
CHANGED
|
@@ -12,6 +12,8 @@ const opened = await fs.open("large.log"); // FileHandle for strea
|
|
|
12
12
|
|
|
13
13
|
## What every read does
|
|
14
14
|
|
|
15
|
+
Identity and containment checks use brief synchronous metadata calls, like Node's module resolution, while file data is read asynchronously. Canonical paths retain Node's native realpath spelling, including expansion of Windows short paths.
|
|
16
|
+
|
|
15
17
|
Regardless of shape, every read goes through the same boundary checks:
|
|
16
18
|
|
|
17
19
|
1. Resolve the input lexically against the canonical real root.
|
package/docs/root.md
CHANGED
|
@@ -18,6 +18,7 @@ const fs = await root("/srv/workspace", {
|
|
|
18
18
|
function root(rootDir: string, defaults?: RootDefaults): Promise<Root>;
|
|
19
19
|
|
|
20
20
|
type RootDefaults = {
|
|
21
|
+
durable?: boolean; // fsync write/create/writeJson/createJson/append; default true
|
|
21
22
|
hardlinks?: "reject" | "allow"; // refuse files with nlink > 1 on read; defaults to "reject"
|
|
22
23
|
denyMutations?: DenyMutationPolicy; // absolute paths/prefixes mutation methods may not change
|
|
23
24
|
maxBytes?: number; // refuse reads larger than this many bytes; defaults to 16 MiB
|
|
@@ -99,7 +100,7 @@ fs.write(rel, data, options?) // overwrite-ok atomic write
|
|
|
99
100
|
fs.create(rel, data, options?) // throws "already-exists" if target exists
|
|
100
101
|
fs.writeJson(rel, value, options?) // JSON.stringify + atomic write
|
|
101
102
|
fs.createJson(rel, value, options?) // create() variant of writeJson
|
|
102
|
-
fs.append(rel, data, options?) // append text/buffer; syncs before close
|
|
103
|
+
fs.append(rel, data, options?) // append text/buffer; syncs before close by default
|
|
103
104
|
fs.copyIn(rel, sourceAbsPath, options?) // copy from outside the root, atomically, with size cap
|
|
104
105
|
fs.openWritable(rel, options?) // FileHandle for streaming writes; supports await using
|
|
105
106
|
fs.move(from, to, options?) // rename within the root; defaults to no clobber
|
|
@@ -110,6 +111,14 @@ fs.ensureRoot(options?) // accepts "" / "." as the root itself
|
|
|
110
111
|
|
|
111
112
|
`write`, `create`, `append`, `writeJson`, and `createJson` accept `mode?: number`; use `0o600` for credentials and other private state. `writeJson` also accepts the same options as `JSON.stringify` plus `trailingNewline?: boolean` (defaults `true` so the file ends in `\n`).
|
|
112
113
|
|
|
114
|
+
These five methods also accept `durable?: boolean`: the per-call value overrides
|
|
115
|
+
`Root.defaults.durable`, which defaults to `true` when omitted. An explicitly
|
|
116
|
+
`undefined` per-call value preserves the root default. `durable: false` keeps
|
|
117
|
+
the existing publication behavior, modes, and identity checks but skips file
|
|
118
|
+
and parent-directory fsync calls. Use it only for reconstructible data: a crash
|
|
119
|
+
may lose the write or leave the previous file. See [Writing](writing.md#write-options)
|
|
120
|
+
for platform details.
|
|
121
|
+
|
|
113
122
|
`copyIn` is a one-shot ingest from a trusted absolute source path: it streams the source through the boundary, atomically renames into the root, and respects `maxBytes`.
|
|
114
123
|
|
|
115
124
|
Root operations that choose a new destination reject a leading Windows
|
package/docs/types.md
CHANGED
|
@@ -100,6 +100,7 @@ type RenameIdentityPolicy = "strict" | "verify-content-with-lock";
|
|
|
100
100
|
|
|
101
101
|
type RootDefaults = {
|
|
102
102
|
denyMutations?: DenyMutationPolicy;
|
|
103
|
+
durable?: boolean; // default true for write/create/writeJson/createJson/append
|
|
103
104
|
hardlinks?: "reject" | "allow";
|
|
104
105
|
maxBytes?: number;
|
|
105
106
|
mkdir?: boolean; // default true for mutation methods
|
|
@@ -126,7 +127,7 @@ type RootOptions = {
|
|
|
126
127
|
|
|
127
128
|
```ts
|
|
128
129
|
type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
|
|
129
|
-
type RootWriteOptions = Pick<RootDefaults, "denyMutations" | "mkdir" | "mode" | "renameIdentity"> & {
|
|
130
|
+
type RootWriteOptions = Pick<RootDefaults, "denyMutations" | "durable" | "mkdir" | "mode" | "renameIdentity"> & {
|
|
130
131
|
encoding?: BufferEncoding;
|
|
131
132
|
overwrite?: boolean;
|
|
132
133
|
};
|
package/docs/writing.md
CHANGED
|
@@ -77,14 +77,37 @@ await fs.write("state/last-run.json", JSON.stringify(run));
|
|
|
77
77
|
await fs.write("notes/today.txt", "hello\n", { encoding: "utf8" });
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
`data` accepts `string | Buffer`. `
|
|
80
|
+
`data` accepts `string | Buffer`. `mode` sets the file's POSIX mode. If neither the call nor `RootDefaults` supplies it, a replacement preserves the existing file mode and a new file uses `0o600`. `mkdir` and `overwrite` both default to `true`; set `overwrite: false` for the same no-clobber behavior as `create()`.
|
|
81
|
+
|
|
82
|
+
#### Write options
|
|
83
|
+
|
|
84
|
+
| Option | Type | Default / behavior |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `denyMutations` | `DenyMutationPolicy` | Merged with root-level denies. |
|
|
87
|
+
| `durable` | `boolean` | `true`; use `false` to skip file and parent fsync. |
|
|
88
|
+
| `encoding` | `BufferEncoding` | `"utf8"` for strings. |
|
|
89
|
+
| `mkdir` | `boolean` | `true`; creates missing parents. |
|
|
90
|
+
| `mode` | `number` | Inherited on replacement, otherwise `0o600`. |
|
|
91
|
+
| `overwrite` | `boolean` | `true`; `false` is create-only. |
|
|
92
|
+
| `renameIdentity` | `RenameIdentityPolicy` | `"strict"`. |
|
|
93
|
+
|
|
94
|
+
`write`, `create`, `writeJson`, `createJson`, and `append` accept `durable`.
|
|
95
|
+
Precedence is per-call option, then `Root.defaults.durable`, then `true`;
|
|
96
|
+
an explicitly `undefined` call option preserves the root default.
|
|
97
|
+
`durable: false` keeps the sibling-temp replace/rename behavior of replacement
|
|
98
|
+
writes but skips file and parent-directory fsync calls. Create-only and append
|
|
99
|
+
publication behavior, permissions, identity checks, and error codes are unchanged.
|
|
100
|
+
Use it only for reconstructible data: a crash may lose the write or leave the
|
|
101
|
+
previous file. `copyIn`, `move`, and streaming `openWritable` do not use this option.
|
|
102
|
+
The existing pure-JavaScript Windows writer performs no fsync calls in either
|
|
103
|
+
setting; native Windows writes honor the option. Directory sync remains best-effort.
|
|
81
104
|
|
|
82
105
|
POSIX modes without read permission, including `0o000` and `0o200`, succeed:
|
|
83
106
|
final verification uses a descriptor retained by the writer rather than reopening
|
|
84
107
|
the published file. The requested mode is not relaxed for verification.
|
|
85
108
|
Publication verification compares exact bigint descriptor and pathname identities,
|
|
86
109
|
including large file indexes that cannot be represented by a JavaScript number.
|
|
87
|
-
|
|
110
|
+
With durability enabled (the default), native publication syncs content before rename and syncs the parent directory.
|
|
88
111
|
Modes that retain owner read/write skip the extra mode-only file sync: after a
|
|
89
112
|
crash, the file may retain staged mode `0o600` instead of the wider requested mode.
|
|
90
113
|
Modes that remove owner read or write, and corrections of observed wider staging
|
|
@@ -143,7 +166,7 @@ type RootWriteJsonOptions = RootWriteOptions & {
|
|
|
143
166
|
|
|
144
167
|
### `fs.append(rel, data, options?)`
|
|
145
168
|
|
|
146
|
-
Open in append mode, write, sync the file handle, and close. Honors `mkdir` for the parent directory and syncs the parent directory when the append creates the file. Pass `prependNewlineIfNeeded: true` to insert a `\n` if the file does not already end in one.
|
|
169
|
+
Open in append mode, write, sync the file handle, and close. Honors `mkdir` for the parent directory and syncs the parent directory when the append creates the file. `durable: false` skips both syncs. Pass `prependNewlineIfNeeded: true` to insert a `\n` if the file does not already end in one.
|
|
147
170
|
|
|
148
171
|
```ts
|
|
149
172
|
await fs.append("logs/today.log", `[${ts}] ${line}\n`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/fs-safe",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Capability-style filesystem roots for Node.js apps that handle untrusted relative paths.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"filesystem",
|
|
@@ -156,13 +156,13 @@
|
|
|
156
156
|
"archive:producer-smoke": "node scripts/archive-producer-smoke.mjs"
|
|
157
157
|
},
|
|
158
158
|
"optionalDependencies": {
|
|
159
|
-
"@openclaw/fs-safe-darwin-arm64": "0.8.
|
|
160
|
-
"@openclaw/fs-safe-darwin-x64": "0.8.
|
|
161
|
-
"@openclaw/fs-safe-linux-arm64-gnu": "0.8.
|
|
162
|
-
"@openclaw/fs-safe-linux-arm64-musl": "0.8.
|
|
163
|
-
"@openclaw/fs-safe-linux-x64-gnu": "0.8.
|
|
164
|
-
"@openclaw/fs-safe-linux-x64-musl": "0.8.
|
|
165
|
-
"@openclaw/fs-safe-win32-x64-msvc": "0.8.
|
|
159
|
+
"@openclaw/fs-safe-darwin-arm64": "0.8.4",
|
|
160
|
+
"@openclaw/fs-safe-darwin-x64": "0.8.4",
|
|
161
|
+
"@openclaw/fs-safe-linux-arm64-gnu": "0.8.4",
|
|
162
|
+
"@openclaw/fs-safe-linux-arm64-musl": "0.8.4",
|
|
163
|
+
"@openclaw/fs-safe-linux-x64-gnu": "0.8.4",
|
|
164
|
+
"@openclaw/fs-safe-linux-x64-musl": "0.8.4",
|
|
165
|
+
"@openclaw/fs-safe-win32-x64-msvc": "0.8.4",
|
|
166
166
|
"jszip": "^3.10.1"
|
|
167
167
|
},
|
|
168
168
|
"devDependencies": {
|