@lousy-agents/agent-shell 5.14.9 → 5.15.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/dist/index.js +373 -198
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __rspack_createRequire } from "node:module";
|
|
3
3
|
const __rspack_createRequire_require = __rspack_createRequire(import.meta.url);
|
|
4
4
|
var __webpack_modules__ = ({
|
|
5
|
-
|
|
5
|
+
341(__unused_rspack_module, __unused_rspack___webpack_exports__, __webpack_require__) {
|
|
6
6
|
|
|
7
7
|
;// CONCATENATED MODULE: external "node:child_process"
|
|
8
8
|
const external_node_child_process_namespaceObject = __rspack_createRequire_require("node:child_process");
|
|
@@ -275,6 +275,9 @@ function path_isPathInside(root, target) {
|
|
|
275
275
|
const firstSegment = relative.split(external_node_path_namespaceObject.posix.sep)[0];
|
|
276
276
|
return relative === "" || (firstSegment !== ".." && !external_node_path_namespaceObject.isAbsolute(relative));
|
|
277
277
|
}
|
|
278
|
+
function isPathRelativeEscape(relativePath) {
|
|
279
|
+
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_namespaceObject.sep}`) || external_node_path_namespaceObject.isAbsolute(relativePath);
|
|
280
|
+
}
|
|
278
281
|
function resolveSafeBaseDir(rootDir) {
|
|
279
282
|
const resolved = path.resolve(rootDir);
|
|
280
283
|
return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
|
|
@@ -423,17 +426,17 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
|
|
|
423
426
|
|
|
424
427
|
|
|
425
428
|
|
|
429
|
+
|
|
426
430
|
function isSameOrChildPath(candidate, parent) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
function isPathEscape(relativePath) {
|
|
430
|
-
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_namespaceObject.sep}`) || external_node_path_namespaceObject.isAbsolute(relativePath);
|
|
431
|
+
const parentPrefix = parent.endsWith(external_node_path_namespaceObject.sep) ? parent : `${parent}${external_node_path_namespaceObject.sep}`;
|
|
432
|
+
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
431
433
|
}
|
|
432
434
|
async function mkdirPathComponentsWithGuards(params) {
|
|
433
435
|
const root = external_node_path_namespaceObject.resolve(params.rootReal);
|
|
436
|
+
const rootCanonical = external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(root));
|
|
434
437
|
const target = external_node_path_namespaceObject.resolve(params.targetPath);
|
|
435
438
|
const relative = external_node_path_namespaceObject.relative(root, target);
|
|
436
|
-
if (
|
|
439
|
+
if (isPathRelativeEscape(relative)) {
|
|
437
440
|
throw new errors_FsSafeError("outside-workspace", "directory is outside workspace root");
|
|
438
441
|
}
|
|
439
442
|
let current = root;
|
|
@@ -456,7 +459,7 @@ async function mkdirPathComponentsWithGuards(params) {
|
|
|
456
459
|
}
|
|
457
460
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
458
461
|
// segment at a time and realpath-check each segment before descending.
|
|
459
|
-
if (!isSameOrChildPath(external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(next)),
|
|
462
|
+
if (!isSameOrChildPath(external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(next)), rootCanonical)) {
|
|
460
463
|
throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
461
464
|
}
|
|
462
465
|
await directory_guard_createAsyncDirectoryGuard(next);
|
|
@@ -543,6 +546,191 @@ function guardedRmSync(params) {
|
|
|
543
546
|
}), { verifyAfter: params.verifyAfter });
|
|
544
547
|
}
|
|
545
548
|
|
|
549
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/deny-mutations.js
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
async function pathExists(filePath) {
|
|
555
|
+
try {
|
|
556
|
+
await promises_namespaceObject.lstat(filePath);
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
catch (err) {
|
|
560
|
+
if (!path_isNotFoundPathError(err)) {
|
|
561
|
+
throw err;
|
|
562
|
+
}
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async function resolvePathViaExistingAncestor(targetPath) {
|
|
567
|
+
const normalized = external_node_path_namespaceObject.resolve(targetPath);
|
|
568
|
+
let cursor = normalized;
|
|
569
|
+
const missingSuffix = [];
|
|
570
|
+
while (external_node_path_namespaceObject.dirname(cursor) !== cursor && !(await pathExists(cursor))) {
|
|
571
|
+
missingSuffix.unshift(external_node_path_namespaceObject.basename(cursor));
|
|
572
|
+
cursor = external_node_path_namespaceObject.dirname(cursor);
|
|
573
|
+
}
|
|
574
|
+
if (!(await pathExists(cursor))) {
|
|
575
|
+
return normalized;
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
const resolvedAncestor = external_node_path_namespaceObject.resolve(await promises_namespaceObject.realpath(cursor));
|
|
579
|
+
return missingSuffix.length === 0
|
|
580
|
+
? resolvedAncestor
|
|
581
|
+
: external_node_path_namespaceObject.resolve(resolvedAncestor, ...missingSuffix);
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
return normalized;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
async function comparablePaths(rawPath) {
|
|
588
|
+
path_assertNoNulPathInput(rawPath, "path contains a NUL byte");
|
|
589
|
+
const resolved = external_node_path_namespaceObject.resolve(rawPath);
|
|
590
|
+
return new Set([resolved, await resolvePathViaExistingAncestor(resolved)]);
|
|
591
|
+
}
|
|
592
|
+
function isSamePath(left, right) {
|
|
593
|
+
return path_isPathInside(left, right) && path_isPathInside(right, left);
|
|
594
|
+
}
|
|
595
|
+
function hasPolicyEntries(policy) {
|
|
596
|
+
return Boolean(policy?.paths?.length || policy?.prefixes?.length);
|
|
597
|
+
}
|
|
598
|
+
function policyPathEntries(entries) {
|
|
599
|
+
const paths = [];
|
|
600
|
+
for (const entry of entries ?? []) {
|
|
601
|
+
if (entry.length === 0) {
|
|
602
|
+
throw new errors_FsSafeError("invalid-path", "deny mutation paths must be non-empty");
|
|
603
|
+
}
|
|
604
|
+
path_assertNoNulPathInput(entry, "deny mutation path contains a NUL byte");
|
|
605
|
+
if (!external_node_path_namespaceObject.isAbsolute(entry)) {
|
|
606
|
+
throw new errors_FsSafeError("invalid-path", "deny mutation paths must be absolute");
|
|
607
|
+
}
|
|
608
|
+
paths.push(entry);
|
|
609
|
+
}
|
|
610
|
+
return paths;
|
|
611
|
+
}
|
|
612
|
+
async function assertMutationNotDenied(filePath, policy, options = {}) {
|
|
613
|
+
if (!hasPolicyEntries(policy)) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const targetPaths = await comparablePaths(filePath);
|
|
617
|
+
for (const deniedPath of policyPathEntries(policy.paths)) {
|
|
618
|
+
const deniedPaths = await comparablePaths(deniedPath);
|
|
619
|
+
for (const target of targetPaths) {
|
|
620
|
+
for (const denied of deniedPaths) {
|
|
621
|
+
if (isSamePath(denied, target) ||
|
|
622
|
+
(options.protectAncestors === true && path_isPathInside(target, denied))) {
|
|
623
|
+
throw new errors_FsSafeError("denied-path", "path is denied by denyMutations policy");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
for (const deniedPrefix of policyPathEntries(policy.prefixes)) {
|
|
629
|
+
const deniedPaths = await comparablePaths(deniedPrefix);
|
|
630
|
+
for (const target of targetPaths) {
|
|
631
|
+
for (const denied of deniedPaths) {
|
|
632
|
+
if (path_isPathInside(denied, target) ||
|
|
633
|
+
(options.protectAncestors === true && path_isPathInside(target, denied))) {
|
|
634
|
+
throw new errors_FsSafeError("denied-path", "path is denied by denyMutations policy");
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function mergeDenyMutationPolicies(defaultPolicy, callPolicy) {
|
|
641
|
+
if (!defaultPolicy) {
|
|
642
|
+
return callPolicy;
|
|
643
|
+
}
|
|
644
|
+
if (!callPolicy) {
|
|
645
|
+
return defaultPolicy;
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
paths: [...(defaultPolicy.paths ?? []), ...(callPolicy.paths ?? [])],
|
|
649
|
+
prefixes: [...(defaultPolicy.prefixes ?? []), ...(callPolicy.prefixes ?? [])],
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-realpath.js
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
660
|
+
const handleStat = await handle.stat();
|
|
661
|
+
const fdCandidates = process.platform === "linux"
|
|
662
|
+
? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`]
|
|
663
|
+
: process.platform === "win32"
|
|
664
|
+
? []
|
|
665
|
+
: [`/dev/fd/${handle.fd}`];
|
|
666
|
+
for (const fdPath of fdCandidates) {
|
|
667
|
+
try {
|
|
668
|
+
const fdRealPath = await promises_namespaceObject.realpath(fdPath);
|
|
669
|
+
const fdRealStat = await promises_namespaceObject.stat(fdRealPath);
|
|
670
|
+
if (file_identity_sameFileIdentity(handleStat, fdRealStat)) {
|
|
671
|
+
return fdRealPath;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
// try next fd path
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
const ioRealPath = await promises_namespaceObject.realpath(ioPath);
|
|
680
|
+
const ioRealStat = await promises_namespaceObject.stat(ioRealPath);
|
|
681
|
+
if (file_identity_sameFileIdentity(handleStat, ioRealStat)) {
|
|
682
|
+
return ioRealPath;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
if (!path_isNotFoundPathError(err)) {
|
|
687
|
+
throw err;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
|
|
691
|
+
if (parentResolved) {
|
|
692
|
+
return parentResolved;
|
|
693
|
+
}
|
|
694
|
+
throw new errors_FsSafeError("path-mismatch", "unable to resolve opened file path");
|
|
695
|
+
}
|
|
696
|
+
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
697
|
+
let parentReal;
|
|
698
|
+
try {
|
|
699
|
+
parentReal = await promises_namespaceObject.realpath(external_node_path_namespaceObject.dirname(ioPath));
|
|
700
|
+
}
|
|
701
|
+
catch (err) {
|
|
702
|
+
if (path_isNotFoundPathError(err)) {
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
throw err;
|
|
706
|
+
}
|
|
707
|
+
let entries;
|
|
708
|
+
try {
|
|
709
|
+
entries = await promises_namespaceObject.readdir(parentReal);
|
|
710
|
+
}
|
|
711
|
+
catch (err) {
|
|
712
|
+
if (path_isNotFoundPathError(err)) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
throw err;
|
|
716
|
+
}
|
|
717
|
+
for (const entry of entries.toSorted()) {
|
|
718
|
+
const candidatePath = external_node_path_namespaceObject.join(parentReal, entry);
|
|
719
|
+
try {
|
|
720
|
+
const candidateStat = await promises_namespaceObject.lstat(candidatePath);
|
|
721
|
+
if (candidateStat.isFile() && file_identity_sameFileIdentity(handleStat, candidateStat)) {
|
|
722
|
+
return await promises_namespaceObject.realpath(candidatePath);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
catch (err) {
|
|
726
|
+
if (!path_isNotFoundPathError(err)) {
|
|
727
|
+
throw err;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
|
|
546
734
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-python-config.js
|
|
547
735
|
let overrideConfig = {};
|
|
548
736
|
function parseMode(value) {
|
|
@@ -589,26 +777,20 @@ function canFallbackFromPythonError(error) {
|
|
|
589
777
|
|
|
590
778
|
|
|
591
779
|
const PINNED_PYTHON_WORKER_SOURCE = String.raw `
|
|
592
|
-
import base64
|
|
593
|
-
import errno
|
|
594
|
-
import json
|
|
595
|
-
import os
|
|
596
|
-
import secrets
|
|
597
|
-
import stat
|
|
598
|
-
import sys
|
|
599
|
-
|
|
780
|
+
import base64, errno, json, os, secrets, stat, sys
|
|
600
781
|
DIR_FLAGS = os.O_RDONLY
|
|
601
782
|
if hasattr(os, "O_DIRECTORY"):
|
|
602
783
|
DIR_FLAGS |= os.O_DIRECTORY
|
|
603
784
|
if hasattr(os, "O_NOFOLLOW"):
|
|
604
785
|
DIR_FLAGS |= os.O_NOFOLLOW
|
|
605
786
|
READ_FLAGS = os.O_RDONLY
|
|
787
|
+
if hasattr(os, "O_NONBLOCK"):
|
|
788
|
+
READ_FLAGS |= os.O_NONBLOCK
|
|
606
789
|
if hasattr(os, "O_NOFOLLOW"):
|
|
607
790
|
READ_FLAGS |= os.O_NOFOLLOW
|
|
608
791
|
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
609
792
|
if hasattr(os, "O_NOFOLLOW"):
|
|
610
793
|
WRITE_FLAGS |= os.O_NOFOLLOW
|
|
611
|
-
|
|
612
794
|
def split_relative(value):
|
|
613
795
|
if value in ("", "."):
|
|
614
796
|
return []
|
|
@@ -621,10 +803,8 @@ def split_relative(value):
|
|
|
621
803
|
if part == "..":
|
|
622
804
|
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
623
805
|
return parts
|
|
624
|
-
|
|
625
806
|
def open_dir(path_value, dir_fd=None):
|
|
626
807
|
return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
|
|
627
|
-
|
|
628
808
|
def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
629
809
|
current_fd = os.dup(root_fd)
|
|
630
810
|
try:
|
|
@@ -642,14 +822,12 @@ def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
|
642
822
|
except Exception:
|
|
643
823
|
os.close(current_fd)
|
|
644
824
|
raise
|
|
645
|
-
|
|
646
825
|
def parent_and_basename(root_fd, relative):
|
|
647
826
|
segments = split_relative(relative)
|
|
648
827
|
if not segments:
|
|
649
828
|
raise OSError(errno.EPERM, "operation requires a non-root path")
|
|
650
829
|
parent_fd = walk_dir(root_fd, segments[:-1])
|
|
651
830
|
return parent_fd, segments[-1]
|
|
652
|
-
|
|
653
831
|
def encode_stat(st):
|
|
654
832
|
mode = st.st_mode
|
|
655
833
|
return {
|
|
@@ -665,14 +843,12 @@ def encode_stat(st):
|
|
|
665
843
|
"size": st.st_size,
|
|
666
844
|
"uid": st.st_uid,
|
|
667
845
|
}
|
|
668
|
-
|
|
669
846
|
def reject_unsafe_endpoint(st):
|
|
670
847
|
mode = st.st_mode
|
|
671
848
|
if stat.S_ISLNK(mode):
|
|
672
849
|
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
673
850
|
if stat.S_ISREG(mode) and st.st_nlink > 1:
|
|
674
851
|
raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
|
|
675
|
-
|
|
676
852
|
def copy_bytes(source_fd, dest_fd):
|
|
677
853
|
while True:
|
|
678
854
|
chunk = os.read(source_fd, 65536)
|
|
@@ -684,7 +860,6 @@ def copy_bytes(source_fd, dest_fd):
|
|
|
684
860
|
if written <= 0:
|
|
685
861
|
raise OSError(errno.EIO, "short write")
|
|
686
862
|
view = view[written:]
|
|
687
|
-
|
|
688
863
|
def write_all(fd, data):
|
|
689
864
|
view = memoryview(data)
|
|
690
865
|
while view:
|
|
@@ -692,11 +867,9 @@ def write_all(fd, data):
|
|
|
692
867
|
if written <= 0:
|
|
693
868
|
raise OSError(errno.EIO, "short write")
|
|
694
869
|
view = view[written:]
|
|
695
|
-
|
|
696
870
|
def link_unsupported(exc):
|
|
697
871
|
unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
|
|
698
872
|
return getattr(exc, "errno", None) in unsupported
|
|
699
|
-
|
|
700
873
|
def link_no_replace(name, new_name, source_fd, target_fd):
|
|
701
874
|
linked = False
|
|
702
875
|
try:
|
|
@@ -711,11 +884,9 @@ def link_no_replace(name, new_name, source_fd, target_fd):
|
|
|
711
884
|
os.fsync(source_fd)
|
|
712
885
|
if source_fd != target_fd:
|
|
713
886
|
os.fsync(target_fd)
|
|
714
|
-
|
|
715
887
|
def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
|
|
716
888
|
source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
|
|
717
|
-
dest_fd = None
|
|
718
|
-
success = False
|
|
889
|
+
dest_fd = None; success = False; dest_stat = None
|
|
719
890
|
try:
|
|
720
891
|
if expected is not None:
|
|
721
892
|
source_stat = os.fstat(source_fd)
|
|
@@ -724,6 +895,7 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
|
|
|
724
895
|
dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
|
|
725
896
|
copy_bytes(source_fd, dest_fd)
|
|
726
897
|
os.fsync(dest_fd)
|
|
898
|
+
dest_stat = os.fstat(dest_fd)
|
|
727
899
|
success = True
|
|
728
900
|
finally:
|
|
729
901
|
os.close(source_fd)
|
|
@@ -739,19 +911,40 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
|
|
|
739
911
|
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
740
912
|
except FileNotFoundError: pass
|
|
741
913
|
raise
|
|
742
|
-
|
|
743
|
-
def
|
|
914
|
+
return dest_stat
|
|
915
|
+
def same_identity(left, right):
|
|
916
|
+
return left.st_dev == right.st_dev and left.st_ino == right.st_ino
|
|
917
|
+
def verify_temp_name(parent_fd, temp_name, expected_stat):
|
|
918
|
+
current_stat = os.lstat(temp_name, dir_fd=parent_fd)
|
|
919
|
+
if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
|
|
920
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
921
|
+
def verify_committed_temp(parent_fd, basename, expected_stat):
|
|
922
|
+
final_stat = os.lstat(basename, dir_fd=parent_fd)
|
|
923
|
+
if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
|
|
924
|
+
return final_stat
|
|
925
|
+
try: os.unlink(basename, dir_fd=parent_fd)
|
|
926
|
+
except FileNotFoundError: pass
|
|
927
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
928
|
+
def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
|
|
929
|
+
verify_temp_name(parent_fd, temp_name, expected_stat)
|
|
744
930
|
if overwrite:
|
|
745
931
|
os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
|
|
932
|
+
return verify_committed_temp(parent_fd, basename, expected_stat)
|
|
746
933
|
else:
|
|
747
934
|
try:
|
|
748
935
|
os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
|
|
936
|
+
final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
|
|
749
937
|
os.unlink(temp_name, dir_fd=parent_fd)
|
|
938
|
+
return final_stat
|
|
750
939
|
except OSError as exc:
|
|
751
940
|
if not link_unsupported(exc):
|
|
752
941
|
raise
|
|
753
|
-
copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode,
|
|
754
|
-
|
|
942
|
+
return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
|
|
943
|
+
def assert_expected_root(root_fd, payload):
|
|
944
|
+
if "rootDev" in payload or "rootIno" in payload:
|
|
945
|
+
root_stat = os.fstat(root_fd)
|
|
946
|
+
if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
|
|
947
|
+
raise RuntimeError("fs-safe-root-mismatch")
|
|
755
948
|
def stat_path(root_fd, payload):
|
|
756
949
|
relative = payload.get("relativePath", "")
|
|
757
950
|
segments = split_relative(relative)
|
|
@@ -765,7 +958,6 @@ def stat_path(root_fd, payload):
|
|
|
765
958
|
return encode_stat(st)
|
|
766
959
|
finally:
|
|
767
960
|
os.close(parent_fd)
|
|
768
|
-
|
|
769
961
|
def readdir_path(root_fd, payload):
|
|
770
962
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
|
|
771
963
|
try:
|
|
@@ -781,12 +973,9 @@ def readdir_path(root_fd, payload):
|
|
|
781
973
|
return entries
|
|
782
974
|
finally:
|
|
783
975
|
os.close(dir_fd)
|
|
784
|
-
|
|
785
976
|
def mkdirp_path(root_fd, payload):
|
|
786
977
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
|
|
787
|
-
os.close(dir_fd)
|
|
788
|
-
return None
|
|
789
|
-
|
|
978
|
+
os.close(dir_fd); return None
|
|
790
979
|
def remove_tree(parent_fd, basename):
|
|
791
980
|
st = os.lstat(basename, dir_fd=parent_fd)
|
|
792
981
|
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
@@ -799,7 +988,6 @@ def remove_tree(parent_fd, basename):
|
|
|
799
988
|
os.rmdir(basename, dir_fd=parent_fd)
|
|
800
989
|
else:
|
|
801
990
|
os.unlink(basename, dir_fd=parent_fd)
|
|
802
|
-
|
|
803
991
|
def remove_path(root_fd, payload):
|
|
804
992
|
parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
|
|
805
993
|
try:
|
|
@@ -882,14 +1070,15 @@ def write_path(root_fd, payload):
|
|
|
882
1070
|
except FileNotFoundError:
|
|
883
1071
|
pass
|
|
884
1072
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
1073
|
+
os.fchmod(temp_fd, mode)
|
|
885
1074
|
write_all(temp_fd, data)
|
|
886
1075
|
os.fsync(temp_fd)
|
|
1076
|
+
temp_stat = os.fstat(temp_fd)
|
|
887
1077
|
os.close(temp_fd)
|
|
888
1078
|
temp_fd = None
|
|
889
|
-
commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
|
|
1079
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
890
1080
|
temp_name = None
|
|
891
1081
|
os.fsync(parent_fd)
|
|
892
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
893
1082
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
894
1083
|
finally:
|
|
895
1084
|
if temp_fd is not None:
|
|
@@ -920,6 +1109,7 @@ def copy_path(root_fd, payload):
|
|
|
920
1109
|
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
|
|
921
1110
|
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
|
|
922
1111
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
1112
|
+
os.fchmod(temp_fd, mode)
|
|
923
1113
|
written_bytes = 0
|
|
924
1114
|
while True:
|
|
925
1115
|
chunk = os.read(source_fd, 65536)
|
|
@@ -935,12 +1125,12 @@ def copy_path(root_fd, payload):
|
|
|
935
1125
|
raise OSError(errno.EIO, "short write")
|
|
936
1126
|
view = view[written:]
|
|
937
1127
|
os.fsync(temp_fd)
|
|
1128
|
+
temp_stat = os.fstat(temp_fd)
|
|
938
1129
|
os.close(temp_fd)
|
|
939
1130
|
temp_fd = None
|
|
940
|
-
commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
|
|
1131
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
941
1132
|
temp_name = None
|
|
942
1133
|
os.fsync(parent_fd)
|
|
943
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
944
1134
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
945
1135
|
finally:
|
|
946
1136
|
os.close(source_fd)
|
|
@@ -957,6 +1147,7 @@ def copy_path(root_fd, payload):
|
|
|
957
1147
|
def run_operation(operation, root_path, payload):
|
|
958
1148
|
root_fd = open_dir(root_path)
|
|
959
1149
|
try:
|
|
1150
|
+
assert_expected_root(root_fd, payload)
|
|
960
1151
|
if operation == "stat":
|
|
961
1152
|
return stat_path(root_fd, payload)
|
|
962
1153
|
if operation == "readdir":
|
|
@@ -1061,6 +1252,12 @@ function mapWorkerError(response) {
|
|
|
1061
1252
|
if (message.includes("fs-safe-source-mismatch")) {
|
|
1062
1253
|
return new errors_FsSafeError("path-mismatch", "source path changed during copy");
|
|
1063
1254
|
}
|
|
1255
|
+
if (message.includes("fs-safe-temp-mismatch")) {
|
|
1256
|
+
return new errors_FsSafeError("path-mismatch", "temp path changed during write");
|
|
1257
|
+
}
|
|
1258
|
+
if (message.includes("fs-safe-root-mismatch")) {
|
|
1259
|
+
return new errors_FsSafeError("path-mismatch", "root path changed during operation");
|
|
1260
|
+
}
|
|
1064
1261
|
if (message.includes("fs-safe-directory-noreplace-unsupported")) {
|
|
1065
1262
|
return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
1066
1263
|
}
|
|
@@ -1241,9 +1438,7 @@ function validatePinnedOperationPayload(payload) {
|
|
|
1241
1438
|
}
|
|
1242
1439
|
}
|
|
1243
1440
|
function isPinnedHelperUnavailable(error) {
|
|
1244
|
-
return error instanceof Error &&
|
|
1245
|
-
"code" in error &&
|
|
1246
|
-
error.code === "helper-unavailable";
|
|
1441
|
+
return error instanceof Error && "code" in error && error.code === "helper-unavailable";
|
|
1247
1442
|
}
|
|
1248
1443
|
function validatePinnedRelativePath(relativePath) {
|
|
1249
1444
|
if (relativePath.length === 0 || relativePath === ".") {
|
|
@@ -1341,29 +1536,21 @@ function assertWithinMaxBytes(bytes, maxBytes) {
|
|
|
1341
1536
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
1342
1537
|
}
|
|
1343
1538
|
}
|
|
1344
|
-
function
|
|
1345
|
-
if (maxBytes === undefined) {
|
|
1346
|
-
return undefined;
|
|
1347
|
-
}
|
|
1539
|
+
async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
1348
1540
|
let bytes = 0;
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1541
|
+
for await (const chunk of stream) {
|
|
1542
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1543
|
+
bytes += buffer.byteLength;
|
|
1544
|
+
assertWithinMaxBytes(bytes, maxBytes);
|
|
1545
|
+
let offset = 0;
|
|
1546
|
+
while (offset < buffer.byteLength) {
|
|
1547
|
+
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
1548
|
+
if (bytesWritten <= 0) {
|
|
1549
|
+
throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
|
|
1355
1550
|
}
|
|
1356
|
-
|
|
1357
|
-
}
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1360
|
-
async function pipelineWithMaxBytes(stream, destination, maxBytes) {
|
|
1361
|
-
const limiter = pinned_write_createMaxBytesTransform(maxBytes);
|
|
1362
|
-
if (limiter) {
|
|
1363
|
-
await (0,external_node_stream_promises_namespaceObject.pipeline)(stream, limiter, destination);
|
|
1364
|
-
return;
|
|
1551
|
+
offset += bytesWritten;
|
|
1552
|
+
}
|
|
1365
1553
|
}
|
|
1366
|
-
await (0,external_node_stream_promises_namespaceObject.pipeline)(stream, destination);
|
|
1367
1554
|
}
|
|
1368
1555
|
async function inputToBase64(input, maxBytes) {
|
|
1369
1556
|
if (input.kind === "buffer") {
|
|
@@ -1409,6 +1596,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
1409
1596
|
mode: params.mode || 0o600,
|
|
1410
1597
|
overwrite: params.overwrite !== false,
|
|
1411
1598
|
relativeParentPath: params.relativeParentPath,
|
|
1599
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
1412
1600
|
};
|
|
1413
1601
|
try {
|
|
1414
1602
|
return await runPinnedPythonOperation({
|
|
@@ -1439,6 +1627,7 @@ async function runPinnedCopyHelper(params) {
|
|
|
1439
1627
|
mode: params.mode || 0o600,
|
|
1440
1628
|
overwrite: params.overwrite !== false,
|
|
1441
1629
|
relativeParentPath: params.relativeParentPath,
|
|
1630
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
1442
1631
|
sourceDev: params.sourceIdentity.dev,
|
|
1443
1632
|
sourceIno: params.sourceIdentity.ino,
|
|
1444
1633
|
sourcePath: params.sourcePath,
|
|
@@ -1449,12 +1638,12 @@ async function runPinnedWriteFallback(params) {
|
|
|
1449
1638
|
const parentPath = params.relativeParentPath
|
|
1450
1639
|
? external_node_path_namespaceObject.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
1451
1640
|
: params.rootPath;
|
|
1452
|
-
const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
1453
1641
|
if (params.mkdir) {
|
|
1454
|
-
await
|
|
1455
|
-
await promises_namespaceObject.mkdir(parentPath, { recursive: true });
|
|
1456
|
-
});
|
|
1642
|
+
await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
|
|
1457
1643
|
}
|
|
1644
|
+
const parentGuard = params.mkdir
|
|
1645
|
+
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
1646
|
+
: await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
1458
1647
|
const targetPath = external_node_path_namespaceObject.join(parentPath, params.basename);
|
|
1459
1648
|
if (params.overwrite === false) {
|
|
1460
1649
|
let handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_namespaceObject.open(targetPath, external_node_fs_namespaceObject.constants.O_WRONLY | external_node_fs_namespaceObject.constants.O_CREAT | external_node_fs_namespaceObject.constants.O_EXCL, params.mode), {
|
|
@@ -1476,7 +1665,7 @@ async function runPinnedWriteFallback(params) {
|
|
|
1476
1665
|
}
|
|
1477
1666
|
}
|
|
1478
1667
|
else {
|
|
1479
|
-
await
|
|
1668
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
1480
1669
|
}
|
|
1481
1670
|
const stat = await handle.stat();
|
|
1482
1671
|
created = false;
|
|
@@ -1497,7 +1686,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
1497
1686
|
? external_node_fs_namespaceObject.constants.O_NOFOLLOW
|
|
1498
1687
|
: 0);
|
|
1499
1688
|
let handle;
|
|
1500
|
-
let
|
|
1689
|
+
let tempStat;
|
|
1690
|
+
let targetStat;
|
|
1691
|
+
let renamed = false;
|
|
1501
1692
|
try {
|
|
1502
1693
|
handle = await promises_namespaceObject.open(tempPath, tempFlags, params.mode);
|
|
1503
1694
|
if (params.input.kind === "buffer") {
|
|
@@ -1510,29 +1701,36 @@ async function runPinnedWriteFallback(params) {
|
|
|
1510
1701
|
}
|
|
1511
1702
|
}
|
|
1512
1703
|
else {
|
|
1513
|
-
|
|
1514
|
-
writable.once("close", () => {
|
|
1515
|
-
handleClosedByStream = true;
|
|
1516
|
-
});
|
|
1517
|
-
await pipelineWithMaxBytes(params.input.stream, writable, params.maxBytes);
|
|
1704
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
1518
1705
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1706
|
+
tempStat = await handle.stat();
|
|
1707
|
+
const tempPathStat = await promises_namespaceObject.lstat(tempPath);
|
|
1708
|
+
if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
|
|
1709
|
+
throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
1522
1710
|
}
|
|
1711
|
+
const expectedTempStat = tempStat;
|
|
1712
|
+
await handle.close().catch(() => undefined);
|
|
1713
|
+
handle = undefined;
|
|
1523
1714
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
1524
1715
|
await promises_namespaceObject.rename(tempPath, targetPath);
|
|
1716
|
+
renamed = true;
|
|
1717
|
+
targetStat = await promises_namespaceObject.lstat(targetPath);
|
|
1718
|
+
if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
|
|
1719
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
1720
|
+
}
|
|
1525
1721
|
});
|
|
1526
1722
|
}
|
|
1527
1723
|
catch (error) {
|
|
1528
|
-
|
|
1529
|
-
|
|
1724
|
+
await handle?.close().catch(() => undefined);
|
|
1725
|
+
if (!renamed) {
|
|
1726
|
+
await promises_namespaceObject.rm(tempPath, { force: true }).catch(() => undefined);
|
|
1530
1727
|
}
|
|
1531
|
-
await promises_namespaceObject.rm(tempPath, { force: true }).catch(() => undefined);
|
|
1532
1728
|
throw error;
|
|
1533
1729
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1730
|
+
if (!targetStat) {
|
|
1731
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target was not verified");
|
|
1732
|
+
}
|
|
1733
|
+
return { dev: targetStat.dev, ino: targetStat.ino };
|
|
1536
1734
|
}
|
|
1537
1735
|
|
|
1538
1736
|
;// CONCATENATED MODULE: external "node:os"
|
|
@@ -1558,7 +1756,7 @@ async function resolveRootPath(params) {
|
|
|
1558
1756
|
const absolutePath = external_node_path_namespaceObject.resolve(params.absolutePath);
|
|
1559
1757
|
const rootCanonicalPath = params.rootCanonicalPath
|
|
1560
1758
|
? external_node_path_namespaceObject.resolve(params.rootCanonicalPath)
|
|
1561
|
-
: await
|
|
1759
|
+
: await root_path_resolvePathViaExistingAncestor(rootPath);
|
|
1562
1760
|
const context = createBoundaryResolutionContext({
|
|
1563
1761
|
resolveParams: params,
|
|
1564
1762
|
rootPath,
|
|
@@ -1927,7 +2125,7 @@ async function resolveOutsideLexicalCanonicalPathAsync(params) {
|
|
|
1927
2125
|
if (path_isPathInside(params.rootPath, params.absolutePath)) {
|
|
1928
2126
|
return undefined;
|
|
1929
2127
|
}
|
|
1930
|
-
return await
|
|
2128
|
+
return await root_path_resolvePathViaExistingAncestor(params.absolutePath);
|
|
1931
2129
|
}
|
|
1932
2130
|
function resolveOutsideLexicalCanonicalPathSync(params) {
|
|
1933
2131
|
if (isPathInside(params.rootPath, params.absolutePath)) {
|
|
@@ -1974,11 +2172,11 @@ function buildResolvedRootPath(params) {
|
|
|
1974
2172
|
kind: params.kind.kind,
|
|
1975
2173
|
};
|
|
1976
2174
|
}
|
|
1977
|
-
async function
|
|
2175
|
+
async function root_path_resolvePathViaExistingAncestor(targetPath) {
|
|
1978
2176
|
const normalized = external_node_path_namespaceObject.resolve(targetPath);
|
|
1979
2177
|
let cursor = normalized;
|
|
1980
2178
|
const missingSuffix = [];
|
|
1981
|
-
while (!isFilesystemRoot(cursor) && !(await
|
|
2179
|
+
while (!isFilesystemRoot(cursor) && !(await root_path_pathExists(cursor))) {
|
|
1982
2180
|
missingSuffix.unshift(external_node_path_namespaceObject.basename(cursor));
|
|
1983
2181
|
const parent = external_node_path_namespaceObject.dirname(cursor);
|
|
1984
2182
|
if (parent === cursor) {
|
|
@@ -1986,7 +2184,7 @@ async function resolvePathViaExistingAncestor(targetPath) {
|
|
|
1986
2184
|
}
|
|
1987
2185
|
cursor = parent;
|
|
1988
2186
|
}
|
|
1989
|
-
if (!(await
|
|
2187
|
+
if (!(await root_path_pathExists(cursor))) {
|
|
1990
2188
|
return normalized;
|
|
1991
2189
|
}
|
|
1992
2190
|
try {
|
|
@@ -2071,7 +2269,7 @@ function relativeInsideRoot(rootPath, targetPath) {
|
|
|
2071
2269
|
if (!relative || relative === ".") {
|
|
2072
2270
|
return "";
|
|
2073
2271
|
}
|
|
2074
|
-
if (
|
|
2272
|
+
if (isPathRelativeEscape(relative)) {
|
|
2075
2273
|
return "";
|
|
2076
2274
|
}
|
|
2077
2275
|
return relative;
|
|
@@ -2098,7 +2296,7 @@ function shortPath(value) {
|
|
|
2098
2296
|
function isFilesystemRoot(candidate) {
|
|
2099
2297
|
return external_node_path_namespaceObject.parse(candidate).root === candidate;
|
|
2100
2298
|
}
|
|
2101
|
-
async function
|
|
2299
|
+
async function root_path_pathExists(targetPath) {
|
|
2102
2300
|
try {
|
|
2103
2301
|
await promises_namespaceObject.lstat(targetPath);
|
|
2104
2302
|
return true;
|
|
@@ -2120,7 +2318,7 @@ async function resolveSymlinkHopPath(symlinkPath) {
|
|
|
2120
2318
|
}
|
|
2121
2319
|
const linkTarget = await promises_namespaceObject.readlink(symlinkPath);
|
|
2122
2320
|
const linkAbsolute = external_node_path_namespaceObject.resolve(external_node_path_namespaceObject.dirname(symlinkPath), linkTarget);
|
|
2123
|
-
return
|
|
2321
|
+
return root_path_resolvePathViaExistingAncestor(linkAbsolute);
|
|
2124
2322
|
}
|
|
2125
2323
|
}
|
|
2126
2324
|
function resolveSymlinkHopPathSync(symlinkPath) {
|
|
@@ -2189,6 +2387,23 @@ function path_policy_shortPath(value) {
|
|
|
2189
2387
|
return value;
|
|
2190
2388
|
}
|
|
2191
2389
|
|
|
2390
|
+
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/read-opened-file.js
|
|
2391
|
+
|
|
2392
|
+
async function read_opened_file_readOpenedFileSafely(params) {
|
|
2393
|
+
if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
|
|
2394
|
+
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
|
|
2395
|
+
}
|
|
2396
|
+
const buffer = await params.opened.handle.readFile();
|
|
2397
|
+
if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
|
|
2398
|
+
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
|
|
2399
|
+
}
|
|
2400
|
+
return {
|
|
2401
|
+
buffer,
|
|
2402
|
+
realPath: params.opened.realPath,
|
|
2403
|
+
stat: params.opened.stat,
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2192
2407
|
;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-stat.js
|
|
2193
2408
|
function pathStatFromStats(stat) {
|
|
2194
2409
|
return {
|
|
@@ -2516,6 +2731,10 @@ async function serializePathWrite(key, run) {
|
|
|
2516
2731
|
|
|
2517
2732
|
|
|
2518
2733
|
|
|
2734
|
+
|
|
2735
|
+
|
|
2736
|
+
|
|
2737
|
+
|
|
2519
2738
|
|
|
2520
2739
|
|
|
2521
2740
|
|
|
@@ -2714,6 +2933,7 @@ class RootHandle {
|
|
|
2714
2933
|
mkdir: this.defaults.mkdir,
|
|
2715
2934
|
mode: this.defaults.mode,
|
|
2716
2935
|
...options,
|
|
2936
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2717
2937
|
append: writeMode === "append",
|
|
2718
2938
|
truncateExisting: writeMode === "replace",
|
|
2719
2939
|
});
|
|
@@ -2725,18 +2945,29 @@ class RootHandle {
|
|
|
2725
2945
|
mkdir: this.defaults.mkdir,
|
|
2726
2946
|
mode: this.defaults.mode,
|
|
2727
2947
|
...options,
|
|
2948
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2728
2949
|
});
|
|
2729
2950
|
}
|
|
2730
|
-
async remove(relativePath) {
|
|
2951
|
+
async remove(relativePath, options = {}) {
|
|
2731
2952
|
assertValidRootRelativePath(relativePath);
|
|
2732
|
-
await removePathInRoot(this.context,
|
|
2953
|
+
await removePathInRoot(this.context, {
|
|
2954
|
+
relativePath,
|
|
2955
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2956
|
+
});
|
|
2733
2957
|
}
|
|
2734
|
-
async mkdir(relativePath) {
|
|
2958
|
+
async mkdir(relativePath, options = {}) {
|
|
2735
2959
|
assertValidRootRelativePath(relativePath);
|
|
2736
|
-
await mkdirPathInRoot(this.context, {
|
|
2960
|
+
await mkdirPathInRoot(this.context, {
|
|
2961
|
+
relativePath,
|
|
2962
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2963
|
+
});
|
|
2737
2964
|
}
|
|
2738
|
-
async ensureRoot() {
|
|
2739
|
-
await mkdirPathInRoot(this.context, {
|
|
2965
|
+
async ensureRoot(options = {}) {
|
|
2966
|
+
await mkdirPathInRoot(this.context, {
|
|
2967
|
+
relativePath: "",
|
|
2968
|
+
allowRoot: true,
|
|
2969
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2970
|
+
});
|
|
2740
2971
|
}
|
|
2741
2972
|
async write(relativePath, data, options = {}) {
|
|
2742
2973
|
await writeFileInRoot(this.context, {
|
|
@@ -2745,6 +2976,7 @@ class RootHandle {
|
|
|
2745
2976
|
mkdir: this.defaults.mkdir,
|
|
2746
2977
|
mode: this.defaults.mode,
|
|
2747
2978
|
...options,
|
|
2979
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2748
2980
|
});
|
|
2749
2981
|
}
|
|
2750
2982
|
async create(relativePath, data, options = {}) {
|
|
@@ -2754,6 +2986,7 @@ class RootHandle {
|
|
|
2754
2986
|
mkdir: this.defaults.mkdir,
|
|
2755
2987
|
mode: this.defaults.mode,
|
|
2756
2988
|
...options,
|
|
2989
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2757
2990
|
overwrite: false,
|
|
2758
2991
|
});
|
|
2759
2992
|
}
|
|
@@ -2776,6 +3009,7 @@ class RootHandle {
|
|
|
2776
3009
|
mkdir: this.defaults.mkdir,
|
|
2777
3010
|
mode: this.defaults.mode,
|
|
2778
3011
|
...options,
|
|
3012
|
+
denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
|
|
2779
3013
|
});
|
|
2780
3014
|
}
|
|
2781
3015
|
async exists(relativePath) {
|
|
@@ -2819,6 +3053,12 @@ class RootHandle {
|
|
|
2819
3053
|
async move(fromRelative, toRelative, options = {}) {
|
|
2820
3054
|
assertValidRootRelativePath(fromRelative);
|
|
2821
3055
|
assertValidRootRelativePath(toRelative);
|
|
3056
|
+
const denyMutations = mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations);
|
|
3057
|
+
await assertMoveMutationAllowed(this.context, {
|
|
3058
|
+
fromRelative,
|
|
3059
|
+
toRelative,
|
|
3060
|
+
denyMutations,
|
|
3061
|
+
});
|
|
2822
3062
|
try {
|
|
2823
3063
|
await runPinnedHelper("rename", this.rootReal, {
|
|
2824
3064
|
from: fromRelative,
|
|
@@ -2830,6 +3070,7 @@ class RootHandle {
|
|
|
2830
3070
|
if (canFallbackFromPythonError(error)) {
|
|
2831
3071
|
await movePathFallback(this.context, {
|
|
2832
3072
|
fromRelative,
|
|
3073
|
+
denyMutations,
|
|
2833
3074
|
overwrite: options.overwrite ?? false,
|
|
2834
3075
|
toRelative,
|
|
2835
3076
|
});
|
|
@@ -2878,7 +3119,7 @@ async function openFileInRoot(root, params) {
|
|
|
2878
3119
|
async function readFileInRoot(root, params) {
|
|
2879
3120
|
const opened = await openFileInRoot(root, params);
|
|
2880
3121
|
try {
|
|
2881
|
-
return await
|
|
3122
|
+
return await read_opened_file_readOpenedFileSafely({ opened, maxBytes: params.maxBytes });
|
|
2882
3123
|
}
|
|
2883
3124
|
finally {
|
|
2884
3125
|
await opened.handle.close().catch(() => { });
|
|
@@ -2911,20 +3152,6 @@ async function openLocalFileSafely(params) {
|
|
|
2911
3152
|
assertNoNulPathInput(params.filePath, "file path contains a NUL byte");
|
|
2912
3153
|
return await openVerifiedLocalFile(params.filePath);
|
|
2913
3154
|
}
|
|
2914
|
-
async function readOpenedFileSafely(params) {
|
|
2915
|
-
if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
|
|
2916
|
-
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
|
|
2917
|
-
}
|
|
2918
|
-
const buffer = await params.opened.handle.readFile();
|
|
2919
|
-
if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
|
|
2920
|
-
throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
|
|
2921
|
-
}
|
|
2922
|
-
return {
|
|
2923
|
-
buffer,
|
|
2924
|
-
realPath: params.opened.realPath,
|
|
2925
|
-
stat: params.opened.stat,
|
|
2926
|
-
};
|
|
2927
|
-
}
|
|
2928
3155
|
function emitWriteBoundaryWarning(reason) {
|
|
2929
3156
|
logWarn(`security: fs-safe write boundary warning (${reason})`);
|
|
2930
3157
|
}
|
|
@@ -2965,82 +3192,9 @@ async function verifyAtomicWriteResult(params) {
|
|
|
2965
3192
|
await opened.handle.close().catch(() => { });
|
|
2966
3193
|
}
|
|
2967
3194
|
}
|
|
2968
|
-
async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
|
|
2969
|
-
const handleStat = await handle.stat();
|
|
2970
|
-
const fdCandidates = process.platform === "linux"
|
|
2971
|
-
? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`]
|
|
2972
|
-
: process.platform === "win32"
|
|
2973
|
-
? []
|
|
2974
|
-
: [`/dev/fd/${handle.fd}`];
|
|
2975
|
-
for (const fdPath of fdCandidates) {
|
|
2976
|
-
try {
|
|
2977
|
-
const fdRealPath = await promises_namespaceObject.realpath(fdPath);
|
|
2978
|
-
const fdRealStat = await promises_namespaceObject.stat(fdRealPath);
|
|
2979
|
-
if (file_identity_sameFileIdentity(handleStat, fdRealStat)) {
|
|
2980
|
-
return fdRealPath;
|
|
2981
|
-
}
|
|
2982
|
-
}
|
|
2983
|
-
catch {
|
|
2984
|
-
// try next fd path
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
try {
|
|
2988
|
-
const ioRealPath = await promises_namespaceObject.realpath(ioPath);
|
|
2989
|
-
const ioRealStat = await promises_namespaceObject.stat(ioRealPath);
|
|
2990
|
-
if (file_identity_sameFileIdentity(handleStat, ioRealStat)) {
|
|
2991
|
-
return ioRealPath;
|
|
2992
|
-
}
|
|
2993
|
-
}
|
|
2994
|
-
catch (err) {
|
|
2995
|
-
if (!path_isNotFoundPathError(err)) {
|
|
2996
|
-
throw err;
|
|
2997
|
-
}
|
|
2998
|
-
}
|
|
2999
|
-
const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
|
|
3000
|
-
if (parentResolved) {
|
|
3001
|
-
return parentResolved;
|
|
3002
|
-
}
|
|
3003
|
-
throw new errors_FsSafeError("path-mismatch", "unable to resolve opened file path");
|
|
3004
|
-
}
|
|
3005
|
-
async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
|
|
3006
|
-
let parentReal;
|
|
3007
|
-
try {
|
|
3008
|
-
parentReal = await promises_namespaceObject.realpath(external_node_path_namespaceObject.dirname(ioPath));
|
|
3009
|
-
}
|
|
3010
|
-
catch (err) {
|
|
3011
|
-
if (path_isNotFoundPathError(err)) {
|
|
3012
|
-
return null;
|
|
3013
|
-
}
|
|
3014
|
-
throw err;
|
|
3015
|
-
}
|
|
3016
|
-
let entries;
|
|
3017
|
-
try {
|
|
3018
|
-
entries = await promises_namespaceObject.readdir(parentReal);
|
|
3019
|
-
}
|
|
3020
|
-
catch (err) {
|
|
3021
|
-
if (path_isNotFoundPathError(err)) {
|
|
3022
|
-
return null;
|
|
3023
|
-
}
|
|
3024
|
-
throw err;
|
|
3025
|
-
}
|
|
3026
|
-
for (const entry of entries.toSorted()) {
|
|
3027
|
-
const candidatePath = external_node_path_namespaceObject.join(parentReal, entry);
|
|
3028
|
-
try {
|
|
3029
|
-
const candidateStat = await promises_namespaceObject.lstat(candidatePath);
|
|
3030
|
-
if (candidateStat.isFile() && file_identity_sameFileIdentity(handleStat, candidateStat)) {
|
|
3031
|
-
return await promises_namespaceObject.realpath(candidatePath);
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
catch (err) {
|
|
3035
|
-
if (!path_isNotFoundPathError(err)) {
|
|
3036
|
-
throw err;
|
|
3037
|
-
}
|
|
3038
|
-
}
|
|
3039
|
-
}
|
|
3040
|
-
return null;
|
|
3041
|
-
}
|
|
3042
3195
|
async function openWritableFileInRoot(root, params) {
|
|
3043
3196
|
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
|
|
3197
|
+
await assertMutationNotDenied(resolved, params.denyMutations);
|
|
3044
3198
|
try {
|
|
3045
3199
|
await assertNoPathAliasEscape({
|
|
3046
3200
|
absolutePath: resolved,
|
|
@@ -3167,6 +3321,7 @@ async function appendFileInRoot(root, params) {
|
|
|
3167
3321
|
relativePath: params.relativePath,
|
|
3168
3322
|
mkdir: params.mkdir,
|
|
3169
3323
|
mode: params.mode,
|
|
3324
|
+
denyMutations: params.denyMutations,
|
|
3170
3325
|
truncateExisting: false,
|
|
3171
3326
|
append: true,
|
|
3172
3327
|
});
|
|
@@ -3194,8 +3349,8 @@ async function appendFileInRoot(root, params) {
|
|
|
3194
3349
|
await target.handle.close().catch(() => { });
|
|
3195
3350
|
}
|
|
3196
3351
|
}
|
|
3197
|
-
async function removePathInRoot(root,
|
|
3198
|
-
const resolved = await resolvePinnedRemovePathInRoot(root, relativePath);
|
|
3352
|
+
async function removePathInRoot(root, params) {
|
|
3353
|
+
const resolved = await resolvePinnedRemovePathInRoot(root, params.relativePath, params.denyMutations);
|
|
3199
3354
|
if (process.platform === "win32") {
|
|
3200
3355
|
await removePathFallback(resolved);
|
|
3201
3356
|
return;
|
|
@@ -3243,7 +3398,7 @@ async function writeFileInRoot(root, params) {
|
|
|
3243
3398
|
});
|
|
3244
3399
|
return;
|
|
3245
3400
|
}
|
|
3246
|
-
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode);
|
|
3401
|
+
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
|
|
3247
3402
|
await serializePathWrite(pinned.targetPath, async () => {
|
|
3248
3403
|
let identity;
|
|
3249
3404
|
try {
|
|
@@ -3299,7 +3454,7 @@ async function copyFileInRoot(root, params) {
|
|
|
3299
3454
|
});
|
|
3300
3455
|
return;
|
|
3301
3456
|
}
|
|
3302
|
-
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode);
|
|
3457
|
+
const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
|
|
3303
3458
|
await serializePathWrite(pinned.targetPath, async () => {
|
|
3304
3459
|
let identity;
|
|
3305
3460
|
try {
|
|
@@ -3343,8 +3498,9 @@ async function copyFileInRoot(root, params) {
|
|
|
3343
3498
|
await source.handle.close().catch(() => { });
|
|
3344
3499
|
}
|
|
3345
3500
|
}
|
|
3346
|
-
async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode) {
|
|
3501
|
+
async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
|
|
3347
3502
|
const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath);
|
|
3503
|
+
await assertMutationNotDenied(resolved, denyMutations);
|
|
3348
3504
|
try {
|
|
3349
3505
|
await assertNoPathAliasEscape({
|
|
3350
3506
|
absolutePath: resolved,
|
|
@@ -3401,12 +3557,16 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode)
|
|
|
3401
3557
|
async function resolvePinnedPathInRoot(root, params) {
|
|
3402
3558
|
return await resolvePinnedOperationPathInRoot(root, {
|
|
3403
3559
|
allowRoot: params.allowRoot,
|
|
3560
|
+
denyMutations: params.denyMutations,
|
|
3561
|
+
protectDenyMutationAncestors: false,
|
|
3404
3562
|
relativePath: params.relativePath,
|
|
3405
3563
|
policy: PATH_ALIAS_POLICIES.strict,
|
|
3406
3564
|
});
|
|
3407
3565
|
}
|
|
3408
|
-
async function resolvePinnedRemovePathInRoot(root, relativePath) {
|
|
3566
|
+
async function resolvePinnedRemovePathInRoot(root, relativePath, denyMutations) {
|
|
3409
3567
|
return await resolvePinnedOperationPathInRoot(root, {
|
|
3568
|
+
denyMutations,
|
|
3569
|
+
protectDenyMutationAncestors: true,
|
|
3410
3570
|
relativePath,
|
|
3411
3571
|
policy: PATH_ALIAS_POLICIES.unlinkTarget,
|
|
3412
3572
|
});
|
|
@@ -3418,6 +3578,7 @@ async function resolvePinnedOperationPathInRoot(root, params) {
|
|
|
3418
3578
|
});
|
|
3419
3579
|
const relativeResolved = external_node_path_namespaceObject.relative(resolved.rootReal, resolved.canonicalPath);
|
|
3420
3580
|
if ((relativeResolved === "" || relativeResolved === ".") && params.allowRoot === true) {
|
|
3581
|
+
await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations);
|
|
3421
3582
|
return { rootReal: resolved.rootReal, resolved: resolved.canonicalPath, relativePosix: "" };
|
|
3422
3583
|
}
|
|
3423
3584
|
const firstSegment = relativeResolved.split(external_node_path_namespaceObject.sep)[0];
|
|
@@ -3431,6 +3592,9 @@ async function resolvePinnedOperationPathInRoot(root, params) {
|
|
|
3431
3592
|
if (!path_isPathInside(resolved.rootWithSep, resolved.canonicalPath)) {
|
|
3432
3593
|
throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
|
|
3433
3594
|
}
|
|
3595
|
+
await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations, {
|
|
3596
|
+
protectAncestors: params.protectDenyMutationAncestors,
|
|
3597
|
+
});
|
|
3434
3598
|
return { rootReal: resolved.rootReal, resolved: resolved.canonicalPath, relativePosix };
|
|
3435
3599
|
}
|
|
3436
3600
|
async function resolvePinnedRootPathInRoot(root, params) {
|
|
@@ -3508,13 +3672,21 @@ async function listPathFallback(root, relativePath, withFileTypes) {
|
|
|
3508
3672
|
throw error;
|
|
3509
3673
|
}
|
|
3510
3674
|
}
|
|
3675
|
+
async function assertMoveMutationAllowed(root, params) {
|
|
3676
|
+
const source = await resolvePathInRoot(root, params.fromRelative);
|
|
3677
|
+
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
3678
|
+
const target = await resolvePathInRoot(root, params.toRelative);
|
|
3679
|
+
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
3680
|
+
}
|
|
3511
3681
|
async function movePathFallback(root, params) {
|
|
3512
3682
|
const source = await resolvePathInRoot(root, params.fromRelative);
|
|
3683
|
+
await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
|
|
3513
3684
|
await resolvePinnedRootPathInRoot(root, {
|
|
3514
3685
|
relativePath: params.fromRelative,
|
|
3515
3686
|
policy: PATH_ALIAS_POLICIES.strict,
|
|
3516
3687
|
});
|
|
3517
3688
|
const target = await resolvePathInRoot(root, params.toRelative);
|
|
3689
|
+
await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
|
|
3518
3690
|
await resolvePinnedRootPathInRoot(root, {
|
|
3519
3691
|
relativePath: params.toRelative,
|
|
3520
3692
|
policy: PATH_ALIAS_POLICIES.unlinkTarget,
|
|
@@ -3598,6 +3770,7 @@ async function writeFileFallback(root, params) {
|
|
|
3598
3770
|
relativePath: params.relativePath,
|
|
3599
3771
|
mkdir: params.mkdir,
|
|
3600
3772
|
mode: params.mode,
|
|
3773
|
+
denyMutations: params.denyMutations,
|
|
3601
3774
|
truncateExisting: false,
|
|
3602
3775
|
});
|
|
3603
3776
|
const destinationPath = target.realPath;
|
|
@@ -3643,6 +3816,7 @@ async function writeFileFallback(root, params) {
|
|
|
3643
3816
|
}
|
|
3644
3817
|
async function writeMissingFileFallback(root, params) {
|
|
3645
3818
|
const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath);
|
|
3819
|
+
await assertMutationNotDenied(resolved, params.denyMutations);
|
|
3646
3820
|
try {
|
|
3647
3821
|
await assertNoPathAliasEscape({
|
|
3648
3822
|
absolutePath: resolved,
|
|
@@ -3716,6 +3890,7 @@ async function copyFileFallback(root, params, source) {
|
|
|
3716
3890
|
relativePath: params.relativePath,
|
|
3717
3891
|
mkdir: params.mkdir,
|
|
3718
3892
|
mode: params.mode,
|
|
3893
|
+
denyMutations: params.denyMutations,
|
|
3719
3894
|
truncateExisting: false,
|
|
3720
3895
|
});
|
|
3721
3896
|
const destinationPath = target.realPath;
|
|
@@ -12347,7 +12522,7 @@ async function emitScriptEndEvent(options, deps) {
|
|
|
12347
12522
|
const capturedEnv = captureEnv(options.env);
|
|
12348
12523
|
const tags = captureTags(options.env);
|
|
12349
12524
|
const event = {
|
|
12350
|
-
v: SCHEMA_VERSION,
|
|
12525
|
+
v: (/* inlined export .SCHEMA_VERSION */1),
|
|
12351
12526
|
session_id: sessionId,
|
|
12352
12527
|
event: "script_end",
|
|
12353
12528
|
command: options.command,
|
|
@@ -12376,7 +12551,7 @@ async function emitShimErrorEvent(options, deps) {
|
|
|
12376
12551
|
const capturedEnv = captureEnv(options.env);
|
|
12377
12552
|
const tags = captureTags(options.env);
|
|
12378
12553
|
const event = {
|
|
12379
|
-
v: SCHEMA_VERSION,
|
|
12554
|
+
v: (/* inlined export .SCHEMA_VERSION */1),
|
|
12380
12555
|
session_id: sessionId,
|
|
12381
12556
|
event: "shim_error",
|
|
12382
12557
|
command: options.command,
|
|
@@ -12397,7 +12572,7 @@ async function emitPolicyDecisionEvent(options, deps) {
|
|
|
12397
12572
|
const capturedEnv = captureEnv(options.env);
|
|
12398
12573
|
const tags = captureTags(options.env);
|
|
12399
12574
|
const event = {
|
|
12400
|
-
v: SCHEMA_VERSION,
|
|
12575
|
+
v: (/* inlined export .SCHEMA_VERSION */1),
|
|
12401
12576
|
session_id: sessionId,
|
|
12402
12577
|
event: "policy_decision",
|
|
12403
12578
|
command: options.command,
|
|
@@ -12420,7 +12595,7 @@ async function emitToolUseEvent(options, deps) {
|
|
|
12420
12595
|
const capturedEnv = captureEnv(options.env);
|
|
12421
12596
|
const tags = captureTags(options.env);
|
|
12422
12597
|
const event = {
|
|
12423
|
-
v: SCHEMA_VERSION,
|
|
12598
|
+
v: (/* inlined export .SCHEMA_VERSION */1),
|
|
12424
12599
|
session_id: sessionId,
|
|
12425
12600
|
event: "tool_use",
|
|
12426
12601
|
tool_name: options.tool_name,
|
|
@@ -14877,4 +15052,4 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
|
|
|
14877
15052
|
// module factories are used so entry inlining is disabled
|
|
14878
15053
|
// startup
|
|
14879
15054
|
// Load entry module and return exports
|
|
14880
|
-
var __webpack_exports__ = __webpack_require__(
|
|
15055
|
+
var __webpack_exports__ = __webpack_require__(341);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lousy-agents/agent-shell",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.15.0",
|
|
4
4
|
"description": "A flight recorder for npm script execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"build": "rspack build --config rspack.config.ts && chmod +x dist/index.js"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@openclaw/fs-safe": "0.
|
|
39
|
+
"@openclaw/fs-safe": "0.3.0",
|
|
40
40
|
"zod": "4.4.3"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|