@openclaw/fs-safe 0.11.0 → 0.12.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 +58 -0
- package/README.md +4 -2
- package/dist/advanced.d.ts +1 -1
- package/dist/advanced.d.ts.map +1 -1
- package/dist/advanced.js +1 -1
- package/dist/archive-entry.d.ts.map +1 -1
- package/dist/archive-entry.js +4 -3
- package/dist/archive-gzip-tail.d.ts.map +1 -1
- package/dist/archive-gzip-tail.js +13 -9
- package/dist/archive-read.d.ts.map +1 -1
- package/dist/archive-read.js +37 -33
- package/dist/archive-zip-names.d.ts.map +1 -1
- package/dist/archive-zip-names.js +17 -5
- package/dist/bounded-read.js +2 -2
- package/dist/device-path.d.ts.map +1 -1
- package/dist/device-path.js +5 -3
- package/dist/file-hash.d.ts.map +1 -1
- package/dist/file-hash.js +9 -2
- package/dist/file-store-prune.d.ts.map +1 -1
- package/dist/file-store-prune.js +9 -1
- package/dist/filename.d.ts +1 -0
- package/dist/filename.d.ts.map +1 -1
- package/dist/filename.js +30 -13
- package/dist/guarded-mkdir.d.ts +2 -0
- package/dist/guarded-mkdir.d.ts.map +1 -1
- package/dist/guarded-mkdir.js +50 -15
- package/dist/guest.d.ts.map +1 -1
- package/dist/guest.js +12 -4
- package/dist/install-path.d.ts +6 -0
- package/dist/install-path.d.ts.map +1 -1
- package/dist/install-path.js +13 -0
- package/dist/json-durable-queue-ownership.d.ts +2 -0
- package/dist/json-durable-queue-ownership.d.ts.map +1 -1
- package/dist/json-durable-queue-ownership.js +47 -3
- package/dist/json-durable-queue-read.d.ts +6 -0
- package/dist/json-durable-queue-read.d.ts.map +1 -0
- package/dist/json-durable-queue-read.js +59 -0
- package/dist/json-durable-queue.d.ts +1 -1
- package/dist/json-durable-queue.d.ts.map +1 -1
- package/dist/json-durable-queue.js +37 -80
- package/dist/native-binding.d.ts +7 -0
- package/dist/native-binding.d.ts.map +1 -1
- package/dist/path-scope-lexical.d.ts +14 -0
- package/dist/path-scope-lexical.d.ts.map +1 -0
- package/dist/path-scope-lexical.js +27 -0
- package/dist/path.d.ts.map +1 -1
- package/dist/path.js +20 -2
- package/dist/pinned-write.js +1 -0
- package/dist/root-boundary.d.ts +25 -0
- package/dist/root-boundary.d.ts.map +1 -0
- package/dist/root-boundary.js +177 -0
- package/dist/root-context.d.ts.map +1 -1
- package/dist/root-context.js +32 -5
- package/dist/root-directory-list.d.ts.map +1 -1
- package/dist/root-directory-list.js +10 -2
- package/dist/root-impl.d.ts.map +1 -1
- package/dist/root-impl.js +53 -34
- package/dist/root-move-preflight.d.ts +8 -0
- package/dist/root-move-preflight.d.ts.map +1 -0
- package/dist/root-move-preflight.js +16 -0
- package/dist/root-path-existing.d.ts +2 -0
- package/dist/root-path-existing.d.ts.map +1 -1
- package/dist/root-path-existing.js +10 -2
- package/dist/root-path.d.ts +2 -0
- package/dist/root-path.d.ts.map +1 -1
- package/dist/root-path.js +54 -26
- package/dist/root-paths.d.ts +2 -6
- package/dist/root-paths.d.ts.map +1 -1
- package/dist/root-paths.js +14 -26
- package/dist/root-walk.d.ts.map +1 -1
- package/dist/root-walk.js +2 -1
- package/dist/root-write-mode.d.ts +2 -0
- package/dist/root-write-mode.d.ts.map +1 -1
- package/dist/root-write-mode.js +20 -7
- package/dist/root-write-verification.d.ts.map +1 -1
- package/dist/root-write-verification.js +12 -3
- package/dist/safe-path-segment.d.ts.map +1 -1
- package/dist/safe-path-segment.js +3 -1
- package/dist/secure-file-windows.d.ts +10 -0
- package/dist/secure-file-windows.d.ts.map +1 -0
- package/dist/secure-file-windows.js +186 -0
- package/dist/secure-file.d.ts.map +1 -1
- package/dist/secure-file.js +6 -3
- package/dist/sibling-staged-file.d.ts +1 -0
- package/dist/sibling-staged-file.d.ts.map +1 -1
- package/dist/sibling-staged-file.js +7 -0
- package/dist/sibling-temp.d.ts.map +1 -1
- package/dist/sibling-temp.js +2 -1
- package/dist/sidecar-lock-acquire.js +1 -1
- package/dist/sidecar-lock.d.ts.map +1 -1
- package/dist/sidecar-lock.js +2 -3
- package/dist/temp-target.d.ts.map +1 -1
- package/dist/temp-target.js +3 -1
- package/dist/walk.d.ts.map +1 -1
- package/dist/walk.js +11 -10
- package/docs/advanced.md +3 -1
- package/docs/archive.md +6 -0
- package/docs/contributing.md +4 -1
- package/docs/copy.md +1 -1
- package/docs/durability.md +4 -2
- package/docs/file-store.md +6 -0
- package/docs/filename.md +9 -2
- package/docs/guest.md +5 -0
- package/docs/install-path.md +59 -13
- package/docs/install.md +4 -1
- package/docs/native-helper.md +4 -2
- package/docs/native.md +14 -2
- package/docs/path.md +1 -1
- package/docs/permissions.md +26 -1
- package/docs/public-api.md +5 -0
- package/docs/root.md +3 -1
- package/docs/secure-file.md +15 -14
- package/docs/security-model.md +1 -1
- package/docs/store.md +22 -1
- package/docs/temp.md +4 -0
- package/package.json +8 -8
package/dist/root-impl.js
CHANGED
|
@@ -69,15 +69,17 @@ import { runPinnedWriteHelper, runPinnedWriteWithRenamePolicy, } from "./pinned-
|
|
|
69
69
|
import { getNativeBinding } from "./native.js";
|
|
70
70
|
import { validatePinnedOperationPayload } from "./pinned-operation.js";
|
|
71
71
|
import { assertNoPathAliasEscape, PATH_ALIAS_POLICIES } from "./path-policy.js";
|
|
72
|
-
import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError,
|
|
72
|
+
import { assertNoNulPathInput, assertNoUnsafeDeviceReadPath, hasNodeErrorCode, isNotFoundPathError, isSymlinkOpenError, } from "./path.js";
|
|
73
73
|
import { readOpenedFileSafely } from "./read-opened-file.js";
|
|
74
74
|
import { cleanupPinnedFilePath } from "./replace-file-temp-owner.js";
|
|
75
75
|
import { resolveReadOpenFlags } from "./read-open-flags.js";
|
|
76
76
|
import { realpathSync } from "./realpath.js";
|
|
77
77
|
import { isNonRegularWriteOpenError, resolveNonblockingWriteFlag } from "./write-open-flags.js";
|
|
78
78
|
import { resolveRootPath } from "./root-path.js";
|
|
79
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
79
80
|
import { openRootDirectoryListing, listDirectoryPath, pathStatFromStats } from "./root-directory-list.js";
|
|
80
81
|
import { entriesInRoot } from "./root-entries.js";
|
|
82
|
+
import { assertMoveMutationAllowed } from "./root-move-preflight.js";
|
|
81
83
|
import { assertRootIdentityCurrent, assertValidRootDestinationPath, assertValidRootRelativePath, ensureTrailingSep, expandRelativePathWithHome, resolvePathInRoot, resolveRootContext, rootRelativeReadPath, } from "./root-context.js";
|
|
82
84
|
import { fileNotFoundError, hardlinkedPathNotAllowedError, isAlreadyExistsError, normalizePinnedPathError, normalizePinnedWriteError, outsideWorkspaceError, } from "./root-errors.js";
|
|
83
85
|
import { getFsSafeTestHooks } from "./test-hooks.js";
|
|
@@ -478,7 +480,7 @@ export function rootFromDirectoryGuard(guard, defaults = {}) {
|
|
|
478
480
|
}, defaults);
|
|
479
481
|
}
|
|
480
482
|
async function openFileInRoot(root, params) {
|
|
481
|
-
const {
|
|
483
|
+
const { resolved } = await resolvePathInRoot(root, params.relativePath, {
|
|
482
484
|
allowFinalSymlink: true,
|
|
483
485
|
rejectUnsafeDeviceReads: true,
|
|
484
486
|
...readSymlinkResolution(params.symlinks),
|
|
@@ -491,10 +493,16 @@ async function openFileInRoot(root, params) {
|
|
|
491
493
|
await opened.handle.close().catch(() => { });
|
|
492
494
|
throw hardlinkedPathNotAllowedError();
|
|
493
495
|
}
|
|
494
|
-
|
|
496
|
+
const admittedRealPath = admitPathInsideRoot({
|
|
497
|
+
rootPath: root.rootReal,
|
|
498
|
+
candidatePath: opened.realPath,
|
|
499
|
+
rootIdentity: root.rootIdentity,
|
|
500
|
+
});
|
|
501
|
+
if (!admittedRealPath) {
|
|
495
502
|
await opened.handle.close().catch(() => { });
|
|
496
503
|
throw outsideWorkspaceError();
|
|
497
504
|
}
|
|
505
|
+
opened.realPath = admittedRealPath.path;
|
|
498
506
|
return opened;
|
|
499
507
|
}
|
|
500
508
|
async function readFileInRoot(root, params) {
|
|
@@ -538,9 +546,10 @@ function buildAtomicWriteTempPath(targetPath) {
|
|
|
538
546
|
function rootWriteQueueKey(root, relativePath) {
|
|
539
547
|
return `${root.rootReal}\0${relativePath}`;
|
|
540
548
|
}
|
|
541
|
-
async function prepareRootWriteTarget(
|
|
549
|
+
async function prepareRootWriteTarget(root, targetPath, assertBeforeMutation) {
|
|
542
550
|
const parentPath = await mkdirPathComponentsWithGuards({
|
|
543
|
-
rootReal,
|
|
551
|
+
rootReal: root.rootReal,
|
|
552
|
+
rootIdentity: root.rootIdentity,
|
|
544
553
|
targetPath: path.dirname(targetPath),
|
|
545
554
|
assertBeforeMutation,
|
|
546
555
|
});
|
|
@@ -573,21 +582,26 @@ async function resolveGuardedWritePathInRoot(root, params) {
|
|
|
573
582
|
return resolvedPath;
|
|
574
583
|
}
|
|
575
584
|
async function openWritableFileInRoot(root, params) {
|
|
576
|
-
const {
|
|
585
|
+
const { resolved } = await resolveGuardedWritePathInRoot(root, {
|
|
577
586
|
relativePath: params.relativePath,
|
|
578
587
|
denyMutations: params.denyMutations,
|
|
579
588
|
mutationSymlinks: params.mutationSymlinks,
|
|
580
589
|
});
|
|
581
590
|
let ioPath = params.mkdir === false
|
|
582
591
|
? resolved
|
|
583
|
-
: await prepareRootWriteTarget(
|
|
592
|
+
: await prepareRootWriteTarget(root, resolved, params.assertBeforeMutation);
|
|
584
593
|
try {
|
|
585
594
|
assertFinalSymlinkRejected(ioPath, params.mutationSymlinks !== undefined);
|
|
586
595
|
const resolvedRealPath = params.mutationSymlinks === undefined ? realpathSync.native(ioPath) : ioPath;
|
|
587
|
-
|
|
596
|
+
const admittedRealPath = admitPathInsideRoot({
|
|
597
|
+
rootPath: root.rootReal,
|
|
598
|
+
candidatePath: resolvedRealPath,
|
|
599
|
+
rootIdentity: root.rootIdentity,
|
|
600
|
+
});
|
|
601
|
+
if (!admittedRealPath) {
|
|
588
602
|
throw outsideWorkspaceError();
|
|
589
603
|
}
|
|
590
|
-
ioPath =
|
|
604
|
+
ioPath = admittedRealPath.path;
|
|
591
605
|
}
|
|
592
606
|
catch (err) {
|
|
593
607
|
if (err instanceof FsSafeError) {
|
|
@@ -657,8 +671,7 @@ async function openWritableFileInRoot(root, params) {
|
|
|
657
671
|
throw err;
|
|
658
672
|
}
|
|
659
673
|
}
|
|
660
|
-
|
|
661
|
-
realPathForCleanup = realPath;
|
|
674
|
+
let realPath = await resolveOpenedFileRealPathForHandle(handle, ioPath);
|
|
662
675
|
const realStat = fsSync.statSync(realPath);
|
|
663
676
|
if (!sameFileIdentity(stat, realStat)) {
|
|
664
677
|
throw new FsSafeError("path-mismatch", "path mismatch");
|
|
@@ -666,9 +679,16 @@ async function openWritableFileInRoot(root, params) {
|
|
|
666
679
|
if (realStat.nlink > 1) {
|
|
667
680
|
throw hardlinkedPathNotAllowedError();
|
|
668
681
|
}
|
|
669
|
-
|
|
682
|
+
const admittedRealPath = admitPathInsideRoot({
|
|
683
|
+
rootPath: root.rootReal,
|
|
684
|
+
candidatePath: realPath,
|
|
685
|
+
rootIdentity: root.rootIdentity,
|
|
686
|
+
});
|
|
687
|
+
if (!admittedRealPath) {
|
|
670
688
|
throw outsideWorkspaceError();
|
|
671
689
|
}
|
|
690
|
+
realPath = admittedRealPath.path;
|
|
691
|
+
realPathForCleanup = realPath;
|
|
672
692
|
// Truncate only after boundary and identity checks complete. This avoids
|
|
673
693
|
// irreversible side effects if a symlink target changes before validation.
|
|
674
694
|
if (params.append !== true && params.truncateExisting !== false && !createdForWrite) {
|
|
@@ -785,7 +805,7 @@ async function mkdirPathInRoot(root, params) {
|
|
|
785
805
|
validatePinnedOperationPayload({ relativePath: params.relativePath });
|
|
786
806
|
const resolved = await resolvePinnedPathInRoot(root, params);
|
|
787
807
|
try {
|
|
788
|
-
await mkdirPathFallback(resolved, params.assertBeforeMutation, params.mutationSymlinks !== undefined);
|
|
808
|
+
await mkdirPathFallback(root, resolved, params.assertBeforeMutation, params.mutationSymlinks !== undefined);
|
|
789
809
|
}
|
|
790
810
|
catch (error) {
|
|
791
811
|
throw normalizePinnedPathError(error);
|
|
@@ -948,8 +968,8 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
|
|
|
948
968
|
denyMutations,
|
|
949
969
|
mutationSymlinks,
|
|
950
970
|
});
|
|
951
|
-
// resolvePathInRoot already
|
|
952
|
-
// is rejected upstream.
|
|
971
|
+
// resolvePathInRoot already admits and rebases the path at the exact Root
|
|
972
|
+
// boundary, so any actual escape is rejected upstream.
|
|
953
973
|
const relativeResolved = path.relative(rootReal, resolved);
|
|
954
974
|
if (path.isAbsolute(relativeResolved)) {
|
|
955
975
|
throw outsideWorkspaceError();
|
|
@@ -978,7 +998,12 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode,
|
|
|
978
998
|
}
|
|
979
999
|
}
|
|
980
1000
|
const mode = overwrite
|
|
981
|
-
? await inheritWriteTargetMode({
|
|
1001
|
+
? await inheritWriteTargetMode({
|
|
1002
|
+
targetPath: resolved,
|
|
1003
|
+
rootWithSep,
|
|
1004
|
+
rootIdentity: root.rootIdentity,
|
|
1005
|
+
requestedMode,
|
|
1006
|
+
})
|
|
982
1007
|
: requestedMode ?? 0o600;
|
|
983
1008
|
return {
|
|
984
1009
|
rootReal,
|
|
@@ -1017,9 +1042,15 @@ async function resolvePinnedOperationPathInRoot(root, params) {
|
|
|
1017
1042
|
throw outsideWorkspaceError();
|
|
1018
1043
|
}
|
|
1019
1044
|
const relativePosix = relativeResolved.split(path.sep).join(path.posix.sep);
|
|
1020
|
-
|
|
1045
|
+
const admittedCanonicalPath = admitPathInsideRoot({
|
|
1046
|
+
rootPath: resolved.rootReal,
|
|
1047
|
+
candidatePath: resolved.canonicalPath,
|
|
1048
|
+
rootIdentity: root.rootIdentity,
|
|
1049
|
+
});
|
|
1050
|
+
if (!admittedCanonicalPath) {
|
|
1021
1051
|
throw outsideWorkspaceError();
|
|
1022
1052
|
}
|
|
1053
|
+
resolved.canonicalPath = admittedCanonicalPath.path;
|
|
1023
1054
|
await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations, {
|
|
1024
1055
|
protectAncestors: params.protectDenyMutationAncestors,
|
|
1025
1056
|
});
|
|
@@ -1037,6 +1068,7 @@ async function resolvePinnedRootPathInRoot(root, params) {
|
|
|
1037
1068
|
: `${ensureTrailingSep(rootReal)}${expandedPath}`,
|
|
1038
1069
|
rootPath: rootReal,
|
|
1039
1070
|
rootCanonicalPath: rootReal,
|
|
1071
|
+
rootIdentity: root.rootIdentity,
|
|
1040
1072
|
boundaryLabel: "root",
|
|
1041
1073
|
policy: params.policy,
|
|
1042
1074
|
...mutationSymlinkResolution(params.mutationSymlinks),
|
|
@@ -1054,9 +1086,10 @@ async function resolvePinnedRootPathInRoot(root, params) {
|
|
|
1054
1086
|
canonicalPath: resolved.canonicalPath,
|
|
1055
1087
|
};
|
|
1056
1088
|
}
|
|
1057
|
-
async function mkdirPathFallback(resolved, assertBeforeMutation, rejectSymlinks = false) {
|
|
1089
|
+
async function mkdirPathFallback(root, resolved, assertBeforeMutation, rejectSymlinks = false) {
|
|
1058
1090
|
await mkdirPathComponentsWithGuards({
|
|
1059
1091
|
rootReal: resolved.rootReal, targetPath: resolved.resolved, assertBeforeMutation,
|
|
1092
|
+
rootIdentity: root.rootIdentity,
|
|
1060
1093
|
rejectSymlinks,
|
|
1061
1094
|
beforeComponent: async (componentPath) => await getFsSafeTestHooks()?.beforeRootFallbackMutation?.("mkdir", componentPath),
|
|
1062
1095
|
});
|
|
@@ -1079,20 +1112,6 @@ async function listPathFallback(root, relativePath, withFileTypes) {
|
|
|
1079
1112
|
const resolved = await resolvePinnedPathInRoot(root, { relativePath, allowRoot: true });
|
|
1080
1113
|
return await listDirectoryPath(root, resolved.resolved, withFileTypes);
|
|
1081
1114
|
}
|
|
1082
|
-
async function assertMoveMutationAllowed(root, params) {
|
|
1083
|
-
// Keep this preflight separate from the pinned resolutions in movePathFallback:
|
|
1084
|
-
// mutation denials must take precedence over source alias or identity failures.
|
|
1085
|
-
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
1086
|
-
aliasErrorCode: "path-alias",
|
|
1087
|
-
allowFinalSymlink: true,
|
|
1088
|
-
});
|
|
1089
|
-
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
1090
|
-
const target = await resolvePathInRoot(root, params.toRelative, {
|
|
1091
|
-
aliasErrorCode: "path-alias",
|
|
1092
|
-
allowFinalSymlink: true,
|
|
1093
|
-
});
|
|
1094
|
-
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
1095
|
-
}
|
|
1096
1115
|
async function movePathFallback(root, params) {
|
|
1097
1116
|
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
1098
1117
|
aliasErrorCode: "path-alias",
|
|
@@ -1297,14 +1316,14 @@ async function writeFileFallback(root, params) {
|
|
|
1297
1316
|
}
|
|
1298
1317
|
}
|
|
1299
1318
|
async function writeMissingFileFallback(root, params) {
|
|
1300
|
-
const {
|
|
1319
|
+
const { resolved } = await resolveGuardedWritePathInRoot(root, {
|
|
1301
1320
|
relativePath: params.relativePath,
|
|
1302
1321
|
denyMutations: params.denyMutations,
|
|
1303
1322
|
mutationSymlinks: params.mutationSymlinks,
|
|
1304
1323
|
});
|
|
1305
1324
|
const targetPath = params.mkdir === false
|
|
1306
1325
|
? resolved
|
|
1307
|
-
: await prepareRootWriteTarget(
|
|
1326
|
+
: await prepareRootWriteTarget(root, resolved, params.assertBeforeMutation);
|
|
1308
1327
|
const parentGuard = await createAsyncDirectoryGuard(path.dirname(targetPath), { bigint: true });
|
|
1309
1328
|
let created = false;
|
|
1310
1329
|
let createdIdentity;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type DenyMutationPolicy } from "./deny-mutations.js";
|
|
2
|
+
import { type RootContext } from "./root-context.js";
|
|
3
|
+
export declare function assertMoveMutationAllowed(root: RootContext, params: {
|
|
4
|
+
fromRelative: string;
|
|
5
|
+
toRelative: string;
|
|
6
|
+
denyMutations?: DenyMutationPolicy;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
//# sourceMappingURL=root-move-preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"root-move-preflight.d.ts","sourceRoot":"","sources":["../src/root-move-preflight.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAExE,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,WAAW,EACjB,MAAM,EAAE;IACN,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC,GACA,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { assertMutationNotDenied, } from "./deny-mutations.js";
|
|
2
|
+
import { resolvePathInRoot } from "./root-context.js";
|
|
3
|
+
export async function assertMoveMutationAllowed(root, params) {
|
|
4
|
+
// Keep this preflight separate from the pinned resolutions in movePathFallback:
|
|
5
|
+
// mutation denials must take precedence over source alias or identity failures.
|
|
6
|
+
const source = await resolvePathInRoot(root, params.fromRelative, {
|
|
7
|
+
aliasErrorCode: "path-alias",
|
|
8
|
+
allowFinalSymlink: true,
|
|
9
|
+
});
|
|
10
|
+
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
11
|
+
const target = await resolvePathInRoot(root, params.toRelative, {
|
|
12
|
+
aliasErrorCode: "path-alias",
|
|
13
|
+
allowFinalSymlink: true,
|
|
14
|
+
});
|
|
15
|
+
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
16
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { type RootBoundaryIdentity } from "./root-boundary.js";
|
|
1
2
|
export declare function absolutePathWithRawSegments(candidate: string): string;
|
|
2
3
|
export declare function rawPathRelativeToCanonicalRoot(candidate: string, rootCanonicalPath: string, options?: {
|
|
3
4
|
rejectSymlinks?: boolean;
|
|
4
5
|
rejectFinalSymlink?: boolean;
|
|
6
|
+
rootIdentity?: RootBoundaryIdentity;
|
|
5
7
|
}): string | undefined;
|
|
6
8
|
export declare function resolvePathViaExistingAncestor(targetPath: string): Promise<string>;
|
|
7
9
|
export declare function resolvePathViaExistingAncestorSync(targetPath: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-path-existing.d.ts","sourceRoot":"","sources":["../src/root-path-existing.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-path-existing.d.ts","sourceRoot":"","sources":["../src/root-path-existing.ts"],"names":[],"mappings":"AAMA,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAEpF,wBAAgB,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKrE;AAED,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE;IACP,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,oBAAoB,CAAC;CAChC,GACL,MAAM,GAAG,SAAS,CA8CpB;AAkBD,wBAAsB,8BAA8B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BxF;AAED,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CA0B7E"}
|
|
@@ -4,6 +4,7 @@ import { FsSafeError } from "./errors.js";
|
|
|
4
4
|
import { formatErrorDetail } from "./error-detail.js";
|
|
5
5
|
import { isNotFoundPathError, isPathInside } from "./path.js";
|
|
6
6
|
import { realpathSync } from "./realpath.js";
|
|
7
|
+
import { admitPathInsideRoot } from "./root-boundary.js";
|
|
7
8
|
export function absolutePathWithRawSegments(candidate) {
|
|
8
9
|
if (path.isAbsolute(candidate))
|
|
9
10
|
return candidate;
|
|
@@ -19,6 +20,7 @@ export function rawPathRelativeToCanonicalRoot(candidate, rootCanonicalPath, opt
|
|
|
19
20
|
const finalComponentIndex = segments.findLastIndex(segment => segment !== "" && segment !== ".");
|
|
20
21
|
let prefix = filesystemRoot;
|
|
21
22
|
let traversedSymlink = false;
|
|
23
|
+
const identityCache = process.platform === "win32" ? new Map() : undefined;
|
|
22
24
|
for (let index = 0; index < segments.length; index += 1) {
|
|
23
25
|
prefix += `${prefix.endsWith(path.sep) ? "" : path.sep}${segments[index]}`;
|
|
24
26
|
let canonical;
|
|
@@ -47,12 +49,18 @@ export function rawPathRelativeToCanonicalRoot(candidate, rootCanonicalPath, opt
|
|
|
47
49
|
return undefined;
|
|
48
50
|
continue;
|
|
49
51
|
}
|
|
50
|
-
|
|
52
|
+
const admitted = admitPathInsideRoot({
|
|
53
|
+
rootPath: rootCanonicalPath,
|
|
54
|
+
candidatePath: canonical,
|
|
55
|
+
rootIdentity: options.rootIdentity,
|
|
56
|
+
identityCache,
|
|
57
|
+
});
|
|
58
|
+
if (!admitted)
|
|
51
59
|
continue;
|
|
52
60
|
if (options.rejectSymlinks && traversedSymlink) {
|
|
53
61
|
throw new FsSafeError("symlink", "symlink path component not allowed");
|
|
54
62
|
}
|
|
55
|
-
return [
|
|
63
|
+
return [admitted.relativePath, ...segments.slice(index + 1)]
|
|
56
64
|
.filter(Boolean).join(path.sep);
|
|
57
65
|
}
|
|
58
66
|
return undefined;
|
package/dist/root-path.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RootBoundaryIdentity } from "./root-boundary.js";
|
|
1
2
|
export { resolvePathViaExistingAncestorSync } from "./root-path-existing.js";
|
|
2
3
|
type RootPathIntent = "read" | "write" | "create" | "delete" | "stat";
|
|
3
4
|
export type RootPathAliasPolicy = {
|
|
@@ -25,6 +26,7 @@ type ResolveRootPathParams = {
|
|
|
25
26
|
rejectUnresolvedSymlinks?: boolean;
|
|
26
27
|
skipLexicalRootCheck?: boolean;
|
|
27
28
|
rootCanonicalPath?: string;
|
|
29
|
+
rootIdentity?: RootBoundaryIdentity;
|
|
28
30
|
};
|
|
29
31
|
type ResolvedRootPathKind = "missing" | "file" | "directory" | "symlink" | "other";
|
|
30
32
|
export type ResolvedRootPath = {
|
package/dist/root-path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-path.d.ts","sourceRoot":"","sources":["../src/root-path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"root-path.d.ts","sourceRoot":"","sources":["../src/root-path.ts"],"names":[],"mappings":"AAeA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EAAE,kCAAkC,EAAE,MAAM,yBAAyB,CAAC;AAE7E,KAAK,cAAc,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;AAEtE,MAAM,MAAM,mBAAmB,GAAG;IAChC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,wBAAwB;aACnC,MAAM;;;;aAIN,YAAY;;;;CAIJ,CAAC;AAEX,KAAK,qBAAqB,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAEF,KAAK,oBAAoB,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,oBAAoB,CAAC;CAC5B,CAAC;AAEF,wBAAsB,eAAe,CACnC,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AAcD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,qBAAqB,GAAG,gBAAgB,CAMnF"}
|
package/dist/root-path.js
CHANGED
|
@@ -2,8 +2,9 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { formatErrorDetail, shortPath } from "./error-detail.js";
|
|
4
4
|
import { FsSafeError } from "./errors.js";
|
|
5
|
-
import { assertNoNulPathInput, isNotFoundPathError,
|
|
5
|
+
import { assertNoNulPathInput, isNotFoundPathError, isPathRelativeEscape, } from "./path.js";
|
|
6
6
|
import { absolutePathWithRawSegments, rawPathRelativeToCanonicalRoot, resolvePathViaExistingAncestor, resolvePathViaExistingAncestorSync, } from "./root-path-existing.js";
|
|
7
|
+
import { admitPathInsideRoot, } from "./root-boundary.js";
|
|
7
8
|
import { resolveSymlinkHopPath, resolveSymlinkHopPathSync } from "./root-path-symlink.js";
|
|
8
9
|
import { assertNoDriveRelativePathSegments } from "./safe-path-segment.js";
|
|
9
10
|
export { resolvePathViaExistingAncestorSync } from "./root-path-existing.js";
|
|
@@ -29,11 +30,10 @@ async function resolveRootPathInternal(params) {
|
|
|
29
30
|
assertValidRootPathInputs(params);
|
|
30
31
|
params = { ...params, absolutePath: absolutePathWithRawSegments(params.absolutePath) };
|
|
31
32
|
const rootPath = path.resolve(params.rootPath);
|
|
32
|
-
const absolutePath = path.resolve(params.absolutePath);
|
|
33
33
|
const rootCanonicalPath = params.rootCanonicalPath
|
|
34
34
|
? path.resolve(params.rootCanonicalPath)
|
|
35
35
|
: await resolvePathViaExistingAncestor(rootPath);
|
|
36
|
-
return resolveRootPathLexicalAsync(prepareRootTraversal(params, rootPath, rootCanonicalPath
|
|
36
|
+
return resolveRootPathLexicalAsync(prepareRootTraversal(params, rootPath, rootCanonicalPath));
|
|
37
37
|
}
|
|
38
38
|
export function resolveRootPathSync(params) {
|
|
39
39
|
try {
|
|
@@ -47,25 +47,38 @@ function resolveRootPathSyncInternal(params) {
|
|
|
47
47
|
assertValidRootPathInputs(params);
|
|
48
48
|
params = { ...params, absolutePath: absolutePathWithRawSegments(params.absolutePath) };
|
|
49
49
|
const rootPath = path.resolve(params.rootPath);
|
|
50
|
-
const absolutePath = path.resolve(params.absolutePath);
|
|
51
50
|
const rootCanonicalPath = params.rootCanonicalPath
|
|
52
51
|
? path.resolve(params.rootCanonicalPath)
|
|
53
52
|
: resolvePathViaExistingAncestorSync(rootPath);
|
|
54
|
-
return resolveRootPathLexicalSync(prepareRootTraversal(params, rootPath, rootCanonicalPath
|
|
53
|
+
return resolveRootPathLexicalSync(prepareRootTraversal(params, rootPath, rootCanonicalPath));
|
|
55
54
|
}
|
|
56
|
-
function prepareRootTraversal(params, rootPath, rootCanonicalPath
|
|
55
|
+
function prepareRootTraversal(params, rootPath, rootCanonicalPath) {
|
|
56
|
+
const callerAbsolutePath = path.resolve(params.absolutePath);
|
|
57
57
|
let raw = params.absolutePath;
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
let trustedAbsolutePath = false;
|
|
59
|
+
const direct = admitRawPathInsideRoot(rootPath, raw, params.rootIdentity);
|
|
60
|
+
if (direct) {
|
|
61
|
+
raw = direct.path;
|
|
62
|
+
trustedAbsolutePath = direct.admission === "identity";
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const canonical = admitRawPathInsideRoot(rootCanonicalPath, raw, params.rootIdentity);
|
|
66
|
+
const relative = canonical?.relativePath
|
|
60
67
|
?? rawPathRelativeToCanonicalRoot(raw, rootCanonicalPath, params);
|
|
61
68
|
if (relative === undefined) {
|
|
62
69
|
throw pathEscapeError({ rootPath, absolutePath: raw, boundaryLabel: params.boundaryLabel });
|
|
63
70
|
}
|
|
64
|
-
|
|
71
|
+
trustedAbsolutePath = canonical?.admission === "identity";
|
|
72
|
+
raw = relative === "" ? rootPath : `${rootPath}${path.sep}${relative}`;
|
|
65
73
|
}
|
|
66
74
|
return {
|
|
67
75
|
params: raw === params.absolutePath ? params : { ...params, absolutePath: raw },
|
|
68
|
-
rootPath,
|
|
76
|
+
rootPath,
|
|
77
|
+
rootCanonicalPath,
|
|
78
|
+
// Preserve the caller-spelling receipt for ordinary aliases. Only a
|
|
79
|
+
// Windows case-fold admission must return the identity-gated Root spelling,
|
|
80
|
+
// because downstream I/O must not reuse the ambiguous caller prefix.
|
|
81
|
+
absolutePath: trustedAbsolutePath ? path.resolve(raw) : callerAbsolutePath,
|
|
69
82
|
};
|
|
70
83
|
}
|
|
71
84
|
function sanitizeRootPathError(error) {
|
|
@@ -93,7 +106,7 @@ function assertNoEmbeddedDriveRelativeSegment(filePath, label) {
|
|
|
93
106
|
}
|
|
94
107
|
function createLexicalTraversalState(params) {
|
|
95
108
|
const rawAbsolutePath = params.params.absolutePath;
|
|
96
|
-
const relative =
|
|
109
|
+
const relative = admitRawPathInsideRoot(params.rootPath, rawAbsolutePath, params.params.rootIdentity)?.relativePath;
|
|
97
110
|
if (relative === undefined)
|
|
98
111
|
throw new Error("Path traversal must begin at the root");
|
|
99
112
|
const segments = splitTraversalSegments(relative);
|
|
@@ -124,7 +137,7 @@ function splitTraversalSegments(value) {
|
|
|
124
137
|
segments.push(".");
|
|
125
138
|
return segments;
|
|
126
139
|
}
|
|
127
|
-
function
|
|
140
|
+
function admitRawPathInsideRoot(rootPath, candidatePath, rootIdentity) {
|
|
128
141
|
if (!path.isAbsolute(candidatePath)) {
|
|
129
142
|
return undefined;
|
|
130
143
|
}
|
|
@@ -132,30 +145,36 @@ function rawPathRelativeToRoot(rootPath, candidatePath) {
|
|
|
132
145
|
const candidate = process.platform === "win32"
|
|
133
146
|
? candidatePath.replaceAll("/", path.sep)
|
|
134
147
|
: candidatePath;
|
|
148
|
+
if (process.platform === "win32") {
|
|
149
|
+
return admitPathInsideRoot({ rootPath: root, candidatePath: candidate, rootIdentity });
|
|
150
|
+
}
|
|
135
151
|
if (candidate === root) {
|
|
136
|
-
return "";
|
|
152
|
+
return { admission: "exact", path: candidate, relativePath: "" };
|
|
137
153
|
}
|
|
138
154
|
const rootWithSep = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
|
|
139
155
|
const candidatePrefix = candidate.slice(0, rootWithSep.length);
|
|
140
|
-
|
|
141
|
-
?
|
|
142
|
-
|
|
143
|
-
|
|
156
|
+
return candidatePrefix === rootWithSep
|
|
157
|
+
? {
|
|
158
|
+
admission: "exact",
|
|
159
|
+
path: candidate,
|
|
160
|
+
relativePath: candidate.slice(rootWithSep.length),
|
|
161
|
+
}
|
|
162
|
+
: undefined;
|
|
144
163
|
}
|
|
145
164
|
function assertLexicalCursorInsideBoundary(context, candidatePath) {
|
|
146
|
-
assertInsideBoundary({
|
|
165
|
+
return assertInsideBoundary({
|
|
147
166
|
boundaryLabel: context.resolveParams.boundaryLabel,
|
|
148
167
|
rootCanonicalPath: context.rootCanonicalPath,
|
|
168
|
+
rootIdentity: context.resolveParams.rootIdentity,
|
|
149
169
|
candidatePath,
|
|
150
170
|
absolutePath: context.absolutePath,
|
|
151
171
|
});
|
|
152
172
|
}
|
|
153
173
|
function advanceCanonicalCursorForSegment(context, segment) {
|
|
154
|
-
context.state.canonicalCursor = path.resolve(context.state.canonicalCursor, segment);
|
|
155
|
-
assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
|
|
174
|
+
context.state.canonicalCursor = assertLexicalCursorInsideBoundary(context, path.resolve(context.state.canonicalCursor, segment));
|
|
156
175
|
}
|
|
157
176
|
function finalizeLexicalResolution(context, kind) {
|
|
158
|
-
assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
|
|
177
|
+
context.state.canonicalCursor = assertLexicalCursorInsideBoundary(context, context.state.canonicalCursor);
|
|
159
178
|
return buildResolvedRootPath({
|
|
160
179
|
absolutePath: context.absolutePath,
|
|
161
180
|
canonicalPath: context.state.canonicalCursor,
|
|
@@ -181,15 +200,19 @@ function lexicalStatDisposition(params) {
|
|
|
181
200
|
return params.allowFinalSymlink && params.isLast ? "break" : "resolve-link";
|
|
182
201
|
}
|
|
183
202
|
function applyResolvedSymlinkHop(context, linkCanonical) {
|
|
184
|
-
|
|
203
|
+
let admitted;
|
|
204
|
+
try {
|
|
205
|
+
admitted = assertLexicalCursorInsideBoundary(context, linkCanonical);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
185
208
|
throw symlinkEscapeError({
|
|
186
209
|
boundaryLabel: context.resolveParams.boundaryLabel,
|
|
187
210
|
rootCanonicalPath: context.rootCanonicalPath,
|
|
188
211
|
symlinkPath: context.state.lexicalCursor,
|
|
189
212
|
});
|
|
190
213
|
}
|
|
191
|
-
context.state.canonicalCursor =
|
|
192
|
-
context.state.lexicalCursor =
|
|
214
|
+
context.state.canonicalCursor = admitted;
|
|
215
|
+
context.state.lexicalCursor = admitted;
|
|
193
216
|
}
|
|
194
217
|
function applyParentTraversalStep(context) {
|
|
195
218
|
context.state.lexicalCursor = path.resolve(context.state.lexicalCursor, "..");
|
|
@@ -380,8 +403,13 @@ function relativeInsideRoot(rootPath, targetPath) {
|
|
|
380
403
|
return relative;
|
|
381
404
|
}
|
|
382
405
|
function assertInsideBoundary(params) {
|
|
383
|
-
|
|
384
|
-
|
|
406
|
+
const admitted = admitPathInsideRoot({
|
|
407
|
+
rootPath: params.rootCanonicalPath,
|
|
408
|
+
candidatePath: params.candidatePath,
|
|
409
|
+
rootIdentity: params.rootIdentity,
|
|
410
|
+
});
|
|
411
|
+
if (admitted) {
|
|
412
|
+
return admitted.path;
|
|
385
413
|
}
|
|
386
414
|
throw new Error(`Path resolves outside ${params.boundaryLabel} (${shortPath(params.rootCanonicalPath)}): ${shortPath(params.absolutePath)}`);
|
|
387
415
|
}
|
package/dist/root-paths.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FsSafeError } from "./errors.js";
|
|
2
|
+
import { type ResolvePathWithinRootParams } from "./path-scope-lexical.js";
|
|
2
3
|
type InvalidPathResult = {
|
|
3
4
|
ok: false;
|
|
4
5
|
error: string;
|
|
@@ -50,12 +51,7 @@ export type PathScope = {
|
|
|
50
51
|
mode?: number;
|
|
51
52
|
}): Promise<DirectoryResult>;
|
|
52
53
|
};
|
|
53
|
-
export declare function resolvePathWithinRoot(params: {
|
|
54
|
-
rootDir: string;
|
|
55
|
-
requestedPath: string;
|
|
56
|
-
scopeLabel: string;
|
|
57
|
-
defaultFileName?: string;
|
|
58
|
-
}): {
|
|
54
|
+
export declare function resolvePathWithinRoot(params: ResolvePathWithinRootParams): {
|
|
59
55
|
ok: true;
|
|
60
56
|
path: string;
|
|
61
57
|
} | {
|
package/dist/root-paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"root-paths.d.ts","sourceRoot":"","sources":["../src/root-paths.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAW1C,OAAO,EAAmC,KAAK,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAE5G,KAAK,iBAAiB,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AACtD,KAAK,eAAe,GAChB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAC3D,KAAK,4BAA4B,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AACF,KAAK,4BAA4B,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,iBAAiB,CAAC;AACtF,MAAM,MAAM,uBAAuB,GAAG;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AACF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AACF,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CACL,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,uBAAuB,GAChC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,4BAA4B,CAAC;IACnE,QAAQ,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC1E,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACvE,QAAQ,CACN,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,SAAS,CACP,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,uBAAuB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GACpD,OAAO,CAAC,eAAe,CAAC,CAAC;CAC7B,CAAC;AAuDF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,2BAA2B,GAClC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAE3D;AAED,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAiCrE;AAuDD,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,eAAe,CAAC,CA2E3B;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,4BAA4B,GACnC,4BAA4B,CAwB9B;AAED,wBAAsB,8BAA8B,CAClD,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,4BAA4B,CAAC,CAEvC;AAED,wBAAsB,oCAAoC,CACxD,MAAM,EAAE,4BAA4B,GACnC,OAAO,CAAC,4BAA4B,CAAC,CAEvC;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAwC/E"}
|
package/dist/root-paths.js
CHANGED
|
@@ -6,19 +6,13 @@ import { FsSafeError } from "./errors.js";
|
|
|
6
6
|
import { assertNoNulPathInput, hasNodeErrorCode, isNodeError, isNotFoundPathError, isPathInside, isPathRelativeEscape, } from "./path.js";
|
|
7
7
|
import { root as openRoot } from "./root.js";
|
|
8
8
|
import { realpathSync } from "./realpath.js";
|
|
9
|
+
import { resolvePathWithinNormalizedRoot } from "./path-scope-lexical.js";
|
|
9
10
|
function invalidPath(scopeLabel) {
|
|
10
11
|
return {
|
|
11
12
|
ok: false,
|
|
12
13
|
error: `Invalid path: must stay within ${scopeLabel}`,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
|
-
function pathStaysWithinRoot(rootDir, candidatePath) {
|
|
16
|
-
if (process.platform !== "win32") {
|
|
17
|
-
return candidatePath !== rootDir && isPathInside(rootDir, candidatePath);
|
|
18
|
-
}
|
|
19
|
-
const relative = path.relative(rootDir, candidatePath);
|
|
20
|
-
return Boolean(relative) && !isPathRelativeEscape(relative);
|
|
21
|
-
}
|
|
22
16
|
async function resolveRealPathIfExists(targetPath) {
|
|
23
17
|
try {
|
|
24
18
|
return realpathSync.native(targetPath);
|
|
@@ -62,23 +56,7 @@ async function validateCanonicalPathWithinRoot(params) {
|
|
|
62
56
|
}
|
|
63
57
|
}
|
|
64
58
|
export function resolvePathWithinRoot(params) {
|
|
65
|
-
|
|
66
|
-
const raw = params.requestedPath.trim();
|
|
67
|
-
if (!raw) {
|
|
68
|
-
if (!params.defaultFileName) {
|
|
69
|
-
return { ok: false, error: "path is required" };
|
|
70
|
-
}
|
|
71
|
-
const defaultPath = path.resolve(root, params.defaultFileName);
|
|
72
|
-
if (!pathStaysWithinRoot(root, defaultPath)) {
|
|
73
|
-
return { ok: false, error: `Invalid path: must stay within ${params.scopeLabel}` };
|
|
74
|
-
}
|
|
75
|
-
return { ok: true, path: defaultPath };
|
|
76
|
-
}
|
|
77
|
-
const resolved = path.resolve(root, raw);
|
|
78
|
-
if (!pathStaysWithinRoot(root, resolved)) {
|
|
79
|
-
return { ok: false, error: `Invalid path: must stay within ${params.scopeLabel}` };
|
|
80
|
-
}
|
|
81
|
-
return { ok: true, path: resolved };
|
|
59
|
+
return resolvePathWithinNormalizedRoot(params, path.resolve(params.rootDir));
|
|
82
60
|
}
|
|
83
61
|
export async function resolveWritablePathWithinRoot(params) {
|
|
84
62
|
const lexical = resolvePathWithinRoot(params);
|
|
@@ -233,12 +211,22 @@ export async function ensureDirectoryWithinRoot(params) {
|
|
|
233
211
|
}
|
|
234
212
|
export function resolvePathsWithinRoot(params) {
|
|
235
213
|
const resolvedPaths = [];
|
|
214
|
+
let previousRootDir;
|
|
215
|
+
let previousResolvedRoot = "";
|
|
236
216
|
for (const raw of params.requestedPaths) {
|
|
237
|
-
const
|
|
217
|
+
const request = {
|
|
238
218
|
rootDir: params.rootDir,
|
|
239
219
|
requestedPath: raw,
|
|
240
220
|
scopeLabel: params.scopeLabel,
|
|
241
|
-
}
|
|
221
|
+
};
|
|
222
|
+
// Relative roots may follow cwd changes made by an input iterator or getter.
|
|
223
|
+
const resolvedRoot = process.platform !== "win32" && typeof request.rootDir === "string" &&
|
|
224
|
+
request.rootDir.startsWith("/") && request.rootDir === previousRootDir
|
|
225
|
+
? previousResolvedRoot
|
|
226
|
+
: path.resolve(request.rootDir);
|
|
227
|
+
previousRootDir = request.rootDir;
|
|
228
|
+
previousResolvedRoot = resolvedRoot;
|
|
229
|
+
const pathResult = resolvePathWithinNormalizedRoot(request, resolvedRoot);
|
|
242
230
|
if (!pathResult.ok) {
|
|
243
231
|
return { ok: false, error: pathResult.error };
|
|
244
232
|
}
|
package/dist/root-walk.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,yBAAyB,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAChC,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"root-walk.d.ts","sourceRoot":"","sources":["../src/root-walk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,KAAK,EAAY,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGrD,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAClE,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,8BAA8B,GAAG,OAAO,GAAG,iBAAiB,CAAC;AACzE,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,MAAM,GAAG,cAAc,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,CAAC;AACnE,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,GAAG,iBAAiB,GAAG,WAAW,CAAC;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB,iBAAiB,GACjB;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GACpD;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,yBAAyB,CAAC;AAE1F,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;IAChC,aAAa,EAAE,qBAAqB,CAAC;IACrC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,gBAAgB,CAAC,EAAE,8BAA8B,CAAC;CACnD,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,CACF,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAClC,CAAC;AAqBF,wBAAuB,QAAQ,CAC7B,IAAI,EAAE,kBAAkB,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,eAAe,GACvB,cAAc,CAAC,aAAa,CAAC,CAsK/B"}
|
package/dist/root-walk.js
CHANGED
|
@@ -98,6 +98,7 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
98
98
|
let failed = false;
|
|
99
99
|
let operationError;
|
|
100
100
|
try {
|
|
101
|
+
const relativeDirectory = directory.split(path.sep).join(path.posix.sep);
|
|
101
102
|
while (true) {
|
|
102
103
|
let next;
|
|
103
104
|
try {
|
|
@@ -112,7 +113,7 @@ export async function* walkRoot(root, relativePath, options) {
|
|
|
112
113
|
return;
|
|
113
114
|
const name = next.kind === "entry" ? next.entry.name : next.name;
|
|
114
115
|
const child = directory
|
|
115
|
-
? path.posix.join(
|
|
116
|
+
? path.posix.join(relativeDirectory, name)
|
|
116
117
|
: name;
|
|
117
118
|
if (next.kind === "limit") {
|
|
118
119
|
yield onLimit(child);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { type RootBoundaryIdentity } from "./root-boundary.js";
|
|
1
2
|
export declare function inheritWriteTargetMode(params: {
|
|
2
3
|
targetPath: string;
|
|
3
4
|
rootWithSep: string;
|
|
5
|
+
rootIdentity?: RootBoundaryIdentity;
|
|
4
6
|
requestedMode?: number;
|
|
5
7
|
}): Promise<number>;
|
|
6
8
|
//# sourceMappingURL=root-write-mode.d.ts.map
|