@openclaw/fs-safe 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -1
- package/README.md +8 -9
- package/dist/archive-staging.d.ts.map +1 -1
- package/dist/archive-staging.js +12 -1
- package/dist/file-store-boundary.js +2 -2
- package/dist/file-store.d.ts.map +1 -1
- package/dist/file-store.js +6 -5
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +6 -6
- package/dist/json-document-store.d.ts +1 -1
- package/dist/json-document-store.d.ts.map +1 -1
- package/dist/json-document-store.js +5 -3
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +157 -4
- package/dist/json-stringify.d.ts +2 -0
- package/dist/json-stringify.d.ts.map +1 -0
- package/dist/json-stringify.js +7 -0
- package/dist/json.d.ts.map +1 -1
- package/dist/json.js +3 -2
- package/dist/local-roots.d.ts.map +1 -1
- package/dist/local-roots.js +4 -1
- package/dist/path.d.ts +1 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +4 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +7 -10
- package/dist/pinned-python.d.ts.map +1 -1
- package/dist/pinned-python.js +158 -69
- package/dist/pinned-write.d.ts +3 -1
- package/dist/pinned-write.d.ts.map +1 -1
- package/dist/pinned-write.js +54 -45
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +6 -2
- package/dist/root-path.js +2 -2
- package/dist/root-paths.js +25 -3
- package/dist/secret-file.d.ts.map +1 -1
- package/dist/secret-file.js +76 -50
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +3 -1
- package/dist/temp-target.js +1 -1
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +8 -1
- package/docs/index.md +1 -1
- package/docs/json-store.md +3 -1
- package/docs/python-helper.md +3 -3
- package/docs/root.md +5 -6
- package/package.json +4 -4
- package/dist/archive-utils.d.ts +0 -3
- package/dist/archive-utils.d.ts.map +0 -1
- package/dist/archive-utils.js +0 -26
- package/dist/boundary-file-read.d.ts +0 -44
- package/dist/boundary-file-read.d.ts.map +0 -1
- package/dist/boundary-file-read.js +0 -129
- package/dist/boundary-path.d.ts +0 -39
- package/dist/boundary-path.d.ts.map +0 -1
- package/dist/boundary-path.js +0 -598
- package/dist/fs-pinned-path-helper.d.ts +0 -7
- package/dist/fs-pinned-path-helper.d.ts.map +0 -1
- package/dist/fs-pinned-path-helper.js +0 -182
- package/dist/fs-pinned-write-helper.d.ts +0 -21
- package/dist/fs-pinned-write-helper.d.ts.map +0 -1
- package/dist/fs-pinned-write-helper.js +0 -263
- package/dist/hardlink-guards.d.ts +0 -7
- package/dist/hardlink-guards.d.ts.map +0 -1
- package/dist/hardlink-guards.js +0 -30
- package/dist/install-safe-path.d.ts +0 -20
- package/dist/install-safe-path.d.ts.map +0 -1
- package/dist/install-safe-path.js +0 -94
- package/dist/json-file.d.ts +0 -3
- package/dist/json-file.d.ts.map +0 -1
- package/dist/json-file.js +0 -123
- package/dist/json-files.d.ts +0 -20
- package/dist/json-files.d.ts.map +0 -1
- package/dist/json-files.js +0 -153
- package/dist/path-alias-guards.d.ts +0 -19
- package/dist/path-alias-guards.d.ts.map +0 -1
- package/dist/path-alias-guards.js +0 -21
- package/dist/path-guards.d.ts +0 -7
- package/dist/path-guards.d.ts.map +0 -1
- package/dist/path-guards.js +0 -49
- package/dist/path-safety.d.ts +0 -12
- package/dist/path-safety.d.ts.map +0 -1
- package/dist/path-safety.js +0 -50
- package/dist/path-scope.d.ts +0 -2
- package/dist/path-scope.d.ts.map +0 -1
- package/dist/path-scope.js +0 -1
- package/dist/private-file-store.d.ts +0 -60
- package/dist/private-file-store.d.ts.map +0 -1
- package/dist/private-file-store.js +0 -233
- package/dist/safe-open-sync.d.ts +0 -24
- package/dist/safe-open-sync.d.ts.map +0 -1
- package/dist/safe-open-sync.js +0 -71
- package/dist/safe-root.d.ts +0 -123
- package/dist/safe-root.d.ts.map +0 -1
- package/dist/safe-root.js +0 -1060
- package/dist/secure-temp-workspace.d.ts +0 -25
- package/dist/secure-temp-workspace.d.ts.map +0 -1
- package/dist/secure-temp-workspace.js +0 -136
- package/dist/sibling-temp-file.d.ts +0 -16
- package/dist/sibling-temp-file.d.ts.map +0 -1
- package/dist/sibling-temp-file.js +0 -73
- package/dist/sibling-temp-write.d.ts +0 -8
- package/dist/sibling-temp-write.d.ts.map +0 -1
- package/dist/sibling-temp-write.js +0 -40
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.2.6 - 2026-05-17
|
|
4
|
+
|
|
5
|
+
### Security and Correctness
|
|
6
|
+
|
|
7
|
+
- Harden DeepSec-reported temp path handling, private secret writes, pinned helper commits, fallback mkdir writes, and dot-prefixed root relative paths against symlink, race, and relative-path edge cases.
|
|
8
|
+
|
|
9
|
+
## 0.2.5 - 2026-05-16
|
|
10
|
+
|
|
11
|
+
### Security and Correctness
|
|
12
|
+
|
|
13
|
+
- Reject Windows drive-letter paths on POSIX secure-file reads, missing path fallbacks under symlinked parents, swapped archive destination roots, broad domain ACL principals, and hardlinked sync store reads.
|
|
14
|
+
- Preserve top-level JSON `null` values in JSON stores and reject non-document JSON values such as top-level `undefined` before replacing files.
|
|
15
|
+
- Fix timeout error factories, root-relative path resolution at filesystem roots, dot-prefixed child names in root path helpers, durable queue directory modes, and pinned helper stale-worker events.
|
|
16
|
+
|
|
17
|
+
### Build
|
|
18
|
+
|
|
19
|
+
- Clean `dist` before package builds and make prepack fail deterministically when dependencies are missing instead of resolving new toolchain versions at publish time.
|
|
20
|
+
|
|
21
|
+
### Tests
|
|
22
|
+
|
|
23
|
+
- Added Clawpatch regression coverage for secure reads, path fallback validation, sync stores, archive destination races, Windows ACL classification, JSON writes/stores, durable queue modes, root create no-clobber behavior, and pinned helper worker replacement.
|
|
4
24
|
|
|
5
25
|
## 0.2.4 - 2026-05-14
|
|
6
26
|
|
package/README.md
CHANGED
|
@@ -66,18 +66,15 @@ before first use when you need a strict environment policy:
|
|
|
66
66
|
import { configureFsSafePython } from "@openclaw/fs-safe";
|
|
67
67
|
|
|
68
68
|
configureFsSafePython({ mode: "auto" }); // default: use helper, fall back if unavailable
|
|
69
|
-
configureFsSafePython({ mode: "off" }); // never spawn Python;
|
|
69
|
+
configureFsSafePython({ mode: "off" }); // never spawn Python; writes that need it fail closed
|
|
70
70
|
configureFsSafePython({ mode: "require" }); // fail closed if helper cannot start
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
Equivalent env vars: `FS_SAFE_PYTHON_MODE=auto|off|require` and
|
|
74
|
-
`FS_SAFE_PYTHON=/path/to/python3`.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
directories between validation and mutation. Windows already uses the Node
|
|
79
|
-
fallback path. See the [Python helper policy](docs/python-helper.md) for
|
|
80
|
-
deployment guidance.
|
|
74
|
+
`FS_SAFE_PYTHON=/path/to/python3`. On POSIX, disabling or losing the helper now
|
|
75
|
+
fails closed for root write paths that require fd-relative parent commits.
|
|
76
|
+
Windows already uses the Node fallback path. See the
|
|
77
|
+
[Python helper policy](docs/python-helper.md) for deployment guidance.
|
|
81
78
|
|
|
82
79
|
## Quick start
|
|
83
80
|
|
|
@@ -270,6 +267,8 @@ the common merge-into-defaults case. Standalone helpers use options bags
|
|
|
270
267
|
because they do not carry a bound root and often need multiple authority, path,
|
|
271
268
|
and policy knobs.
|
|
272
269
|
|
|
270
|
+
Sidecar locks fail closed on stale holders by default. Use the [file lock docs](docs/sidecar-lock.md) for the lower-level `remove-if-unchanged` recovery mode when your application can prove the old owner is gone.
|
|
271
|
+
|
|
273
272
|
Use `fileStore()` for cache/blob/media-style directories where callers
|
|
274
273
|
need safe relative paths, size limits, atomic replacement, stream writes, and
|
|
275
274
|
TTL cleanup behind one root. Pass `private: true` for credentials, auth
|
|
@@ -433,7 +432,7 @@ Current `FsSafeErrorCode` values are `already-exists`, `hardlink`, `helper-faile
|
|
|
433
432
|
- root-bounded APIs resolve paths against a configured root and reject canonical escapes
|
|
434
433
|
- reads open with `O_NOFOLLOW` where available, then verify fd identity matches the path identity before returning the buffer or handle
|
|
435
434
|
- writes use pinned parent-directory helpers and atomic replacement on POSIX, with verified post-write identity
|
|
436
|
-
- `remove`, `mkdir`, `move`, `stat`, `list`, and parent-fd writes use one persistent fd-relative Python helper on POSIX
|
|
435
|
+
- `remove`, `mkdir`, `move`, `stat`, `list`, and parent-fd writes use one persistent fd-relative Python helper on POSIX; security-sensitive writes fail closed when that helper is disabled or unavailable
|
|
437
436
|
- archive extraction stages into a private directory and merges through the same boundary checks used by direct writes
|
|
438
437
|
|
|
439
438
|
## Limitations
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,wBAAwB,GAChC,2BAA2B,GAC3B,qBAAqB,GACrB,+BAA+B,CAAC;AAEpC,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,EAAE,wBAAwB,CAAC;gBAEnB,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAKpF;AAkCD,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"archive-staging.d.ts","sourceRoot":"","sources":["../src/archive-staging.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,wBAAwB,GAChC,2BAA2B,GAC3B,qBAAqB,GACrB,+BAA+B,CAAC;AAEpC,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,IAAI,EAAE,wBAAwB,CAAC;gBAEnB,IAAI,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAKpF;AAkCD,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BnF;AA6CD,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmChB;AAiGD,wBAAsB,4BAA4B,CAAC,CAAC,EAAE,MAAM,EAAE;IAC5D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CACzC,GAAG,OAAO,CAAC,CAAC,CAAC,CAkBb;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyEhB;AAED,wBAAgB,kCAAkC,CAAC,YAAY,EAAE,MAAM,GAAG,oBAAoB,CAE7F"}
|
package/dist/archive-staging.js
CHANGED
|
@@ -50,7 +50,18 @@ export async function prepareArchiveDestinationDir(destDir) {
|
|
|
50
50
|
if (!stat.isDirectory()) {
|
|
51
51
|
throw new ArchiveSecurityError("destination-not-directory", "archive destination is not a directory");
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
const realPath = await fs.realpath(destDir);
|
|
54
|
+
const realStat = await fs.stat(realPath);
|
|
55
|
+
const postStat = await fs.lstat(destDir);
|
|
56
|
+
if (realStat.dev !== stat.dev ||
|
|
57
|
+
realStat.ino !== stat.ino ||
|
|
58
|
+
postStat.isSymbolicLink() ||
|
|
59
|
+
!postStat.isDirectory() ||
|
|
60
|
+
postStat.dev !== stat.dev ||
|
|
61
|
+
postStat.ino !== stat.ino) {
|
|
62
|
+
throw new ArchiveSecurityError("destination-symlink-traversal", "archive destination changed during extraction");
|
|
63
|
+
}
|
|
64
|
+
return realPath;
|
|
54
65
|
}
|
|
55
66
|
async function assertNoSymlinkTraversal(params) {
|
|
56
67
|
const parts = params.relPath.split(/[\\/]+/).filter(Boolean);
|
|
@@ -5,7 +5,7 @@ import { Transform } from "node:stream";
|
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import { assertSyncDirectoryGuard as assertDirectoryGuardSync, createSyncDirectoryGuard, } from "./directory-guard.js";
|
|
7
7
|
import { FsSafeError } from "./errors.js";
|
|
8
|
-
import { isPathInside } from "./path.js";
|
|
8
|
+
import { isPathInside, isPathRelativeEscape } from "./path.js";
|
|
9
9
|
import { resolveOpenedFileRealPathForHandle, root } from "./root.js";
|
|
10
10
|
import { resolveSecureTempRoot } from "./secure-temp-dir.js";
|
|
11
11
|
function parentRelativePath(relativePath) {
|
|
@@ -131,7 +131,7 @@ export function ensureParentSync(params) {
|
|
|
131
131
|
const rootDir = path.resolve(params.rootDir);
|
|
132
132
|
const dir = path.dirname(path.resolve(params.filePath));
|
|
133
133
|
const relative = path.relative(rootDir, dir);
|
|
134
|
-
if (
|
|
134
|
+
if (isPathRelativeEscape(relative)) {
|
|
135
135
|
throw new FsSafeError("outside-workspace", "file path escapes store root");
|
|
136
136
|
}
|
|
137
137
|
syncFs.mkdirSync(rootDir, { recursive: true, mode: params.mode });
|
package/dist/file-store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAG5C,OAAO,EAEL,KAAK,qBAAqB,EAC3B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAUnE,OAAO,EAAmB,KAAK,oBAAoB,EAAE,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../src/file-store.ts"],"names":[],"mappings":"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"}
|
package/dist/file-store.js
CHANGED
|
@@ -8,6 +8,7 @@ import { pruneExpiredStoreEntries, } from "./file-store-prune.js";
|
|
|
8
8
|
import { assertSyncDirectoryGuard, ensureParentInRoot, ensureParentSync, openWritableStoreRoot, writeStreamToTempSource, } from "./file-store-boundary.js";
|
|
9
9
|
import { readFileStoreCopySource } from "./file-store-source.js";
|
|
10
10
|
import { createJsonStore } from "./json-document-store.js";
|
|
11
|
+
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
11
12
|
import { isPathInside, resolveSafeRelativePath } from "./path.js";
|
|
12
13
|
import { root } from "./root.js";
|
|
13
14
|
import { DEFAULT_ROOT_MAX_BYTES } from "./root-impl.js";
|
|
@@ -232,7 +233,7 @@ export function fileStore(options) {
|
|
|
232
233
|
exists: async (relativePath) => await (await openRoot()).exists(assertRelativePath(relativePath)),
|
|
233
234
|
writeText: async (relativePath, data, writeOptions) => await write(relativePath, data, writeOptions),
|
|
234
235
|
writeJson: async (relativePath, data, writeOptions) => {
|
|
235
|
-
const json =
|
|
236
|
+
const json = stringifyJsonDocument(data, null, 2);
|
|
236
237
|
return await write(relativePath, writeOptions?.trailingNewline === false ? json : `${json}\n`, writeOptions);
|
|
237
238
|
},
|
|
238
239
|
json: (relativePath, jsonOptions) => {
|
|
@@ -245,14 +246,14 @@ export function fileStore(options) {
|
|
|
245
246
|
}
|
|
246
247
|
catch (error) {
|
|
247
248
|
if (isNotFound(error)) {
|
|
248
|
-
return
|
|
249
|
+
return undefined;
|
|
249
250
|
}
|
|
250
251
|
throw error;
|
|
251
252
|
}
|
|
252
253
|
},
|
|
253
254
|
readRequired: async () => await (await openRoot()).readJson(assertRelativePath(relativePath)),
|
|
254
255
|
write: async (value, options) => {
|
|
255
|
-
const json =
|
|
256
|
+
const json = stringifyJsonDocument(value, null, 2);
|
|
256
257
|
await write(relativePath, options?.trailingNewline === false ? json : `${json}\n`);
|
|
257
258
|
},
|
|
258
259
|
}, jsonOptions);
|
|
@@ -403,7 +404,7 @@ export function fileStoreSync(options) {
|
|
|
403
404
|
absolutePath: targetPath,
|
|
404
405
|
rootPath: rootDir,
|
|
405
406
|
boundaryLabel: "store root",
|
|
406
|
-
rejectHardlinks:
|
|
407
|
+
rejectHardlinks: true,
|
|
407
408
|
});
|
|
408
409
|
if (!opened.ok) {
|
|
409
410
|
return handleSyncStoreReadOpenFailure(opened);
|
|
@@ -426,7 +427,7 @@ export function fileStoreSync(options) {
|
|
|
426
427
|
write,
|
|
427
428
|
writeText: (relativePath, data, writeOptions) => write(relativePath, data, writeOptions),
|
|
428
429
|
writeJson: (relativePath, data, writeOptions) => {
|
|
429
|
-
const json =
|
|
430
|
+
const json = stringifyJsonDocument(data, null, 2);
|
|
430
431
|
return write(relativePath, writeOptions?.trailingNewline === false ? json : `${json}\n`, writeOptions);
|
|
431
432
|
},
|
|
432
433
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guarded-mkdir.d.ts","sourceRoot":"","sources":["../src/guarded-mkdir.ts"],"names":[],"mappings":"AAWA,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACnE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkChB"}
|
package/dist/guarded-mkdir.js
CHANGED
|
@@ -2,17 +2,17 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { assertAsyncDirectoryGuard, createAsyncDirectoryGuard } from "./directory-guard.js";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
|
+
import { isPathRelativeEscape } from "./path.js";
|
|
5
6
|
function isSameOrChildPath(candidate, parent) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
function isPathEscape(relativePath) {
|
|
9
|
-
return relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath);
|
|
7
|
+
const parentPrefix = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`;
|
|
8
|
+
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
10
9
|
}
|
|
11
10
|
export async function mkdirPathComponentsWithGuards(params) {
|
|
12
11
|
const root = path.resolve(params.rootReal);
|
|
12
|
+
const rootCanonical = path.resolve(await fs.realpath(root));
|
|
13
13
|
const target = path.resolve(params.targetPath);
|
|
14
14
|
const relative = path.relative(root, target);
|
|
15
|
-
if (
|
|
15
|
+
if (isPathRelativeEscape(relative)) {
|
|
16
16
|
throw new FsSafeError("outside-workspace", "directory is outside workspace root");
|
|
17
17
|
}
|
|
18
18
|
let current = root;
|
|
@@ -35,7 +35,7 @@ export async function mkdirPathComponentsWithGuards(params) {
|
|
|
35
35
|
}
|
|
36
36
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
37
37
|
// segment at a time and realpath-check each segment before descending.
|
|
38
|
-
if (!isSameOrChildPath(path.resolve(await fs.realpath(next)),
|
|
38
|
+
if (!isSameOrChildPath(path.resolve(await fs.realpath(next)), rootCanonical)) {
|
|
39
39
|
throw new FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
40
40
|
}
|
|
41
41
|
await createAsyncDirectoryGuard(next);
|
|
@@ -22,7 +22,7 @@ export type JsonStore<T> = {
|
|
|
22
22
|
};
|
|
23
23
|
export type JsonStoreAdapter<T> = {
|
|
24
24
|
filePath: string;
|
|
25
|
-
readIfExists(): Promise<T |
|
|
25
|
+
readIfExists(): Promise<T | undefined>;
|
|
26
26
|
readRequired(): Promise<T>;
|
|
27
27
|
write(value: T, options?: {
|
|
28
28
|
trailingNewline?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAElE,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,IAAI,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,IAAI,OAAO,CAAC,CAAC,GAAG,
|
|
1
|
+
{"version":3,"file":"json-document-store.d.ts","sourceRoot":"","sources":["../src/json-document-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAElE,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,OAAO,GAAG,oBAAoB,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,IAAI,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/B,MAAM,CAAC,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAChC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACxE,CAAC;AAEF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,IAAI,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACvC,YAAY,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3B,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzE,CAAC;AA2BF,wBAAgB,eAAe,CAAC,CAAC,EAC/B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,oBAAyB,GACjC,SAAS,CAAC,CAAC,CAAC,CA6Dd"}
|
|
@@ -24,10 +24,11 @@ export function createJsonStore(adapter, options = {}) {
|
|
|
24
24
|
const lockOptions = resolveLockOptions(adapter.filePath, options);
|
|
25
25
|
const locks = lockOptions ? createSidecarLockManager(lockOptions.managerKey) : null;
|
|
26
26
|
async function read() {
|
|
27
|
-
return
|
|
27
|
+
return await adapter.readIfExists();
|
|
28
28
|
}
|
|
29
29
|
async function readOr(fallback) {
|
|
30
|
-
|
|
30
|
+
const current = await read();
|
|
31
|
+
return current === undefined ? cloneFallback(fallback) : current;
|
|
31
32
|
}
|
|
32
33
|
async function write(value) {
|
|
33
34
|
await adapter.write(value, {
|
|
@@ -64,7 +65,8 @@ export function createJsonStore(adapter, options = {}) {
|
|
|
64
65
|
return next;
|
|
65
66
|
}),
|
|
66
67
|
updateOr: async (fallback, run) => await withOptionalLock(async () => {
|
|
67
|
-
const
|
|
68
|
+
const current = await read();
|
|
69
|
+
const next = await run(current === undefined ? cloneFallback(fallback) : current);
|
|
68
70
|
await write(next);
|
|
69
71
|
return next;
|
|
70
72
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-durable-queue.d.ts","sourceRoot":"","sources":["../src/json-durable-queue.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,0BAA0B,CAAC,CAAC,IAAI;IAC1C,KAAK,EAAE,CAAC,CAAC;IACT,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,2BAA2B,CAAC,CAAC,IAAI;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAOF,eAAO,MAAM,0CAA0C,QAAmB,CAAC;AAY3E,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE;AAED,wBAAsB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpF;AAmBD,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,GACT,0BAA0B,CAM5B;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhB;AA+KD,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhB;AAiDD,wBAAsB,yBAAyB,CAAC,CAAC,EAC/C,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClC,OAAO,CAAC,CAAC,CAAC,CAOZ;AAED,wBAAsB,wBAAwB,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/F;AAED,wBAAsB,yBAAyB,CAAC,CAAC,EAAE,MAAM,EAAE;IACzD,KAAK,EAAE,0BAA0B,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAwBpB;AAED,wBAAsB,kCAAkC,CAAC,CAAC,EACxD,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC,GACtC,OAAO,CAAC,CAAC,EAAE,CAAC,CAkDd;AAED,wBAAsB,iCAAiC,CAAC,MAAM,EAAE;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { sameFileIdentity } from "./file-identity.js";
|
|
4
|
+
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
4
5
|
import { replaceFileAtomic } from "./replace-file.js";
|
|
5
6
|
import { assertSafePathSegment } from "./safe-path-segment.js";
|
|
6
7
|
export const DEFAULT_JSON_DURABLE_QUEUE_ENTRY_MAX_BYTES = 16 * 1024 * 1024;
|
|
@@ -48,13 +49,163 @@ export function resolveJsonDurableQueueEntryPaths(queueDir, id) {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
export async function ensureJsonDurableQueueDirs(params) {
|
|
51
|
-
await
|
|
52
|
-
await
|
|
52
|
+
const roots = await queueValidationRoots(params.queueDir, params.failedDir);
|
|
53
|
+
await ensureJsonDurableQueueDir(params.queueDir, roots.queueRoot);
|
|
54
|
+
await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
|
|
55
|
+
}
|
|
56
|
+
async function ensureJsonDurableQueueDir(dir, validationRoot) {
|
|
57
|
+
const root = validationRoot
|
|
58
|
+
? validationRoot
|
|
59
|
+
: queueValidationRoot(dir);
|
|
60
|
+
await assertNoSymlinkDirectorySegments(root, dir, true);
|
|
61
|
+
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
62
|
+
await assertNoSymlinkDirectorySegments(root, dir, false);
|
|
63
|
+
await chmodQueueDirectory(dir);
|
|
64
|
+
}
|
|
65
|
+
async function assertJsonDurableQueueDir(dir, validationRoot) {
|
|
66
|
+
const root = validationRoot
|
|
67
|
+
? validationRoot
|
|
68
|
+
: queueValidationRoot(dir);
|
|
69
|
+
await assertNoSymlinkDirectorySegments(root, dir, false);
|
|
70
|
+
}
|
|
71
|
+
function commonPathAncestor(paths) {
|
|
72
|
+
const resolved = paths.map((entry) => path.resolve(entry));
|
|
73
|
+
const root = path.parse(resolved[0] ?? process.cwd()).root;
|
|
74
|
+
const parts = resolved.map((entry) => path.relative(root, entry).split(path.sep));
|
|
75
|
+
const common = [];
|
|
76
|
+
for (let index = 0; parts.every((part) => index < part.length); index++) {
|
|
77
|
+
const segment = parts[0]?.[index];
|
|
78
|
+
if (!segment || !parts.every((part) => part[index] === segment))
|
|
79
|
+
break;
|
|
80
|
+
common.push(segment);
|
|
81
|
+
}
|
|
82
|
+
return path.join(root, ...common);
|
|
83
|
+
}
|
|
84
|
+
function samePathRoot(paths) {
|
|
85
|
+
const roots = paths.map((entry) => path.parse(path.resolve(entry)).root);
|
|
86
|
+
const first = process.platform === "win32" ? roots[0]?.toLowerCase() : roots[0];
|
|
87
|
+
return roots.every((root) => (process.platform === "win32" ? root.toLowerCase() : root) === first);
|
|
88
|
+
}
|
|
89
|
+
async function queueValidationRoots(queueDir, failedDir) {
|
|
90
|
+
if (samePathRoot([queueDir, failedDir])) {
|
|
91
|
+
const common = commonPathAncestor([queueDir, failedDir]);
|
|
92
|
+
const root = queueValidationRoot(common);
|
|
93
|
+
return { failedRoot: root, queueRoot: root };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
failedRoot: queueValidationRoot(failedDir),
|
|
97
|
+
queueRoot: queueValidationRoot(queueDir),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function queueValidationRoot(dir) {
|
|
101
|
+
return {
|
|
102
|
+
path: path.parse(path.resolve(dir)).root,
|
|
103
|
+
allowSymlinkBase: process.platform === "darwin",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function isDarwinSystemAlias(dir, stat) {
|
|
107
|
+
if (process.platform !== "darwin" || !stat.isSymbolicLink()) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const resolved = path.resolve(dir);
|
|
111
|
+
if (resolved !== "/tmp" && resolved !== "/var") {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
return await fs.promises.realpath(resolved).then((realPath) => realPath === `/private${resolved}`, () => false);
|
|
115
|
+
}
|
|
116
|
+
async function assertNoSymlinkDirectorySegments(validationRoot, dir, allowMissing) {
|
|
117
|
+
let base = path.resolve(validationRoot.path);
|
|
118
|
+
let target = path.resolve(dir);
|
|
119
|
+
let current = base;
|
|
120
|
+
let baseStat = await fs.promises.lstat(base);
|
|
121
|
+
if (baseStat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
|
|
122
|
+
const relative = path.relative(base, target);
|
|
123
|
+
if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
124
|
+
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
125
|
+
}
|
|
126
|
+
base = await fs.promises.realpath(base);
|
|
127
|
+
target = path.join(base, ...relative.split(path.sep).filter(Boolean));
|
|
128
|
+
current = base;
|
|
129
|
+
baseStat = await fs.promises.lstat(base);
|
|
130
|
+
}
|
|
131
|
+
if (baseStat.isSymbolicLink() || !baseStat.isDirectory()) {
|
|
132
|
+
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
133
|
+
}
|
|
134
|
+
const segments = path.relative(base, target).split(path.sep).filter(Boolean);
|
|
135
|
+
for (let index = 0; index < segments.length; index++) {
|
|
136
|
+
const segment = segments[index];
|
|
137
|
+
if (segment === undefined) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
current = path.join(current, segment);
|
|
141
|
+
let stat;
|
|
142
|
+
try {
|
|
143
|
+
stat = await fs.promises.lstat(current);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (allowMissing && error.code === "ENOENT") {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
152
|
+
if (stat.isSymbolicLink() && validationRoot.allowSymlinkBase) {
|
|
153
|
+
if (await isDarwinSystemAlias(current, stat)) {
|
|
154
|
+
current = await fs.promises.realpath(current);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function chmodQueueDirectory(dir) {
|
|
163
|
+
const noFollow = typeof fs.constants.O_NOFOLLOW === "number" && process.platform !== "win32"
|
|
164
|
+
? fs.constants.O_NOFOLLOW
|
|
165
|
+
: 0;
|
|
166
|
+
const directoryFlag = typeof fs.constants.O_DIRECTORY === "number" && process.platform !== "win32"
|
|
167
|
+
? fs.constants.O_DIRECTORY
|
|
168
|
+
: 0;
|
|
169
|
+
if (noFollow || directoryFlag) {
|
|
170
|
+
let handle;
|
|
171
|
+
try {
|
|
172
|
+
handle = await fs.promises.open(dir, fs.constants.O_RDONLY | noFollow | directoryFlag);
|
|
173
|
+
const stat = await handle.stat();
|
|
174
|
+
if (!stat.isDirectory()) {
|
|
175
|
+
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
await handle.chmod(0o700);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Best-effort on platforms that do not enforce POSIX modes.
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
try {
|
|
187
|
+
await handle?.close();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// Best-effort cleanup after chmod/open failures.
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const stat = await fs.promises.lstat(dir);
|
|
195
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
196
|
+
throw new Error(`durable queue path is not a directory: ${dir}`);
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
await fs.promises.chmod(dir, 0o700);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Best-effort on platforms that do not enforce POSIX modes.
|
|
203
|
+
}
|
|
53
204
|
}
|
|
54
205
|
export async function writeJsonDurableQueueEntry(params) {
|
|
55
206
|
await replaceFileAtomic({
|
|
56
207
|
filePath: params.filePath,
|
|
57
|
-
content:
|
|
208
|
+
content: stringifyJsonDocument(params.entry, null, 2),
|
|
58
209
|
mode: 0o600,
|
|
59
210
|
tempPrefix: params.tempPrefix,
|
|
60
211
|
});
|
|
@@ -195,6 +346,8 @@ export async function loadPendingJsonDurableQueueEntries(options) {
|
|
|
195
346
|
}
|
|
196
347
|
export async function moveJsonDurableQueueEntryToFailed(params) {
|
|
197
348
|
assertSafeQueueEntryId(params.id);
|
|
198
|
-
await
|
|
349
|
+
const roots = await queueValidationRoots(params.queueDir, params.failedDir);
|
|
350
|
+
await assertJsonDurableQueueDir(params.queueDir, roots.queueRoot);
|
|
351
|
+
await ensureJsonDurableQueueDir(params.failedDir, roots.failedRoot);
|
|
199
352
|
await fs.promises.rename(path.join(params.queueDir, `${params.id}.json`), path.join(params.failedDir, `${params.id}.json`));
|
|
200
353
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-stringify.d.ts","sourceRoot":"","sources":["../src/json-stringify.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,EACd,QAAQ,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC/C,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAC3C,MAAM,CAMR"}
|
package/dist/json.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../src/json.ts"],"names":[],"mappings":"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"}
|
package/dist/json.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import fsSync from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { stringifyJsonDocument } from "./json-stringify.js";
|
|
4
5
|
import { readRegularFile, readRegularFileSync } from "./regular-file.js";
|
|
5
6
|
import { openRootFileSync } from "./root-file.js";
|
|
6
7
|
import { writeTextAtomic } from "./text-atomic.js";
|
|
@@ -104,7 +105,7 @@ export function tryReadJsonSync(pathname) {
|
|
|
104
105
|
export function writeJsonSync(pathname, data) {
|
|
105
106
|
const targetPath = pathname;
|
|
106
107
|
const tmpPath = `${targetPath}.${randomUUID()}.tmp`;
|
|
107
|
-
const payload = `${
|
|
108
|
+
const payload = `${stringifyJsonDocument(data, null, 2)}\n`;
|
|
108
109
|
fsSync.mkdirSync(path.dirname(targetPath), { recursive: true, mode: JSON_DIR_MODE });
|
|
109
110
|
try {
|
|
110
111
|
writeTempJsonFile(tmpPath, payload);
|
|
@@ -255,7 +256,7 @@ export function readJsonSync(filePath) {
|
|
|
255
256
|
}
|
|
256
257
|
}
|
|
257
258
|
export async function writeJson(filePath, value, options) {
|
|
258
|
-
const text =
|
|
259
|
+
const text = stringifyJsonDocument(value, null, 2);
|
|
259
260
|
await writeTextAtomic(filePath, text, {
|
|
260
261
|
mode: options?.mode,
|
|
261
262
|
dirMode: options?.dirMode,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAmGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"local-roots.d.ts","sourceRoot":"","sources":["../src/local-roots.ts"],"names":[],"mappings":"AAMA,OAAO,EAAQ,KAAK,cAAc,EAAE,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG;IAC9C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAAG,sBAAsB,GAAG;IAC1E,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG,sBAAsB,GAAG;IACnE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAmGF,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,oCAAoC,GAC5C,oBAAoB,GAAG,IAAI,CA6B7B;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA0CtC"}
|
package/dist/local-roots.js
CHANGED
|
@@ -139,7 +139,10 @@ export async function readLocalFileFromRoots(options) {
|
|
|
139
139
|
continue;
|
|
140
140
|
}
|
|
141
141
|
const relativePath = path.relative(scopedRoot.rootDir, requestedPath);
|
|
142
|
-
if (!relativePath ||
|
|
142
|
+
if (!relativePath ||
|
|
143
|
+
relativePath === ".." ||
|
|
144
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
145
|
+
path.isAbsolute(relativePath)) {
|
|
143
146
|
continue;
|
|
144
147
|
}
|
|
145
148
|
try {
|
package/dist/path.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function assertNoNulPathInput(filePath: string, message?: string)
|
|
|
6
6
|
export declare function isNotFoundPathError(value: unknown): boolean;
|
|
7
7
|
export declare function isSymlinkOpenError(value: unknown): boolean;
|
|
8
8
|
export declare function isPathInside(root: string, target: string): boolean;
|
|
9
|
+
export declare function isPathRelativeEscape(relativePath: string): boolean;
|
|
9
10
|
export declare function resolveSafeBaseDir(rootDir: string): string;
|
|
10
11
|
export declare function isWithinDir(rootDir: string, targetPath: string): boolean;
|
|
11
12
|
export declare function safeRealpathSync(targetPath: string, cache?: Map<string, string>): string | null;
|
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;AAUzB,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,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"}
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAUzB,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
|
@@ -55,6 +55,9 @@ export function isPathInside(root, target) {
|
|
|
55
55
|
const firstSegment = relative.split(path.posix.sep)[0];
|
|
56
56
|
return relative === "" || (firstSegment !== ".." && !path.isAbsolute(relative));
|
|
57
57
|
}
|
|
58
|
+
export function isPathRelativeEscape(relativePath) {
|
|
59
|
+
return relativePath === ".." || relativePath.startsWith(`..${path.sep}`) || path.isAbsolute(relativePath);
|
|
60
|
+
}
|
|
58
61
|
export function resolveSafeBaseDir(rootDir) {
|
|
59
62
|
const resolved = path.resolve(rootDir);
|
|
60
63
|
return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
|
|
@@ -120,7 +123,7 @@ export function splitSafeRelativePath(relativePath) {
|
|
|
120
123
|
export function resolveSafeRelativePath(rootDir, relativePath) {
|
|
121
124
|
const root = path.resolve(rootDir);
|
|
122
125
|
const target = path.resolve(root, ...splitSafeRelativePath(relativePath));
|
|
123
|
-
if (
|
|
126
|
+
if (!isPathInside(root, target)) {
|
|
124
127
|
throw new FsSafeError("outside-workspace", "relative path escapes root");
|
|
125
128
|
}
|
|
126
129
|
return target;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,cAAc,GAAG,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEjD,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,cAAc,EAAE,eAAe,EAAE,CAAC;IAClC,cAAc,EAAE,eAAe,EAAE,CAAC;IAClC,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,MAAM;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAEzE,MAAM,MAAM,yBAAyB,GAAG;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,uBAAuB,CAAC;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
|