@openclaw/fs-safe 0.4.0 → 0.4.2
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 +25 -0
- package/README.md +4 -2
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -0
- package/dist/archive-limits.d.ts.map +1 -1
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/async-lock.d.ts.map +1 -1
- package/dist/bounded-read.d.ts +14 -0
- package/dist/bounded-read.d.ts.map +1 -0
- package/dist/bounded-read.js +86 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/file-identity.d.ts +1 -0
- package/dist/file-identity.d.ts.map +1 -1
- package/dist/file-identity.js +10 -2
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +5 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/json.d.ts +8 -5
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +27 -14
- package/dist/pinned-python.js +10 -10
- package/dist/pinned-write.d.ts +9 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +88 -5
- package/dist/read-opened-file.d.ts.map +1 -1
- package/dist/read-opened-file.js +4 -4
- package/dist/regular-file.d.ts.map +1 -1
- package/dist/regular-file.js +11 -52
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-impl.d.ts +4 -1
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +10 -7
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root.d.ts +1 -1
- package/dist/root.d.ts.map +1 -1
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +5 -2
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +6 -7
- package/dist/sidecar-lock.d.ts +5 -1
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +3 -42
- package/dist/test-hooks.d.ts +1 -0
- package/dist/test-hooks.d.ts.map +1 -1
- package/docs/advanced.md +20 -0
- package/docs/config.md +2 -2
- package/docs/index.md +1 -1
- package/docs/json-store.md +2 -2
- package/docs/json.md +13 -5
- package/docs/regular-file.md +26 -32
- package/docs/security-model.md +1 -0
- package/docs/sidecar-lock.md +8 -24
- package/docs/writing.md +21 -1
- package/package.json +19 -16
package/dist/pinned-write.js
CHANGED
|
@@ -5,11 +5,13 @@ import path from "node:path";
|
|
|
5
5
|
import { createAsyncDirectoryGuard, createNearestExistingDirectoryGuard } from "./directory-guard.js";
|
|
6
6
|
import { FsSafeError } from "./errors.js";
|
|
7
7
|
import { syncDirectoryBestEffort } from "./fsync.js";
|
|
8
|
-
import { sameFileIdentity } from "./file-identity.js";
|
|
8
|
+
import { sameFileIdentity, sha256Hex } from "./file-identity.js";
|
|
9
9
|
import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
10
10
|
import { mkdirPathComponentsWithGuards } from "./guarded-mkdir.js";
|
|
11
11
|
import { canFallbackFromPythonError, getFsSafePythonConfig } from "./pinned-python-config.js";
|
|
12
12
|
import { assertPinnedPythonOperationAvailable, runPinnedPythonOperation, validatePinnedOperationPayload, } from "./pinned-python.js";
|
|
13
|
+
import { withSidecarLock } from "./sidecar-lock.js";
|
|
14
|
+
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
13
15
|
function byteLength(input, encoding) {
|
|
14
16
|
return typeof input === "string"
|
|
15
17
|
? Buffer.byteLength(input, encoding ?? "utf8")
|
|
@@ -29,6 +31,16 @@ function assertWithinMaxBytes(bytes, maxBytes) {
|
|
|
29
31
|
throw new FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
34
|
+
async function syncFileBestEffort(handle) {
|
|
35
|
+
try {
|
|
36
|
+
await handle.sync();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error?.code !== "EPERM") {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
32
44
|
async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
33
45
|
let bytes = 0;
|
|
34
46
|
for await (const chunk of stream) {
|
|
@@ -67,6 +79,12 @@ export async function runPinnedWriteHelper(params) {
|
|
|
67
79
|
validatePinnedOperationPayload({
|
|
68
80
|
relativeParentPath: params.relativeParentPath,
|
|
69
81
|
});
|
|
82
|
+
// The Python helper deliberately enforces the strict post-rename inode
|
|
83
|
+
// contract. The explicit compatibility policy therefore uses the guarded
|
|
84
|
+
// Node fallback, where content verification can replace that one check.
|
|
85
|
+
if (params.onRenameIdentityMismatch === "verify-content") {
|
|
86
|
+
return await runPinnedWriteFallback(params);
|
|
87
|
+
}
|
|
70
88
|
if (getFsSafePythonConfig().mode === "off") {
|
|
71
89
|
return await runPinnedWriteFallback(params);
|
|
72
90
|
}
|
|
@@ -81,8 +99,11 @@ export async function runPinnedWriteHelper(params) {
|
|
|
81
99
|
throw error;
|
|
82
100
|
}
|
|
83
101
|
}
|
|
102
|
+
const input = params.input.kind === "stream"
|
|
103
|
+
? { kind: "buffer", data: Buffer.from(await inputToBase64(params.input, params.maxBytes), "base64") }
|
|
104
|
+
: params.input;
|
|
84
105
|
const payload = {
|
|
85
|
-
base64: await inputToBase64(
|
|
106
|
+
base64: await inputToBase64(input, params.maxBytes),
|
|
86
107
|
basename: params.basename,
|
|
87
108
|
maxBytes: params.maxBytes ?? -1,
|
|
88
109
|
mkdir: params.mkdir,
|
|
@@ -100,11 +121,32 @@ export async function runPinnedWriteHelper(params) {
|
|
|
100
121
|
}
|
|
101
122
|
catch (error) {
|
|
102
123
|
if (canFallbackFromPythonError(error)) {
|
|
103
|
-
return await runPinnedWriteFallback(params);
|
|
124
|
+
return await runPinnedWriteFallback({ ...params, input });
|
|
104
125
|
}
|
|
105
126
|
throw error;
|
|
106
127
|
}
|
|
107
128
|
}
|
|
129
|
+
export async function runPinnedWriteWithRenamePolicy(params) {
|
|
130
|
+
const { targetPath, renameIdentity, ...writeParams } = params;
|
|
131
|
+
if (renameIdentity !== "verify-content-with-lock") {
|
|
132
|
+
return await runPinnedWriteHelper(writeParams);
|
|
133
|
+
}
|
|
134
|
+
const relativeTargetPath = writeParams.relativeParentPath
|
|
135
|
+
? `${writeParams.relativeParentPath}/${writeParams.basename}`
|
|
136
|
+
: writeParams.basename;
|
|
137
|
+
const lockPath = path.join(writeParams.rootPath, `.fs-safe-write-${sha256Hex(relativeTargetPath)}.lock`);
|
|
138
|
+
return await withSidecarLock(writeParams.rootPath, {
|
|
139
|
+
managerKey: `fs-safe.write:${targetPath}`,
|
|
140
|
+
lockPath,
|
|
141
|
+
staleMs: 30_000,
|
|
142
|
+
timeoutMs: 5_000,
|
|
143
|
+
payload: () => ({ pid: process.pid, createdAt: new Date().toISOString() }),
|
|
144
|
+
retry: { retries: 5, minTimeout: 100, maxTimeout: 2_000, factor: 2 },
|
|
145
|
+
}, async () => await runPinnedWriteHelper({
|
|
146
|
+
...writeParams,
|
|
147
|
+
onRenameIdentityMismatch: "verify-content",
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
108
150
|
export async function runPinnedCopyHelper(params) {
|
|
109
151
|
assertSafeBasename(params.basename);
|
|
110
152
|
validatePinnedOperationPayload({
|
|
@@ -160,7 +202,10 @@ async function runPinnedWriteFallback(params) {
|
|
|
160
202
|
else {
|
|
161
203
|
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
162
204
|
}
|
|
205
|
+
await syncFileBestEffort(handle);
|
|
163
206
|
const stat = await handle.stat();
|
|
207
|
+
await handle.close().catch(() => undefined);
|
|
208
|
+
await syncDirectoryBestEffort(parentPath);
|
|
164
209
|
created = false;
|
|
165
210
|
return { dev: stat.dev, ino: stat.ino };
|
|
166
211
|
}
|
|
@@ -202,17 +247,55 @@ async function runPinnedWriteFallback(params) {
|
|
|
202
247
|
throw new FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
203
248
|
}
|
|
204
249
|
const expectedTempStat = tempStat;
|
|
205
|
-
await handle
|
|
250
|
+
await syncFileBestEffort(handle);
|
|
206
251
|
await handle.close().catch(() => undefined);
|
|
207
252
|
handle = undefined;
|
|
208
253
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
209
254
|
await fs.rename(tempPath, targetPath);
|
|
210
255
|
renamed = true;
|
|
256
|
+
await getFsSafeTestHooks()?.afterPinnedWriteFallbackRename?.(targetPath);
|
|
211
257
|
await syncDirectoryBestEffort(parentPath);
|
|
212
258
|
targetStat = await fs.lstat(targetPath);
|
|
213
|
-
if (targetStat.isSymbolicLink()
|
|
259
|
+
if (targetStat.isSymbolicLink()) {
|
|
214
260
|
throw new FsSafeError("path-mismatch", "fallback target changed during write");
|
|
215
261
|
}
|
|
262
|
+
if (!sameFileIdentity(targetStat, expectedTempStat)) {
|
|
263
|
+
// On filesystems like rclone FUSE, rename(2) can give the destination a
|
|
264
|
+
// different inode from the source temp fd even with zero concurrency. The
|
|
265
|
+
// caller must ensure mutual exclusion before passing "verify-content";
|
|
266
|
+
// fall back to a content hash for this rename-boundary check only.
|
|
267
|
+
if (params.onRenameIdentityMismatch !== "verify-content") {
|
|
268
|
+
throw new FsSafeError("path-mismatch", "fallback target changed during write");
|
|
269
|
+
}
|
|
270
|
+
if (params.input.kind !== "buffer") {
|
|
271
|
+
throw new FsSafeError("path-mismatch", "fallback target changed during write");
|
|
272
|
+
}
|
|
273
|
+
const expectedHash = sha256Hex(params.input.data, params.input.encoding);
|
|
274
|
+
const readFlags = fsSync.constants.O_RDONLY |
|
|
275
|
+
(process.platform !== "win32" && "O_NOFOLLOW" in fsSync.constants
|
|
276
|
+
? fsSync.constants.O_NOFOLLOW
|
|
277
|
+
: 0);
|
|
278
|
+
const readHandle = await fs.open(targetPath, readFlags);
|
|
279
|
+
let actualHash;
|
|
280
|
+
let readHandleStat;
|
|
281
|
+
try {
|
|
282
|
+
// Capture fd-based identity before reading — this is stable across all
|
|
283
|
+
// subsequent lookups (on FUSE and locally), unlike the lstat-based
|
|
284
|
+
// targetStat that triggered this fallback.
|
|
285
|
+
readHandleStat = await readHandle.stat();
|
|
286
|
+
actualHash = sha256Hex(await readHandle.readFile());
|
|
287
|
+
}
|
|
288
|
+
finally {
|
|
289
|
+
await readHandle.close().catch(() => undefined);
|
|
290
|
+
}
|
|
291
|
+
if (actualHash !== expectedHash) {
|
|
292
|
+
throw new FsSafeError("path-mismatch", "fallback target changed during write");
|
|
293
|
+
}
|
|
294
|
+
// Replace the unreliable lstat-based targetStat with the fd-based stat so
|
|
295
|
+
// the returned identity is consistent with what subsequent verifications
|
|
296
|
+
// (e.g. verifyAtomicWriteResult) will obtain by opening the same file.
|
|
297
|
+
targetStat = readHandleStat;
|
|
298
|
+
}
|
|
216
299
|
});
|
|
217
300
|
}
|
|
218
301
|
catch (error) {
|
|
@@ -1 +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;
|
|
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;AAInD,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,CAgBtB"}
|
package/dist/read-opened-file.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { readFileHandleBounded } from "./bounded-read.js";
|
|
1
2
|
import { FsSafeError } from "./errors.js";
|
|
2
3
|
export async function readOpenedFileSafely(params) {
|
|
3
4
|
if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
|
|
4
5
|
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
|
|
5
6
|
}
|
|
6
|
-
const buffer =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
7
|
+
const buffer = params.maxBytes === undefined
|
|
8
|
+
? await params.opened.handle.readFile()
|
|
9
|
+
: await readFileHandleBounded(params.opened.handle, params.maxBytes);
|
|
10
10
|
return {
|
|
11
11
|
buffer,
|
|
12
12
|
realPath: params.opened.realPath,
|
|
@@ -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;AAW7B,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;AAWD,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,CAuD3C;AA+CD,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,CAwBA;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,7 @@
|
|
|
1
1
|
import fsSync from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { readFileDescriptorBoundedSync, readFileHandleBounded } from "./bounded-read.js";
|
|
4
5
|
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
5
6
|
import { FsSafeError } from "./errors.js";
|
|
6
7
|
import { sameFileIdentity } from "./file-identity.js";
|
|
@@ -19,44 +20,6 @@ function resolveRegularFileReadFlags() {
|
|
|
19
20
|
? fsSync.constants.O_NOFOLLOW
|
|
20
21
|
: 0));
|
|
21
22
|
}
|
|
22
|
-
async function readFileHandleBounded(params) {
|
|
23
|
-
if (params.maxBytes === undefined) {
|
|
24
|
-
return await params.handle.readFile();
|
|
25
|
-
}
|
|
26
|
-
const chunks = [];
|
|
27
|
-
const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, Math.max(1, params.maxBytes + 1)));
|
|
28
|
-
let total = 0;
|
|
29
|
-
while (true) {
|
|
30
|
-
const { bytesRead } = await params.handle.read(scratch, 0, scratch.length, null);
|
|
31
|
-
if (bytesRead === 0) {
|
|
32
|
-
return Buffer.concat(chunks, total);
|
|
33
|
-
}
|
|
34
|
-
total += bytesRead;
|
|
35
|
-
if (total > params.maxBytes) {
|
|
36
|
-
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
37
|
-
}
|
|
38
|
-
chunks.push(Buffer.from(scratch.subarray(0, bytesRead)));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function readFileDescriptorBounded(params) {
|
|
42
|
-
if (params.maxBytes === undefined) {
|
|
43
|
-
return fsSync.readFileSync(params.fd);
|
|
44
|
-
}
|
|
45
|
-
const chunks = [];
|
|
46
|
-
const scratch = Buffer.allocUnsafe(Math.min(64 * 1024, Math.max(1, params.maxBytes + 1)));
|
|
47
|
-
let total = 0;
|
|
48
|
-
while (true) {
|
|
49
|
-
const bytesRead = fsSync.readSync(params.fd, scratch, 0, scratch.length, null);
|
|
50
|
-
if (bytesRead === 0) {
|
|
51
|
-
return Buffer.concat(chunks, total);
|
|
52
|
-
}
|
|
53
|
-
total += bytesRead;
|
|
54
|
-
if (total > params.maxBytes) {
|
|
55
|
-
throw new Error(`File exceeds ${params.maxBytes} bytes: ${params.filePath}`);
|
|
56
|
-
}
|
|
57
|
-
chunks.push(Buffer.from(scratch.subarray(0, bytesRead)));
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
23
|
export async function statRegularFile(filePath) {
|
|
61
24
|
let stat;
|
|
62
25
|
try {
|
|
@@ -96,7 +59,7 @@ export async function readRegularFile(params) {
|
|
|
96
59
|
throw Object.assign(new Error(`File not found: ${params.filePath}`), { code: "ENOENT" });
|
|
97
60
|
}
|
|
98
61
|
if (params.maxBytes !== undefined && result.stat.size > params.maxBytes) {
|
|
99
|
-
throw new
|
|
62
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${result.stat.size})`);
|
|
100
63
|
}
|
|
101
64
|
let handle;
|
|
102
65
|
try {
|
|
@@ -127,15 +90,13 @@ export async function readRegularFile(params) {
|
|
|
127
90
|
preOpenStat: result.stat,
|
|
128
91
|
});
|
|
129
92
|
if (params.maxBytes !== undefined && stat.size > params.maxBytes) {
|
|
130
|
-
throw new
|
|
93
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${stat.size})`);
|
|
131
94
|
}
|
|
132
95
|
// With a byte cap, avoid readFile(): a raced file growth would allocate
|
|
133
96
|
// the oversized content before the post-read check could reject it.
|
|
134
|
-
const buffer =
|
|
135
|
-
handle
|
|
136
|
-
|
|
137
|
-
maxBytes: params.maxBytes,
|
|
138
|
-
});
|
|
97
|
+
const buffer = params.maxBytes === undefined
|
|
98
|
+
? await handle.readFile()
|
|
99
|
+
: await readFileHandleBounded(handle, params.maxBytes);
|
|
139
100
|
return { buffer, stat };
|
|
140
101
|
}
|
|
141
102
|
finally {
|
|
@@ -160,15 +121,13 @@ function readOpenedRegularFileSync(params) {
|
|
|
160
121
|
preOpenStat: params.preOpenStat,
|
|
161
122
|
});
|
|
162
123
|
if (params.maxBytes !== undefined && stat.size > params.maxBytes) {
|
|
163
|
-
throw new
|
|
124
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${stat.size})`);
|
|
164
125
|
}
|
|
165
126
|
// Keep capped sync reads incremental for the same reason as async reads:
|
|
166
127
|
// readFileSync(fd) would buffer a raced oversized file before throwing.
|
|
167
|
-
const buffer =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
maxBytes: params.maxBytes,
|
|
171
|
-
});
|
|
128
|
+
const buffer = params.maxBytes === undefined
|
|
129
|
+
? fsSync.readFileSync(params.fd)
|
|
130
|
+
: readFileDescriptorBoundedSync(params.fd, params.maxBytes);
|
|
172
131
|
return { buffer, stat };
|
|
173
132
|
}
|
|
174
133
|
export function readRegularFileSync(params) {
|
|
@@ -178,7 +137,7 @@ export function readRegularFileSync(params) {
|
|
|
178
137
|
throw Object.assign(new Error(`File not found: ${params.filePath}`), { code: "ENOENT" });
|
|
179
138
|
}
|
|
180
139
|
if (params.maxBytes !== undefined && result.stat.size > params.maxBytes) {
|
|
181
|
-
throw new
|
|
140
|
+
throw new FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${result.stat.size})`);
|
|
182
141
|
}
|
|
183
142
|
const fd = fsSync.openSync(params.filePath, resolveRegularFileReadFlags());
|
|
184
143
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"root-context.d.ts","sourceRoot":"","sources":["../src/root-context.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,iBAAiB,UAAW,MAAM,WACM,CAAC;AAEtD,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAEtE;AAID,wBAAsB,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAuB9E;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAQtE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEvE"}
|
package/dist/root-impl.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Stats } from "node:fs";
|
|
2
2
|
import type { FileHandle } from "node:fs/promises";
|
|
3
3
|
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
4
|
+
import { type RenameIdentityPolicy } from "./pinned-write.js";
|
|
4
5
|
import { type ReadResult } from "./read-opened-file.js";
|
|
5
6
|
import type { DirEntry, PathStat } from "./types.js";
|
|
6
7
|
export type { DenyMutationPolicy } from "./deny-mutations.js";
|
|
8
|
+
export type { RenameIdentityPolicy } from "./pinned-write.js";
|
|
7
9
|
export { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
|
|
8
10
|
export type { ReadResult } from "./read-opened-file.js";
|
|
9
11
|
export type OpenResult = {
|
|
@@ -26,11 +28,12 @@ export type RootDefaults = {
|
|
|
26
28
|
mode?: number;
|
|
27
29
|
denyMutations?: DenyMutationPolicy;
|
|
28
30
|
nonBlockingRead?: boolean;
|
|
31
|
+
renameIdentity?: RenameIdentityPolicy;
|
|
29
32
|
symlinks?: SymlinkPolicy;
|
|
30
33
|
};
|
|
31
34
|
export type RootReadOptions = Pick<RootDefaults, "hardlinks" | "maxBytes" | "nonBlockingRead" | "symlinks">;
|
|
32
35
|
export type RootOpenOptions = Omit<RootReadOptions, "maxBytes">;
|
|
33
|
-
export type RootWriteOptions = Pick<RootDefaults, "denyMutations" | "mkdir" | "mode"> & {
|
|
36
|
+
export type RootWriteOptions = Pick<RootDefaults, "denyMutations" | "mkdir" | "mode" | "renameIdentity"> & {
|
|
34
37
|
encoding?: BufferEncoding;
|
|
35
38
|
overwrite?: boolean;
|
|
36
39
|
};
|
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;AAWnD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,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
|
@@ -13,7 +13,7 @@ import { withAsyncDirectoryGuards } from "./guarded-mutation.js";
|
|
|
13
13
|
import { assertMutationNotDenied, mergeDenyMutationPolicies, } from "./deny-mutations.js";
|
|
14
14
|
import { resolveOpenedFileRealPathForHandle } from "./opened-realpath.js";
|
|
15
15
|
import { isPinnedPathHelperSpawnError, runPinnedPathHelper } from "./pinned-path.js";
|
|
16
|
-
import { runPinnedCopyHelper,
|
|
16
|
+
import { runPinnedCopyHelper, runPinnedWriteWithRenamePolicy, } from "./pinned-write.js";
|
|
17
17
|
import { canFallbackFromPythonError, getFsSafePythonConfig } from "./pinned-python-config.js";
|
|
18
18
|
import { assertNoPathAliasEscape, PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
19
19
|
import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isPathInside, isSymlinkOpenError, } from "./path.js";
|
|
@@ -259,6 +259,7 @@ class RootHandle {
|
|
|
259
259
|
data,
|
|
260
260
|
mkdir: this.defaults.mkdir,
|
|
261
261
|
mode: this.defaults.mode,
|
|
262
|
+
renameIdentity: this.defaults.renameIdentity,
|
|
262
263
|
...options,
|
|
263
264
|
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
264
265
|
});
|
|
@@ -693,21 +694,23 @@ async function writeFileInRoot(root, params) {
|
|
|
693
694
|
async function commitPinnedWriteInRoot(root, pinned, params) {
|
|
694
695
|
let identity;
|
|
695
696
|
try {
|
|
696
|
-
identity = await
|
|
697
|
+
identity = await runPinnedWriteWithRenamePolicy({
|
|
697
698
|
rootPath: pinned.rootReal,
|
|
698
699
|
relativeParentPath: pinned.relativeParentPath,
|
|
699
700
|
basename: pinned.basename,
|
|
701
|
+
targetPath: pinned.targetPath,
|
|
702
|
+
renameIdentity: params.renameIdentity,
|
|
700
703
|
mkdir: params.mkdir !== false,
|
|
701
704
|
mode: params.mode ?? pinned.mode,
|
|
702
705
|
overwrite: params.overwrite,
|
|
703
|
-
input: {
|
|
704
|
-
kind: "buffer",
|
|
705
|
-
data: params.data,
|
|
706
|
-
encoding: params.encoding,
|
|
707
|
-
},
|
|
706
|
+
input: { kind: "buffer", data: params.data, encoding: params.encoding },
|
|
708
707
|
});
|
|
709
708
|
}
|
|
710
709
|
catch (error) {
|
|
710
|
+
const errorCode = error?.code;
|
|
711
|
+
if (errorCode === "file_lock_stale" || errorCode === "file_lock_timeout") {
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
711
714
|
if (params.overwrite === false && isAlreadyExistsError(error)) {
|
|
712
715
|
throw new FsSafeError("already-exists", "file already exists", {
|
|
713
716
|
cause: error instanceof Error ? error : undefined,
|
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":"AAMA,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
|
|
1
|
+
{"version":3,"file":"root-path.d.ts","sourceRoot":"","sources":["../src/root-path.ts"],"names":[],"mappings":"AAMA,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,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,CA+B3B;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,qBAAqB,GAAG,gBAAgB,CA+BnF;AAmkBD,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA6B7E"}
|
package/dist/root.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
|
|
1
|
+
export { DEFAULT_ROOT_MAX_BYTES, openLocalFileSafely, readLocalFileSafely, resolveOpenedFileRealPathForHandle, root, type DenyMutationPolicy, type HardlinkPolicy, type RenameIdentityPolicy, type OpenResult, type ReadResult, type Root, type RootAppendOptions, type RootCopyOptions, type RootCreateJsonOptions, type RootCreateOptions, type RootDefaults, type RootMkdirOptions, type RootMoveOptions, type RootOpenOptions, type RootOpenWritableOptions, type RootOptions, type RootReadOptions, type RootRemoveOptions, type RootWriteJsonOptions, type RootWriteOptions, type SymlinkPolicy, type WritableOpenMode, type WritableOpenResult, } from "./root-impl.js";
|
|
2
2
|
//# sourceMappingURL=root.d.ts.map
|
package/dist/root.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC"}
|
|
1
|
+
{"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../src/root.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,kCAAkC,EAClC,IAAI,EACJ,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,GACxB,MAAM,gBAAgB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secret-file.d.ts","sourceRoot":"","sources":["../src/secret-file.ts"],"names":[],"mappings":"AAWA,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;AAqIF,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,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,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,GAAG,OAAO,CAAC,IAAI,CAAC,CA+ChB"}
|
package/dist/secret-file.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import fsp from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { readFileDescriptorBoundedSync } from "./bounded-read.js";
|
|
4
5
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
5
6
|
import { FsSafeError } from "./errors.js";
|
|
6
7
|
import { sameFileIdentity } from "./file-identity.js";
|
|
@@ -100,7 +101,7 @@ function readSecretFileOutcomeSync(filePath, label, options = {}) {
|
|
|
100
101
|
};
|
|
101
102
|
}
|
|
102
103
|
try {
|
|
103
|
-
const raw =
|
|
104
|
+
const raw = readFileDescriptorBoundedSync(opened.fd, maxBytes).toString("utf8");
|
|
104
105
|
const secret = raw.trim();
|
|
105
106
|
if (!secret) {
|
|
106
107
|
return {
|
|
@@ -115,7 +116,9 @@ function readSecretFileOutcomeSync(filePath, label, options = {}) {
|
|
|
115
116
|
const normalized = normalizeSecretReadError(error);
|
|
116
117
|
return {
|
|
117
118
|
ok: false,
|
|
118
|
-
code: "
|
|
119
|
+
code: error instanceof FsSafeError && error.code === "too-large"
|
|
120
|
+
? "too-large"
|
|
121
|
+
: "invalid-path",
|
|
119
122
|
error: normalized,
|
|
120
123
|
message: `Failed to read ${label} file at ${resolvedPath}: ${String(normalized)}`,
|
|
121
124
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"secure-file.d.ts","sourceRoot":"","sources":["../src/secure-file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAYrC,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC5B,MAAM,kBAAkB,CAAC;AAK1B,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,sBAAsB,CAAC;IAC/B,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,EAAE,CAAC,EAAE,mBAAmB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;IACZ,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B,CAAC;AA0KF,wBAAsB,cAAc,CAClC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAc/B"}
|
package/dist/secure-file.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { constants as fsConstants } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { readFileHandleBounded } from "./bounded-read.js";
|
|
4
5
|
import { assertNoUnsafeDeviceReadPath } from "./device-path.js";
|
|
5
6
|
import { FsSafeError } from "./errors.js";
|
|
6
7
|
import { sameFileIdentity } from "./file-identity.js";
|
|
@@ -129,14 +130,15 @@ async function assertSecurePermissions(options, stat, realPath) {
|
|
|
129
130
|
}
|
|
130
131
|
return permissions;
|
|
131
132
|
}
|
|
132
|
-
async function readHandleWithTimeout(handle, timeoutMs) {
|
|
133
|
+
async function readHandleWithTimeout(handle, timeoutMs, maxBytes) {
|
|
134
|
+
const read = () => maxBytes === undefined ? handle.readFile() : readFileHandleBounded(handle, maxBytes);
|
|
133
135
|
if (timeoutMs === undefined || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
134
|
-
return await
|
|
136
|
+
return await read();
|
|
135
137
|
}
|
|
136
138
|
let timeout;
|
|
137
139
|
try {
|
|
138
140
|
return await Promise.race([
|
|
139
|
-
|
|
141
|
+
read(),
|
|
140
142
|
new Promise((_resolve, reject) => {
|
|
141
143
|
timeout = setTimeout(() => {
|
|
142
144
|
void handle.close().catch(() => undefined);
|
|
@@ -155,10 +157,7 @@ export async function readSecureFile(options) {
|
|
|
155
157
|
try {
|
|
156
158
|
await assertTrustedDirs(options, opened.realPath);
|
|
157
159
|
const permissions = await assertSecurePermissions(options, opened.pathStat, opened.realPath);
|
|
158
|
-
const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs);
|
|
159
|
-
if (options.io?.maxBytes !== undefined && buffer.byteLength > options.io.maxBytes) {
|
|
160
|
-
throw new FsSafeError("too-large", `${label(options)} exceeded maxBytes (${options.io.maxBytes}).`);
|
|
161
|
-
}
|
|
160
|
+
const buffer = await readHandleWithTimeout(opened.handle, options.io?.timeoutMs, options.io?.maxBytes);
|
|
162
161
|
return { buffer, realPath: opened.realPath, stat: opened.pathStat, permissions };
|
|
163
162
|
}
|
|
164
163
|
finally {
|
package/dist/sidecar-lock.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ export type SidecarLockRetryOptions = {
|
|
|
5
5
|
maxTimeout?: number;
|
|
6
6
|
randomize?: boolean;
|
|
7
7
|
};
|
|
8
|
-
export type SidecarLockStaleRecovery = "fail-closed"
|
|
8
|
+
export type SidecarLockStaleRecovery = "fail-closed"
|
|
9
|
+
/** @deprecated Stale locks now always fail closed. */
|
|
10
|
+
| "remove-if-unchanged";
|
|
11
|
+
/** @deprecated Stale-removal callbacks are retained for source compatibility but are not invoked. */
|
|
9
12
|
export type SidecarLockStaleSnapshot = {
|
|
10
13
|
lockPath: string;
|
|
11
14
|
normalizedTargetPath: string;
|
|
@@ -29,6 +32,7 @@ export type SidecarLockAcquireOptions<TPayload extends Record<string, unknown>>
|
|
|
29
32
|
nowMs: number;
|
|
30
33
|
heldByThisProcess: boolean;
|
|
31
34
|
}) => boolean | Promise<boolean>;
|
|
35
|
+
/** @deprecated Stale locks now always fail closed; this callback is not invoked. */
|
|
32
36
|
shouldRemoveStaleLock?: (snapshot: SidecarLockStaleSnapshot) => boolean | Promise<boolean>;
|
|
33
37
|
metadata?: Record<string, unknown>;
|
|
34
38
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,uBAAuB,GAAG;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,
|
|
1
|
+
{"version":3,"file":"sidecar-lock.d.ts","sourceRoot":"","sources":["../src/sidecar-lock.ts"],"names":[],"mappings":"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"}
|
package/dist/sidecar-lock.js
CHANGED
|
@@ -67,36 +67,6 @@ async function lockSnapshotStillPresent(lockPath, observed) {
|
|
|
67
67
|
const current = await readLockSnapshot(lockPath);
|
|
68
68
|
return !!current && !!observed && snapshotMatches(current, observed);
|
|
69
69
|
}
|
|
70
|
-
async function removeStaleLockIfAllowed(params) {
|
|
71
|
-
if (!params.shouldRemoveStaleLock) {
|
|
72
|
-
return "not-approved";
|
|
73
|
-
}
|
|
74
|
-
if (params.snapshot.raw === undefined) {
|
|
75
|
-
return "not-approved";
|
|
76
|
-
}
|
|
77
|
-
if (!(await params.shouldRemoveStaleLock({
|
|
78
|
-
lockPath: params.lockPath,
|
|
79
|
-
normalizedTargetPath: params.normalizedTargetPath,
|
|
80
|
-
raw: params.snapshot.raw,
|
|
81
|
-
payload: params.snapshot.payload,
|
|
82
|
-
}))) {
|
|
83
|
-
return "not-approved";
|
|
84
|
-
}
|
|
85
|
-
const current = await readLockSnapshot(params.lockPath);
|
|
86
|
-
if (!current || !snapshotMatches(current, params.snapshot)) {
|
|
87
|
-
return "changed";
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
await fs.rm(params.lockPath, { force: true });
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
if (err.code === "ENOENT") {
|
|
94
|
-
return "changed";
|
|
95
|
-
}
|
|
96
|
-
return "not-approved";
|
|
97
|
-
}
|
|
98
|
-
return "removed";
|
|
99
|
-
}
|
|
100
70
|
function snapshotMatchesSync(lockPath, observed) {
|
|
101
71
|
try {
|
|
102
72
|
const stat = fsSync.lstatSync(lockPath);
|
|
@@ -281,18 +251,9 @@ export function createSidecarLockManager(key) {
|
|
|
281
251
|
if (!(await lockSnapshotStillPresent(lockPath, snapshot))) {
|
|
282
252
|
continue;
|
|
283
253
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
lockPath,
|
|
288
|
-
normalizedTargetPath,
|
|
289
|
-
snapshot,
|
|
290
|
-
shouldRemoveStaleLock: options.shouldRemoveStaleLock,
|
|
291
|
-
});
|
|
292
|
-
if (removal === "removed" || removal === "changed") {
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
254
|
+
// A pathname recheck followed by unlink is not atomic: a fresh lock
|
|
255
|
+
// can replace the observed file in between. Legacy recovery inputs
|
|
256
|
+
// remain accepted, but third-party stale locks always fail closed.
|
|
296
257
|
throw Object.assign(new Error(`file lock stale for ${normalizedTargetPath}`), {
|
|
297
258
|
code: "file_lock_stale",
|
|
298
259
|
lockPath,
|
package/dist/test-hooks.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export type FsSafeTestHooks = {
|
|
|
7
7
|
beforeFileStorePruneDescend?: (dirPath: string) => Promise<void> | void;
|
|
8
8
|
beforeFileStoreSyncPrivateWrite?: (filePath: string) => void;
|
|
9
9
|
beforeRootFallbackMutation?: (operation: "mkdir" | "move" | "remove", targetPath: string) => Promise<void> | void;
|
|
10
|
+
afterPinnedWriteFallbackRename?: (targetPath: string) => Promise<void> | void;
|
|
10
11
|
beforeSiblingTempWrite?: (tempPath: string) => Promise<void> | void;
|
|
11
12
|
beforeTrashMove?: (targetPath: string, destPath: string) => void;
|
|
12
13
|
};
|
package/dist/test-hooks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-hooks.d.ts","sourceRoot":"","sources":["../src/test-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG;IAC5B,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3E,2BAA2B,CAAC,EAAE,CAC5B,SAAS,EAAE,OAAO,GAAG,OAAO,EAC5B,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,2BAA2B,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxE,+BAA+B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,0BAA0B,CAAC,EAAE,CAC3B,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,EACtC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpE,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAClE,CAAC;AAQF,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI,CAKzE"}
|
|
1
|
+
{"version":3,"file":"test-hooks.d.ts","sourceRoot":"","sources":["../src/test-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG;IAC5B,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC/D,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3E,2BAA2B,CAAC,EAAE,CAC5B,SAAS,EAAE,OAAO,GAAG,OAAO,EAC5B,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,2BAA2B,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxE,+BAA+B,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7D,0BAA0B,CAAC,EAAE,CAC3B,SAAS,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,EACtC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,8BAA8B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9E,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACpE,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CAClE,CAAC;AAQF,wBAAgB,kBAAkB,IAAI,eAAe,GAAG,SAAS,CAEhE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,IAAI,CAKzE"}
|