@lousy-agents/cli 5.14.9 → 5.14.10
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 +106 -87
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3210,6 +3210,9 @@ function path_isPathInside(root, target) {
|
|
|
3210
3210
|
const firstSegment = relative.split(external_node_path_.posix.sep)[0];
|
|
3211
3211
|
return relative === "" || (firstSegment !== ".." && !external_node_path_.isAbsolute(relative));
|
|
3212
3212
|
}
|
|
3213
|
+
function isPathRelativeEscape(relativePath) {
|
|
3214
|
+
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
|
|
3215
|
+
}
|
|
3213
3216
|
function resolveSafeBaseDir(rootDir) {
|
|
3214
3217
|
const resolved = path.resolve(rootDir);
|
|
3215
3218
|
return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
|
|
@@ -3358,17 +3361,17 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
|
|
|
3358
3361
|
|
|
3359
3362
|
|
|
3360
3363
|
|
|
3364
|
+
|
|
3361
3365
|
function isSameOrChildPath(candidate, parent) {
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
function isPathEscape(relativePath) {
|
|
3365
|
-
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
|
|
3366
|
+
const parentPrefix = parent.endsWith(external_node_path_.sep) ? parent : `${parent}${external_node_path_.sep}`;
|
|
3367
|
+
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
3366
3368
|
}
|
|
3367
3369
|
async function mkdirPathComponentsWithGuards(params) {
|
|
3368
3370
|
const root = external_node_path_.resolve(params.rootReal);
|
|
3371
|
+
const rootCanonical = external_node_path_.resolve(await promises_.realpath(root));
|
|
3369
3372
|
const target = external_node_path_.resolve(params.targetPath);
|
|
3370
3373
|
const relative = external_node_path_.relative(root, target);
|
|
3371
|
-
if (
|
|
3374
|
+
if (isPathRelativeEscape(relative)) {
|
|
3372
3375
|
throw new errors_FsSafeError("outside-workspace", "directory is outside workspace root");
|
|
3373
3376
|
}
|
|
3374
3377
|
let current = root;
|
|
@@ -3391,7 +3394,7 @@ async function mkdirPathComponentsWithGuards(params) {
|
|
|
3391
3394
|
}
|
|
3392
3395
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
3393
3396
|
// segment at a time and realpath-check each segment before descending.
|
|
3394
|
-
if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)),
|
|
3397
|
+
if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)), rootCanonical)) {
|
|
3395
3398
|
throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
3396
3399
|
}
|
|
3397
3400
|
await directory_guard_createAsyncDirectoryGuard(next);
|
|
@@ -3526,26 +3529,20 @@ var external_node_child_process_ = __webpack_require__(1421);
|
|
|
3526
3529
|
|
|
3527
3530
|
|
|
3528
3531
|
const PINNED_PYTHON_WORKER_SOURCE = String.raw `
|
|
3529
|
-
import base64
|
|
3530
|
-
import errno
|
|
3531
|
-
import json
|
|
3532
|
-
import os
|
|
3533
|
-
import secrets
|
|
3534
|
-
import stat
|
|
3535
|
-
import sys
|
|
3536
|
-
|
|
3532
|
+
import base64, errno, json, os, secrets, stat, sys
|
|
3537
3533
|
DIR_FLAGS = os.O_RDONLY
|
|
3538
3534
|
if hasattr(os, "O_DIRECTORY"):
|
|
3539
3535
|
DIR_FLAGS |= os.O_DIRECTORY
|
|
3540
3536
|
if hasattr(os, "O_NOFOLLOW"):
|
|
3541
3537
|
DIR_FLAGS |= os.O_NOFOLLOW
|
|
3542
3538
|
READ_FLAGS = os.O_RDONLY
|
|
3539
|
+
if hasattr(os, "O_NONBLOCK"):
|
|
3540
|
+
READ_FLAGS |= os.O_NONBLOCK
|
|
3543
3541
|
if hasattr(os, "O_NOFOLLOW"):
|
|
3544
3542
|
READ_FLAGS |= os.O_NOFOLLOW
|
|
3545
3543
|
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
3546
3544
|
if hasattr(os, "O_NOFOLLOW"):
|
|
3547
3545
|
WRITE_FLAGS |= os.O_NOFOLLOW
|
|
3548
|
-
|
|
3549
3546
|
def split_relative(value):
|
|
3550
3547
|
if value in ("", "."):
|
|
3551
3548
|
return []
|
|
@@ -3558,10 +3555,8 @@ def split_relative(value):
|
|
|
3558
3555
|
if part == "..":
|
|
3559
3556
|
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
3560
3557
|
return parts
|
|
3561
|
-
|
|
3562
3558
|
def open_dir(path_value, dir_fd=None):
|
|
3563
3559
|
return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
|
|
3564
|
-
|
|
3565
3560
|
def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
3566
3561
|
current_fd = os.dup(root_fd)
|
|
3567
3562
|
try:
|
|
@@ -3579,14 +3574,12 @@ def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
|
3579
3574
|
except Exception:
|
|
3580
3575
|
os.close(current_fd)
|
|
3581
3576
|
raise
|
|
3582
|
-
|
|
3583
3577
|
def parent_and_basename(root_fd, relative):
|
|
3584
3578
|
segments = split_relative(relative)
|
|
3585
3579
|
if not segments:
|
|
3586
3580
|
raise OSError(errno.EPERM, "operation requires a non-root path")
|
|
3587
3581
|
parent_fd = walk_dir(root_fd, segments[:-1])
|
|
3588
3582
|
return parent_fd, segments[-1]
|
|
3589
|
-
|
|
3590
3583
|
def encode_stat(st):
|
|
3591
3584
|
mode = st.st_mode
|
|
3592
3585
|
return {
|
|
@@ -3602,14 +3595,12 @@ def encode_stat(st):
|
|
|
3602
3595
|
"size": st.st_size,
|
|
3603
3596
|
"uid": st.st_uid,
|
|
3604
3597
|
}
|
|
3605
|
-
|
|
3606
3598
|
def reject_unsafe_endpoint(st):
|
|
3607
3599
|
mode = st.st_mode
|
|
3608
3600
|
if stat.S_ISLNK(mode):
|
|
3609
3601
|
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
3610
3602
|
if stat.S_ISREG(mode) and st.st_nlink > 1:
|
|
3611
3603
|
raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
|
|
3612
|
-
|
|
3613
3604
|
def copy_bytes(source_fd, dest_fd):
|
|
3614
3605
|
while True:
|
|
3615
3606
|
chunk = os.read(source_fd, 65536)
|
|
@@ -3621,7 +3612,6 @@ def copy_bytes(source_fd, dest_fd):
|
|
|
3621
3612
|
if written <= 0:
|
|
3622
3613
|
raise OSError(errno.EIO, "short write")
|
|
3623
3614
|
view = view[written:]
|
|
3624
|
-
|
|
3625
3615
|
def write_all(fd, data):
|
|
3626
3616
|
view = memoryview(data)
|
|
3627
3617
|
while view:
|
|
@@ -3629,11 +3619,9 @@ def write_all(fd, data):
|
|
|
3629
3619
|
if written <= 0:
|
|
3630
3620
|
raise OSError(errno.EIO, "short write")
|
|
3631
3621
|
view = view[written:]
|
|
3632
|
-
|
|
3633
3622
|
def link_unsupported(exc):
|
|
3634
3623
|
unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
|
|
3635
3624
|
return getattr(exc, "errno", None) in unsupported
|
|
3636
|
-
|
|
3637
3625
|
def link_no_replace(name, new_name, source_fd, target_fd):
|
|
3638
3626
|
linked = False
|
|
3639
3627
|
try:
|
|
@@ -3648,11 +3636,9 @@ def link_no_replace(name, new_name, source_fd, target_fd):
|
|
|
3648
3636
|
os.fsync(source_fd)
|
|
3649
3637
|
if source_fd != target_fd:
|
|
3650
3638
|
os.fsync(target_fd)
|
|
3651
|
-
|
|
3652
3639
|
def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
|
|
3653
3640
|
source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
|
|
3654
|
-
dest_fd = None
|
|
3655
|
-
success = False
|
|
3641
|
+
dest_fd = None; success = False; dest_stat = None
|
|
3656
3642
|
try:
|
|
3657
3643
|
if expected is not None:
|
|
3658
3644
|
source_stat = os.fstat(source_fd)
|
|
@@ -3661,6 +3647,7 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
|
|
|
3661
3647
|
dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
|
|
3662
3648
|
copy_bytes(source_fd, dest_fd)
|
|
3663
3649
|
os.fsync(dest_fd)
|
|
3650
|
+
dest_stat = os.fstat(dest_fd)
|
|
3664
3651
|
success = True
|
|
3665
3652
|
finally:
|
|
3666
3653
|
os.close(source_fd)
|
|
@@ -3676,19 +3663,40 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
|
|
|
3676
3663
|
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
3677
3664
|
except FileNotFoundError: pass
|
|
3678
3665
|
raise
|
|
3679
|
-
|
|
3680
|
-
def
|
|
3666
|
+
return dest_stat
|
|
3667
|
+
def same_identity(left, right):
|
|
3668
|
+
return left.st_dev == right.st_dev and left.st_ino == right.st_ino
|
|
3669
|
+
def verify_temp_name(parent_fd, temp_name, expected_stat):
|
|
3670
|
+
current_stat = os.lstat(temp_name, dir_fd=parent_fd)
|
|
3671
|
+
if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
|
|
3672
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
3673
|
+
def verify_committed_temp(parent_fd, basename, expected_stat):
|
|
3674
|
+
final_stat = os.lstat(basename, dir_fd=parent_fd)
|
|
3675
|
+
if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
|
|
3676
|
+
return final_stat
|
|
3677
|
+
try: os.unlink(basename, dir_fd=parent_fd)
|
|
3678
|
+
except FileNotFoundError: pass
|
|
3679
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
3680
|
+
def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
|
|
3681
|
+
verify_temp_name(parent_fd, temp_name, expected_stat)
|
|
3681
3682
|
if overwrite:
|
|
3682
3683
|
os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
|
|
3684
|
+
return verify_committed_temp(parent_fd, basename, expected_stat)
|
|
3683
3685
|
else:
|
|
3684
3686
|
try:
|
|
3685
3687
|
os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
|
|
3688
|
+
final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
|
|
3686
3689
|
os.unlink(temp_name, dir_fd=parent_fd)
|
|
3690
|
+
return final_stat
|
|
3687
3691
|
except OSError as exc:
|
|
3688
3692
|
if not link_unsupported(exc):
|
|
3689
3693
|
raise
|
|
3690
|
-
copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode,
|
|
3691
|
-
|
|
3694
|
+
return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
|
|
3695
|
+
def assert_expected_root(root_fd, payload):
|
|
3696
|
+
if "rootDev" in payload or "rootIno" in payload:
|
|
3697
|
+
root_stat = os.fstat(root_fd)
|
|
3698
|
+
if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
|
|
3699
|
+
raise RuntimeError("fs-safe-root-mismatch")
|
|
3692
3700
|
def stat_path(root_fd, payload):
|
|
3693
3701
|
relative = payload.get("relativePath", "")
|
|
3694
3702
|
segments = split_relative(relative)
|
|
@@ -3702,7 +3710,6 @@ def stat_path(root_fd, payload):
|
|
|
3702
3710
|
return encode_stat(st)
|
|
3703
3711
|
finally:
|
|
3704
3712
|
os.close(parent_fd)
|
|
3705
|
-
|
|
3706
3713
|
def readdir_path(root_fd, payload):
|
|
3707
3714
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
|
|
3708
3715
|
try:
|
|
@@ -3718,12 +3725,9 @@ def readdir_path(root_fd, payload):
|
|
|
3718
3725
|
return entries
|
|
3719
3726
|
finally:
|
|
3720
3727
|
os.close(dir_fd)
|
|
3721
|
-
|
|
3722
3728
|
def mkdirp_path(root_fd, payload):
|
|
3723
3729
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
|
|
3724
|
-
os.close(dir_fd)
|
|
3725
|
-
return None
|
|
3726
|
-
|
|
3730
|
+
os.close(dir_fd); return None
|
|
3727
3731
|
def remove_tree(parent_fd, basename):
|
|
3728
3732
|
st = os.lstat(basename, dir_fd=parent_fd)
|
|
3729
3733
|
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
@@ -3736,7 +3740,6 @@ def remove_tree(parent_fd, basename):
|
|
|
3736
3740
|
os.rmdir(basename, dir_fd=parent_fd)
|
|
3737
3741
|
else:
|
|
3738
3742
|
os.unlink(basename, dir_fd=parent_fd)
|
|
3739
|
-
|
|
3740
3743
|
def remove_path(root_fd, payload):
|
|
3741
3744
|
parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
|
|
3742
3745
|
try:
|
|
@@ -3819,14 +3822,15 @@ def write_path(root_fd, payload):
|
|
|
3819
3822
|
except FileNotFoundError:
|
|
3820
3823
|
pass
|
|
3821
3824
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
3825
|
+
os.fchmod(temp_fd, mode)
|
|
3822
3826
|
write_all(temp_fd, data)
|
|
3823
3827
|
os.fsync(temp_fd)
|
|
3828
|
+
temp_stat = os.fstat(temp_fd)
|
|
3824
3829
|
os.close(temp_fd)
|
|
3825
3830
|
temp_fd = None
|
|
3826
|
-
commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
|
|
3831
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
3827
3832
|
temp_name = None
|
|
3828
3833
|
os.fsync(parent_fd)
|
|
3829
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
3830
3834
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
3831
3835
|
finally:
|
|
3832
3836
|
if temp_fd is not None:
|
|
@@ -3857,6 +3861,7 @@ def copy_path(root_fd, payload):
|
|
|
3857
3861
|
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
|
|
3858
3862
|
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
|
|
3859
3863
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
3864
|
+
os.fchmod(temp_fd, mode)
|
|
3860
3865
|
written_bytes = 0
|
|
3861
3866
|
while True:
|
|
3862
3867
|
chunk = os.read(source_fd, 65536)
|
|
@@ -3872,12 +3877,12 @@ def copy_path(root_fd, payload):
|
|
|
3872
3877
|
raise OSError(errno.EIO, "short write")
|
|
3873
3878
|
view = view[written:]
|
|
3874
3879
|
os.fsync(temp_fd)
|
|
3880
|
+
temp_stat = os.fstat(temp_fd)
|
|
3875
3881
|
os.close(temp_fd)
|
|
3876
3882
|
temp_fd = None
|
|
3877
|
-
commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
|
|
3883
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
3878
3884
|
temp_name = None
|
|
3879
3885
|
os.fsync(parent_fd)
|
|
3880
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
3881
3886
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
3882
3887
|
finally:
|
|
3883
3888
|
os.close(source_fd)
|
|
@@ -3894,6 +3899,7 @@ def copy_path(root_fd, payload):
|
|
|
3894
3899
|
def run_operation(operation, root_path, payload):
|
|
3895
3900
|
root_fd = open_dir(root_path)
|
|
3896
3901
|
try:
|
|
3902
|
+
assert_expected_root(root_fd, payload)
|
|
3897
3903
|
if operation == "stat":
|
|
3898
3904
|
return stat_path(root_fd, payload)
|
|
3899
3905
|
if operation == "readdir":
|
|
@@ -3998,6 +4004,12 @@ function mapWorkerError(response) {
|
|
|
3998
4004
|
if (message.includes("fs-safe-source-mismatch")) {
|
|
3999
4005
|
return new errors_FsSafeError("path-mismatch", "source path changed during copy");
|
|
4000
4006
|
}
|
|
4007
|
+
if (message.includes("fs-safe-temp-mismatch")) {
|
|
4008
|
+
return new errors_FsSafeError("path-mismatch", "temp path changed during write");
|
|
4009
|
+
}
|
|
4010
|
+
if (message.includes("fs-safe-root-mismatch")) {
|
|
4011
|
+
return new errors_FsSafeError("path-mismatch", "root path changed during operation");
|
|
4012
|
+
}
|
|
4001
4013
|
if (message.includes("fs-safe-directory-noreplace-unsupported")) {
|
|
4002
4014
|
return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
4003
4015
|
}
|
|
@@ -4178,9 +4190,7 @@ function validatePinnedOperationPayload(payload) {
|
|
|
4178
4190
|
}
|
|
4179
4191
|
}
|
|
4180
4192
|
function isPinnedHelperUnavailable(error) {
|
|
4181
|
-
return error instanceof Error &&
|
|
4182
|
-
"code" in error &&
|
|
4183
|
-
error.code === "helper-unavailable";
|
|
4193
|
+
return error instanceof Error && "code" in error && error.code === "helper-unavailable";
|
|
4184
4194
|
}
|
|
4185
4195
|
function validatePinnedRelativePath(relativePath) {
|
|
4186
4196
|
if (relativePath.length === 0 || relativePath === ".") {
|
|
@@ -4278,29 +4288,21 @@ function assertWithinMaxBytes(bytes, maxBytes) {
|
|
|
4278
4288
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
4279
4289
|
}
|
|
4280
4290
|
}
|
|
4281
|
-
function
|
|
4282
|
-
if (maxBytes === undefined) {
|
|
4283
|
-
return undefined;
|
|
4284
|
-
}
|
|
4291
|
+
async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
4285
4292
|
let bytes = 0;
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4293
|
+
for await (const chunk of stream) {
|
|
4294
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
4295
|
+
bytes += buffer.byteLength;
|
|
4296
|
+
assertWithinMaxBytes(bytes, maxBytes);
|
|
4297
|
+
let offset = 0;
|
|
4298
|
+
while (offset < buffer.byteLength) {
|
|
4299
|
+
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
4300
|
+
if (bytesWritten <= 0) {
|
|
4301
|
+
throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
|
|
4292
4302
|
}
|
|
4293
|
-
|
|
4294
|
-
}
|
|
4295
|
-
});
|
|
4296
|
-
}
|
|
4297
|
-
async function pipelineWithMaxBytes(stream, destination, maxBytes) {
|
|
4298
|
-
const limiter = pinned_write_createMaxBytesTransform(maxBytes);
|
|
4299
|
-
if (limiter) {
|
|
4300
|
-
await (0,external_node_stream_promises_.pipeline)(stream, limiter, destination);
|
|
4301
|
-
return;
|
|
4303
|
+
offset += bytesWritten;
|
|
4304
|
+
}
|
|
4302
4305
|
}
|
|
4303
|
-
await (0,external_node_stream_promises_.pipeline)(stream, destination);
|
|
4304
4306
|
}
|
|
4305
4307
|
async function inputToBase64(input, maxBytes) {
|
|
4306
4308
|
if (input.kind === "buffer") {
|
|
@@ -4325,7 +4327,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
4325
4327
|
relativeParentPath: params.relativeParentPath,
|
|
4326
4328
|
});
|
|
4327
4329
|
if (getFsSafePythonConfig().mode === "off") {
|
|
4328
|
-
return await
|
|
4330
|
+
return await runPinnedWriteFallbackOrThrow(params);
|
|
4329
4331
|
}
|
|
4330
4332
|
if (params.input.kind === "stream") {
|
|
4331
4333
|
try {
|
|
@@ -4333,7 +4335,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
4333
4335
|
}
|
|
4334
4336
|
catch (error) {
|
|
4335
4337
|
if (canFallbackFromPythonError(error)) {
|
|
4336
|
-
return await
|
|
4338
|
+
return await runPinnedWriteFallbackOrThrow(params, error);
|
|
4337
4339
|
}
|
|
4338
4340
|
throw error;
|
|
4339
4341
|
}
|
|
@@ -4346,6 +4348,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
4346
4348
|
mode: params.mode || 0o600,
|
|
4347
4349
|
overwrite: params.overwrite !== false,
|
|
4348
4350
|
relativeParentPath: params.relativeParentPath,
|
|
4351
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
4349
4352
|
};
|
|
4350
4353
|
try {
|
|
4351
4354
|
return await runPinnedPythonOperation({
|
|
@@ -4356,7 +4359,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
4356
4359
|
}
|
|
4357
4360
|
catch (error) {
|
|
4358
4361
|
if (canFallbackFromPythonError(error)) {
|
|
4359
|
-
return await
|
|
4362
|
+
return await runPinnedWriteFallbackOrThrow(params, error);
|
|
4360
4363
|
}
|
|
4361
4364
|
throw error;
|
|
4362
4365
|
}
|
|
@@ -4376,22 +4379,29 @@ async function runPinnedCopyHelper(params) {
|
|
|
4376
4379
|
mode: params.mode || 0o600,
|
|
4377
4380
|
overwrite: params.overwrite !== false,
|
|
4378
4381
|
relativeParentPath: params.relativeParentPath,
|
|
4382
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
4379
4383
|
sourceDev: params.sourceIdentity.dev,
|
|
4380
4384
|
sourceIno: params.sourceIdentity.ino,
|
|
4381
4385
|
sourcePath: params.sourcePath,
|
|
4382
4386
|
},
|
|
4383
4387
|
});
|
|
4384
4388
|
}
|
|
4389
|
+
async function runPinnedWriteFallbackOrThrow(params, cause) {
|
|
4390
|
+
if (process.platform !== "win32") {
|
|
4391
|
+
throw new errors_FsSafeError("helper-unavailable", "Python helper is required for pinned writes on this platform", { cause });
|
|
4392
|
+
}
|
|
4393
|
+
return await runPinnedWriteFallback(params);
|
|
4394
|
+
}
|
|
4385
4395
|
async function runPinnedWriteFallback(params) {
|
|
4386
4396
|
const parentPath = params.relativeParentPath
|
|
4387
4397
|
? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
4388
4398
|
: params.rootPath;
|
|
4389
|
-
const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
4390
4399
|
if (params.mkdir) {
|
|
4391
|
-
await
|
|
4392
|
-
await promises_.mkdir(parentPath, { recursive: true });
|
|
4393
|
-
});
|
|
4400
|
+
await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
|
|
4394
4401
|
}
|
|
4402
|
+
const parentGuard = params.mkdir
|
|
4403
|
+
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
4404
|
+
: await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
4395
4405
|
const targetPath = external_node_path_.join(parentPath, params.basename);
|
|
4396
4406
|
if (params.overwrite === false) {
|
|
4397
4407
|
let handle = await withAsyncDirectoryGuards([parentGuard], async () => await promises_.open(targetPath, external_node_fs_.constants.O_WRONLY | external_node_fs_.constants.O_CREAT | external_node_fs_.constants.O_EXCL, params.mode), {
|
|
@@ -4413,7 +4423,7 @@ async function runPinnedWriteFallback(params) {
|
|
|
4413
4423
|
}
|
|
4414
4424
|
}
|
|
4415
4425
|
else {
|
|
4416
|
-
await
|
|
4426
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
4417
4427
|
}
|
|
4418
4428
|
const stat = await handle.stat();
|
|
4419
4429
|
created = false;
|
|
@@ -4434,7 +4444,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
4434
4444
|
? external_node_fs_.constants.O_NOFOLLOW
|
|
4435
4445
|
: 0);
|
|
4436
4446
|
let handle;
|
|
4437
|
-
let
|
|
4447
|
+
let tempStat;
|
|
4448
|
+
let targetStat;
|
|
4449
|
+
let renamed = false;
|
|
4438
4450
|
try {
|
|
4439
4451
|
handle = await promises_.open(tempPath, tempFlags, params.mode);
|
|
4440
4452
|
if (params.input.kind === "buffer") {
|
|
@@ -4447,29 +4459,36 @@ async function runPinnedWriteFallback(params) {
|
|
|
4447
4459
|
}
|
|
4448
4460
|
}
|
|
4449
4461
|
else {
|
|
4450
|
-
|
|
4451
|
-
writable.once("close", () => {
|
|
4452
|
-
handleClosedByStream = true;
|
|
4453
|
-
});
|
|
4454
|
-
await pipelineWithMaxBytes(params.input.stream, writable, params.maxBytes);
|
|
4462
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
4455
4463
|
}
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4464
|
+
tempStat = await handle.stat();
|
|
4465
|
+
const tempPathStat = await promises_.lstat(tempPath);
|
|
4466
|
+
if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
|
|
4467
|
+
throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
4459
4468
|
}
|
|
4469
|
+
const expectedTempStat = tempStat;
|
|
4470
|
+
await handle.close().catch(() => undefined);
|
|
4471
|
+
handle = undefined;
|
|
4460
4472
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
4461
4473
|
await promises_.rename(tempPath, targetPath);
|
|
4474
|
+
renamed = true;
|
|
4475
|
+
targetStat = await promises_.lstat(targetPath);
|
|
4476
|
+
if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
|
|
4477
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
4478
|
+
}
|
|
4462
4479
|
});
|
|
4463
4480
|
}
|
|
4464
4481
|
catch (error) {
|
|
4465
|
-
|
|
4466
|
-
|
|
4482
|
+
await handle?.close().catch(() => undefined);
|
|
4483
|
+
if (!renamed) {
|
|
4484
|
+
await promises_.rm(tempPath, { force: true }).catch(() => undefined);
|
|
4467
4485
|
}
|
|
4468
|
-
await promises_.rm(tempPath, { force: true }).catch(() => undefined);
|
|
4469
4486
|
throw error;
|
|
4470
4487
|
}
|
|
4471
|
-
|
|
4472
|
-
|
|
4488
|
+
if (!targetStat) {
|
|
4489
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target was not verified");
|
|
4490
|
+
}
|
|
4491
|
+
return { dev: targetStat.dev, ino: targetStat.ino };
|
|
4473
4492
|
}
|
|
4474
4493
|
|
|
4475
4494
|
// EXTERNAL MODULE: external "node:os"
|
|
@@ -5008,7 +5027,7 @@ function relativeInsideRoot(rootPath, targetPath) {
|
|
|
5008
5027
|
if (!relative || relative === ".") {
|
|
5009
5028
|
return "";
|
|
5010
5029
|
}
|
|
5011
|
-
if (
|
|
5030
|
+
if (isPathRelativeEscape(relative)) {
|
|
5012
5031
|
return "";
|
|
5013
5032
|
}
|
|
5014
5033
|
return relative;
|
package/package.json
CHANGED