@openclaw/fs-safe 0.2.7 → 0.4.0
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 +26 -0
- package/README.md +18 -5
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/deny-mutations.d.ts +11 -0
- package/dist/deny-mutations.d.ts.map +1 -0
- package/dist/deny-mutations.js +102 -0
- package/dist/device-path.d.ts +13 -0
- package/dist/device-path.d.ts.map +1 -0
- package/dist/device-path.js +112 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/fsync.d.ts +2 -0
- package/dist/fsync.d.ts.map +1 -0
- package/dist/fsync.js +21 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +51 -4
- package/dist/move-path.d.ts +3 -0
- package/dist/move-path.d.ts.map +1 -1
- package/dist/move-path.js +13 -1
- package/dist/opened-realpath.d.ts +3 -0
- package/dist/opened-realpath.d.ts.map +1 -0
- package/dist/opened-realpath.js +79 -0
- package/dist/path.d.ts +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +1 -0
- package/dist/pinned-open.d.ts.map +1 -1
- package/dist/pinned-open.js +7 -0
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +3 -0
- package/dist/read-opened-file.d.ts +18 -0
- package/dist/read-opened-file.d.ts.map +1 -0
- package/dist/read-opened-file.js +15 -0
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +26 -3
- package/dist/root-impl.d.ts +18 -15
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +110 -147
- package/dist/root.d.ts +1 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +2 -0
- package/dist/walk.d.ts +13 -2
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +29 -6
- package/docs/contributing.md +1 -1
- package/docs/errors.md +5 -0
- package/docs/install.md +3 -3
- package/docs/path.md +13 -0
- package/docs/reading.md +6 -4
- package/docs/root.md +15 -5
- package/docs/security-model.md +7 -2
- package/docs/types.md +22 -9
- package/docs/walk.md +11 -1
- package/docs/writing.md +21 -3
- package/package.json +22 -17
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { FsSafeError } from "./errors.js";
|
|
4
|
+
import { sameFileIdentity } from "./file-identity.js";
|
|
5
|
+
import { isNotFoundPathError } from "./path.js";
|
|
6
|
+
export async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
7
|
+
const handleStat = await handle.stat();
|
|
8
|
+
const fdCandidates = process.platform === "linux"
|
|
9
|
+
? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`]
|
|
10
|
+
: process.platform === "win32"
|
|
11
|
+
? []
|
|
12
|
+
: [`/dev/fd/${handle.fd}`];
|
|
13
|
+
for (const fdPath of fdCandidates) {
|
|
14
|
+
try {
|
|
15
|
+
const fdRealPath = await fs.realpath(fdPath);
|
|
16
|
+
const fdRealStat = await fs.stat(fdRealPath);
|
|
17
|
+
if (sameFileIdentity(handleStat, fdRealStat)) {
|
|
18
|
+
return fdRealPath;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// try next fd path
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const ioRealPath = await fs.realpath(ioPath);
|
|
27
|
+
const ioRealStat = await fs.stat(ioRealPath);
|
|
28
|
+
if (sameFileIdentity(handleStat, ioRealStat)) {
|
|
29
|
+
return ioRealPath;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (!isNotFoundPathError(err)) {
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
|
|
38
|
+
if (parentResolved) {
|
|
39
|
+
return parentResolved;
|
|
40
|
+
}
|
|
41
|
+
throw new FsSafeError("path-mismatch", "unable to resolve opened file path");
|
|
42
|
+
}
|
|
43
|
+
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
44
|
+
let parentReal;
|
|
45
|
+
try {
|
|
46
|
+
parentReal = await fs.realpath(path.dirname(ioPath));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (isNotFoundPathError(err)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = await fs.readdir(parentReal);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isNotFoundPathError(err)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
for (const entry of entries.toSorted()) {
|
|
65
|
+
const candidatePath = path.join(parentReal, entry);
|
|
66
|
+
try {
|
|
67
|
+
const candidateStat = await fs.lstat(candidatePath);
|
|
68
|
+
if (candidateStat.isFile() && sameFileIdentity(handleStat, candidateStat)) {
|
|
69
|
+
return await fs.realpath(candidatePath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (!isNotFoundPathError(err)) {
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
package/dist/path.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, type UnsafeDeviceReadPathMatch, type UnsafeDeviceReadPathOptions, type UnsafeDeviceReadPathReason, } from "./device-path.js";
|
|
2
3
|
export declare function normalizeWindowsPathForComparison(input: string): string;
|
|
3
4
|
export declare function isNodeError(value: unknown): value is NodeJS.ErrnoException;
|
|
4
5
|
export declare function hasNodeErrorCode(value: unknown, code: string): boolean;
|
package/dist/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAMzB,OAAO,EACL,4BAA4B,EAC5B,sBAAsB,EACtB,yBAAyB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,GAChC,MAAM,kBAAkB,CAAC;AAM1B,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASvE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,cAAc,CAI1E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,SAA6B,GAAG,IAAI,CAIjG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE3D;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAE1D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CA4BlE;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAElE;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAa/F;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE;IAAE,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAChE,OAAO,CAUT;AAED,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG,IAAI,CAMhE;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CAuBpE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAOrF"}
|
package/dist/path.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { FsSafeError } from "./errors.js";
|
|
4
4
|
import { normalizeLowercaseStringOrEmpty } from "./string-coerce.js";
|
|
5
|
+
export { assertNoUnsafeDeviceReadPath, isUnsafeDeviceReadPath, matchUnsafeDeviceReadPath, } from "./device-path.js";
|
|
5
6
|
const NOT_FOUND_CODES = new Set(["ENOENT", "ENOTDIR"]);
|
|
6
7
|
const SYMLINK_OPEN_CODES = new Set(["ELOOP", "EINVAL", "ENOTSUP"]);
|
|
7
8
|
const POSIX_SEPARATOR_CHAR_CODE = 0x2f;
|
|
@@ -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;AAIzB,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,CAiEvB"}
|
package/dist/pinned-open.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { isUnsafeDeviceReadPath } from "./device-path.js";
|
|
2
3
|
import { sameFileIdentity as hasSameFileIdentity } from "./file-identity.js";
|
|
3
4
|
function isExpectedPathError(error) {
|
|
4
5
|
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
@@ -14,6 +15,9 @@ export function openPinnedFileSync(params) {
|
|
|
14
15
|
(typeof ioFs.constants.O_NOFOLLOW === "number" ? ioFs.constants.O_NOFOLLOW : 0);
|
|
15
16
|
let fd = null;
|
|
16
17
|
try {
|
|
18
|
+
if (isUnsafeDeviceReadPath(params.filePath)) {
|
|
19
|
+
return { ok: false, reason: "validation" };
|
|
20
|
+
}
|
|
17
21
|
if (params.rejectPathSymlink) {
|
|
18
22
|
const candidateStat = ioFs.lstatSync(params.filePath);
|
|
19
23
|
if (candidateStat.isSymbolicLink()) {
|
|
@@ -21,6 +25,9 @@ export function openPinnedFileSync(params) {
|
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
const realPath = params.resolvedPath ?? ioFs.realpathSync(params.filePath);
|
|
28
|
+
if (isUnsafeDeviceReadPath(realPath)) {
|
|
29
|
+
return { ok: false, reason: "validation" };
|
|
30
|
+
}
|
|
24
31
|
const preOpenStat = ioFs.lstatSync(realPath);
|
|
25
32
|
if (!isAllowedType(preOpenStat, allowedType)) {
|
|
26
33
|
return { ok: false, reason: "validation" };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"pinned-write.d.ts","sourceRoot":"","sources":["../src/pinned-write.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAI5C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAW3D,KAAK,gBAAgB,GACjB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AA6EzC,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAwC5B;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,gBAAgB,CAAC;IACjC,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAqB5B"}
|
package/dist/pinned-write.js
CHANGED
|
@@ -4,6 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
|
+
import { syncDirectoryBestEffort } from "./fsync.js";
|
|
7
8
|
import { sameFileIdentity } from "./file-identity.js";
|
|
8
9
|
import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
9
10
|
import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
|
|
@@ -201,11 +202,13 @@ async function runPinnedWriteFallback(params) {
|
|
|
201
202
|
throw new FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
202
203
|
}
|
|
203
204
|
const expectedTempStat = tempStat;
|
|
205
|
+
await handle.sync();
|
|
204
206
|
await handle.close().catch(() => undefined);
|
|
205
207
|
handle = undefined;
|
|
206
208
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
207
209
|
await fs.rename(tempPath, targetPath);
|
|
208
210
|
renamed = true;
|
|
211
|
+
await syncDirectoryBestEffort(parentPath);
|
|
209
212
|
targetStat = await fs.lstat(targetPath);
|
|
210
213
|
if (targetStat.isSymbolicLink() || !sameFileIdentity(targetStat, expectedTempStat)) {
|
|
211
214
|
throw new FsSafeError("path-mismatch", "fallback target changed during write");
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
export type ReadResult = {
|
|
4
|
+
buffer: Buffer;
|
|
5
|
+
realPath: string;
|
|
6
|
+
stat: Stats;
|
|
7
|
+
};
|
|
8
|
+
type OpenedFile = {
|
|
9
|
+
handle: FileHandle;
|
|
10
|
+
realPath: string;
|
|
11
|
+
stat: Stats;
|
|
12
|
+
};
|
|
13
|
+
export declare function readOpenedFileSafely(params: {
|
|
14
|
+
opened: OpenedFile;
|
|
15
|
+
maxBytes?: number;
|
|
16
|
+
}): Promise<ReadResult>;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=read-opened-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-opened-file.d.ts","sourceRoot":"","sources":["../src/read-opened-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,UAAU,CAAC,CAmBtB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FsSafeError } from "./errors.js";
|
|
2
|
+
export async function readOpenedFileSafely(params) {
|
|
3
|
+
if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
|
|
4
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
|
|
5
|
+
}
|
|
6
|
+
const buffer = await params.opened.handle.readFile();
|
|
7
|
+
if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
|
|
8
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
buffer,
|
|
12
|
+
realPath: params.opened.realPath,
|
|
13
|
+
stat: params.opened.stat,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -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;AAU7B,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;AA2DD,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,CAkD3C;AA6CD,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,CAqBA;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
|
@@ -1,6 +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 { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
5
|
+
import { FsSafeError } from "./errors.js";
|
|
4
6
|
import { sameFileIdentity } from "./file-identity.js";
|
|
5
7
|
import { isNotFoundPathError } from "./path.js";
|
|
6
8
|
import { assertNoSymlinkParents, assertNoSymlinkParentsSync } from "./symlink-parents.js";
|
|
@@ -88,6 +90,7 @@ export function statRegularFileSync(filePath) {
|
|
|
88
90
|
return { missing: false, stat };
|
|
89
91
|
}
|
|
90
92
|
export async function readRegularFile(params) {
|
|
93
|
+
assertNoUnsafeDeviceReadPath(params.filePath);
|
|
91
94
|
const result = await statRegularFile(params.filePath);
|
|
92
95
|
if (result.missing) {
|
|
93
96
|
throw Object.assign(new Error(`File not found: ${params.filePath}`), { code: "ENOENT" });
|
|
@@ -95,12 +98,31 @@ export async function readRegularFile(params) {
|
|
|
95
98
|
if (params.maxBytes !== undefined && result.stat.size > params.maxBytes) {
|
|
96
99
|
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
97
100
|
}
|
|
98
|
-
|
|
101
|
+
let handle;
|
|
102
|
+
try {
|
|
103
|
+
handle = await fs.open(params.filePath, resolveRegularFileReadFlags());
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
if (isNotFoundPathError(err)) {
|
|
107
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
99
111
|
try {
|
|
100
112
|
const stat = await handle.stat();
|
|
113
|
+
let pathStat;
|
|
114
|
+
try {
|
|
115
|
+
pathStat = await fs.lstat(params.filePath);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (isNotFoundPathError(err)) {
|
|
119
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
120
|
+
}
|
|
121
|
+
throw err;
|
|
122
|
+
}
|
|
101
123
|
verifyStableReadTarget({
|
|
102
124
|
filePath: params.filePath,
|
|
103
|
-
pathStat
|
|
125
|
+
pathStat,
|
|
104
126
|
postOpenStat: stat,
|
|
105
127
|
preOpenStat: result.stat,
|
|
106
128
|
});
|
|
@@ -126,7 +148,7 @@ function verifyStableReadTarget(params) {
|
|
|
126
148
|
}
|
|
127
149
|
if (!sameFileIdentity(params.preOpenStat, params.postOpenStat) ||
|
|
128
150
|
!sameFileIdentity(params.pathStat, params.postOpenStat)) {
|
|
129
|
-
throw new
|
|
151
|
+
throw new FsSafeError("path-mismatch", `File changed during read: ${params.filePath}`);
|
|
130
152
|
}
|
|
131
153
|
}
|
|
132
154
|
function readOpenedRegularFileSync(params) {
|
|
@@ -150,6 +172,7 @@ function readOpenedRegularFileSync(params) {
|
|
|
150
172
|
return { buffer, stat };
|
|
151
173
|
}
|
|
152
174
|
export function readRegularFileSync(params) {
|
|
175
|
+
assertNoUnsafeDeviceReadPath(params.filePath);
|
|
153
176
|
const result = statRegularFileSync(params.filePath);
|
|
154
177
|
if (result.missing) {
|
|
155
178
|
throw Object.assign(new Error(`File not found: ${params.filePath}`), { code: "ENOENT" });
|
package/dist/root-impl.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { Stats } from "node:fs";
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
|
+
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
4
|
+
import { type ReadResult } from "./read-opened-file.js";
|
|
3
5
|
import type { DirEntry, PathStat } from "./types.js";
|
|
6
|
+
export type { DenyMutationPolicy } from "./deny-mutations.js";
|
|
7
|
+
export { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
|
|
8
|
+
export type { ReadResult } from "./read-opened-file.js";
|
|
4
9
|
export type OpenResult = {
|
|
5
10
|
handle: FileHandle;
|
|
6
11
|
realPath: string;
|
|
7
12
|
stat: Stats;
|
|
8
13
|
[Symbol.asyncDispose](): Promise<void>;
|
|
9
14
|
};
|
|
10
|
-
export type ReadResult = {
|
|
11
|
-
buffer: Buffer;
|
|
12
|
-
realPath: string;
|
|
13
|
-
stat: Stats;
|
|
14
|
-
};
|
|
15
15
|
export type RootOptions = {
|
|
16
16
|
rootDir: string;
|
|
17
17
|
defaults?: RootDefaults;
|
|
@@ -24,19 +24,20 @@ export type RootDefaults = {
|
|
|
24
24
|
maxBytes?: number;
|
|
25
25
|
mkdir?: boolean;
|
|
26
26
|
mode?: number;
|
|
27
|
+
denyMutations?: DenyMutationPolicy;
|
|
27
28
|
nonBlockingRead?: boolean;
|
|
28
29
|
symlinks?: SymlinkPolicy;
|
|
29
30
|
};
|
|
30
31
|
export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
|
|
31
32
|
export type RootOpenOptions = Omit<RootReadOptions, "maxBytes">;
|
|
32
|
-
export type RootWriteOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
33
|
+
export type RootWriteOptions = Pick<RootDefaults, "denyMutations" | "mkdir" | "mode"> & {
|
|
33
34
|
encoding?: BufferEncoding;
|
|
34
35
|
overwrite?: boolean;
|
|
35
36
|
};
|
|
36
|
-
export type RootOpenWritableOptions = Pick<RootDefaults, "mkdir" | "mode"> & {
|
|
37
|
+
export type RootOpenWritableOptions = Pick<RootDefaults, "denyMutations" | "mkdir" | "mode"> & {
|
|
37
38
|
writeMode?: WritableOpenMode;
|
|
38
39
|
};
|
|
39
|
-
export type RootCopyOptions = Pick<RootDefaults, "maxBytes" | "mkdir" | "mode"> & {
|
|
40
|
+
export type RootCopyOptions = Pick<RootDefaults, "denyMutations" | "maxBytes" | "mkdir" | "mode"> & {
|
|
40
41
|
sourceHardlinks?: HardlinkPolicy;
|
|
41
42
|
};
|
|
42
43
|
export type RootWriteJsonOptions = RootWriteOptions & {
|
|
@@ -49,6 +50,11 @@ export type RootCreateJsonOptions = Omit<RootWriteJsonOptions, "overwrite">;
|
|
|
49
50
|
export type RootAppendOptions = RootWriteOptions & {
|
|
50
51
|
prependNewlineIfNeeded?: boolean;
|
|
51
52
|
};
|
|
53
|
+
export type RootMoveOptions = Pick<RootDefaults, "denyMutations"> & {
|
|
54
|
+
overwrite?: boolean;
|
|
55
|
+
};
|
|
56
|
+
export type RootRemoveOptions = Pick<RootDefaults, "denyMutations">;
|
|
57
|
+
export type RootMkdirOptions = Pick<RootDefaults, "denyMutations">;
|
|
52
58
|
export declare const DEFAULT_ROOT_MAX_BYTES: number;
|
|
53
59
|
export interface Root {
|
|
54
60
|
readonly rootDir: string;
|
|
@@ -69,9 +75,9 @@ export interface Root {
|
|
|
69
75
|
reader(options?: RootReadOptions): (filePath: string) => Promise<Buffer>;
|
|
70
76
|
openWritable(relativePath: string, options?: RootOpenWritableOptions): Promise<WritableOpenResult>;
|
|
71
77
|
append(relativePath: string, data: string | Buffer, options?: RootAppendOptions): Promise<void>;
|
|
72
|
-
remove(relativePath: string): Promise<void>;
|
|
73
|
-
mkdir(relativePath: string): Promise<void>;
|
|
74
|
-
ensureRoot(): Promise<void>;
|
|
78
|
+
remove(relativePath: string, options?: RootRemoveOptions): Promise<void>;
|
|
79
|
+
mkdir(relativePath: string, options?: RootMkdirOptions): Promise<void>;
|
|
80
|
+
ensureRoot(options?: RootMkdirOptions): Promise<void>;
|
|
75
81
|
write(relativePath: string, data: string | Buffer, options?: RootWriteOptions): Promise<void>;
|
|
76
82
|
create(relativePath: string, data: string | Buffer, options?: RootCreateOptions): Promise<void>;
|
|
77
83
|
writeJson(relativePath: string, data: unknown, options?: RootWriteJsonOptions): Promise<void>;
|
|
@@ -85,9 +91,7 @@ export interface Root {
|
|
|
85
91
|
list(relativePath: string, options: {
|
|
86
92
|
withFileTypes: true;
|
|
87
93
|
}): Promise<DirEntry[]>;
|
|
88
|
-
move(fromRelative: string, toRelative: string, options?:
|
|
89
|
-
overwrite?: boolean;
|
|
90
|
-
}): Promise<void>;
|
|
94
|
+
move(fromRelative: string, toRelative: string, options?: RootMoveOptions): Promise<void>;
|
|
91
95
|
}
|
|
92
96
|
export declare function root(rootDir: string, defaults?: RootDefaults): Promise<Root>;
|
|
93
97
|
export declare function readLocalFileSafely(params: {
|
|
@@ -104,5 +108,4 @@ export type WritableOpenResult = {
|
|
|
104
108
|
stat: Stats;
|
|
105
109
|
[Symbol.asyncDispose](): Promise<void>;
|
|
106
110
|
};
|
|
107
|
-
export declare function resolveOpenedFileRealPathForHandle(handle: FileHandle, ioPath: string): Promise<string>;
|
|
108
111
|
//# sourceMappingURL=root-impl.d.ts.map
|
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;
|
|
1
|
+
{"version":3,"file":"root-impl.d.ts","sourceRoot":"","sources":["../src/root-impl.ts"],"names":[],"mappings":"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"}
|