@lousy-agents/lint 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
|
@@ -1031,6 +1031,9 @@ function path_isPathInside(root, target) {
|
|
|
1031
1031
|
const firstSegment = relative.split(external_node_path_.posix.sep)[0];
|
|
1032
1032
|
return relative === "" || (firstSegment !== ".." && !external_node_path_.isAbsolute(relative));
|
|
1033
1033
|
}
|
|
1034
|
+
function isPathRelativeEscape(relativePath) {
|
|
1035
|
+
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
|
|
1036
|
+
}
|
|
1034
1037
|
function resolveSafeBaseDir(rootDir) {
|
|
1035
1038
|
const resolved = path.resolve(rootDir);
|
|
1036
1039
|
return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
|
|
@@ -1179,17 +1182,17 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
|
|
|
1179
1182
|
|
|
1180
1183
|
|
|
1181
1184
|
|
|
1185
|
+
|
|
1182
1186
|
function isSameOrChildPath(candidate, parent) {
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
function isPathEscape(relativePath) {
|
|
1186
|
-
return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
|
|
1187
|
+
const parentPrefix = parent.endsWith(external_node_path_.sep) ? parent : `${parent}${external_node_path_.sep}`;
|
|
1188
|
+
return candidate === parent || candidate.startsWith(parentPrefix);
|
|
1187
1189
|
}
|
|
1188
1190
|
async function mkdirPathComponentsWithGuards(params) {
|
|
1189
1191
|
const root = external_node_path_.resolve(params.rootReal);
|
|
1192
|
+
const rootCanonical = external_node_path_.resolve(await promises_.realpath(root));
|
|
1190
1193
|
const target = external_node_path_.resolve(params.targetPath);
|
|
1191
1194
|
const relative = external_node_path_.relative(root, target);
|
|
1192
|
-
if (
|
|
1195
|
+
if (isPathRelativeEscape(relative)) {
|
|
1193
1196
|
throw new errors_FsSafeError("outside-workspace", "directory is outside workspace root");
|
|
1194
1197
|
}
|
|
1195
1198
|
let current = root;
|
|
@@ -1212,7 +1215,7 @@ async function mkdirPathComponentsWithGuards(params) {
|
|
|
1212
1215
|
}
|
|
1213
1216
|
// Node's recursive mkdir follows symlinks in missing components. Build one
|
|
1214
1217
|
// segment at a time and realpath-check each segment before descending.
|
|
1215
|
-
if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)),
|
|
1218
|
+
if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)), rootCanonical)) {
|
|
1216
1219
|
throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
|
|
1217
1220
|
}
|
|
1218
1221
|
await directory_guard_createAsyncDirectoryGuard(next);
|
|
@@ -1347,26 +1350,20 @@ var external_node_child_process_ = __webpack_require__(1421);
|
|
|
1347
1350
|
|
|
1348
1351
|
|
|
1349
1352
|
const PINNED_PYTHON_WORKER_SOURCE = String.raw `
|
|
1350
|
-
import base64
|
|
1351
|
-
import errno
|
|
1352
|
-
import json
|
|
1353
|
-
import os
|
|
1354
|
-
import secrets
|
|
1355
|
-
import stat
|
|
1356
|
-
import sys
|
|
1357
|
-
|
|
1353
|
+
import base64, errno, json, os, secrets, stat, sys
|
|
1358
1354
|
DIR_FLAGS = os.O_RDONLY
|
|
1359
1355
|
if hasattr(os, "O_DIRECTORY"):
|
|
1360
1356
|
DIR_FLAGS |= os.O_DIRECTORY
|
|
1361
1357
|
if hasattr(os, "O_NOFOLLOW"):
|
|
1362
1358
|
DIR_FLAGS |= os.O_NOFOLLOW
|
|
1363
1359
|
READ_FLAGS = os.O_RDONLY
|
|
1360
|
+
if hasattr(os, "O_NONBLOCK"):
|
|
1361
|
+
READ_FLAGS |= os.O_NONBLOCK
|
|
1364
1362
|
if hasattr(os, "O_NOFOLLOW"):
|
|
1365
1363
|
READ_FLAGS |= os.O_NOFOLLOW
|
|
1366
1364
|
WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
|
|
1367
1365
|
if hasattr(os, "O_NOFOLLOW"):
|
|
1368
1366
|
WRITE_FLAGS |= os.O_NOFOLLOW
|
|
1369
|
-
|
|
1370
1367
|
def split_relative(value):
|
|
1371
1368
|
if value in ("", "."):
|
|
1372
1369
|
return []
|
|
@@ -1379,10 +1376,8 @@ def split_relative(value):
|
|
|
1379
1376
|
if part == "..":
|
|
1380
1377
|
raise OSError(errno.EPERM, "path traversal is not allowed")
|
|
1381
1378
|
return parts
|
|
1382
|
-
|
|
1383
1379
|
def open_dir(path_value, dir_fd=None):
|
|
1384
1380
|
return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
|
|
1385
|
-
|
|
1386
1381
|
def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
1387
1382
|
current_fd = os.dup(root_fd)
|
|
1388
1383
|
try:
|
|
@@ -1400,14 +1395,12 @@ def walk_dir(root_fd, segments, mkdir_enabled=False):
|
|
|
1400
1395
|
except Exception:
|
|
1401
1396
|
os.close(current_fd)
|
|
1402
1397
|
raise
|
|
1403
|
-
|
|
1404
1398
|
def parent_and_basename(root_fd, relative):
|
|
1405
1399
|
segments = split_relative(relative)
|
|
1406
1400
|
if not segments:
|
|
1407
1401
|
raise OSError(errno.EPERM, "operation requires a non-root path")
|
|
1408
1402
|
parent_fd = walk_dir(root_fd, segments[:-1])
|
|
1409
1403
|
return parent_fd, segments[-1]
|
|
1410
|
-
|
|
1411
1404
|
def encode_stat(st):
|
|
1412
1405
|
mode = st.st_mode
|
|
1413
1406
|
return {
|
|
@@ -1423,14 +1416,12 @@ def encode_stat(st):
|
|
|
1423
1416
|
"size": st.st_size,
|
|
1424
1417
|
"uid": st.st_uid,
|
|
1425
1418
|
}
|
|
1426
|
-
|
|
1427
1419
|
def reject_unsafe_endpoint(st):
|
|
1428
1420
|
mode = st.st_mode
|
|
1429
1421
|
if stat.S_ISLNK(mode):
|
|
1430
1422
|
raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
|
|
1431
1423
|
if stat.S_ISREG(mode) and st.st_nlink > 1:
|
|
1432
1424
|
raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
|
|
1433
|
-
|
|
1434
1425
|
def copy_bytes(source_fd, dest_fd):
|
|
1435
1426
|
while True:
|
|
1436
1427
|
chunk = os.read(source_fd, 65536)
|
|
@@ -1442,7 +1433,6 @@ def copy_bytes(source_fd, dest_fd):
|
|
|
1442
1433
|
if written <= 0:
|
|
1443
1434
|
raise OSError(errno.EIO, "short write")
|
|
1444
1435
|
view = view[written:]
|
|
1445
|
-
|
|
1446
1436
|
def write_all(fd, data):
|
|
1447
1437
|
view = memoryview(data)
|
|
1448
1438
|
while view:
|
|
@@ -1450,11 +1440,9 @@ def write_all(fd, data):
|
|
|
1450
1440
|
if written <= 0:
|
|
1451
1441
|
raise OSError(errno.EIO, "short write")
|
|
1452
1442
|
view = view[written:]
|
|
1453
|
-
|
|
1454
1443
|
def link_unsupported(exc):
|
|
1455
1444
|
unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
|
|
1456
1445
|
return getattr(exc, "errno", None) in unsupported
|
|
1457
|
-
|
|
1458
1446
|
def link_no_replace(name, new_name, source_fd, target_fd):
|
|
1459
1447
|
linked = False
|
|
1460
1448
|
try:
|
|
@@ -1469,11 +1457,9 @@ def link_no_replace(name, new_name, source_fd, target_fd):
|
|
|
1469
1457
|
os.fsync(source_fd)
|
|
1470
1458
|
if source_fd != target_fd:
|
|
1471
1459
|
os.fsync(target_fd)
|
|
1472
|
-
|
|
1473
1460
|
def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
|
|
1474
1461
|
source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
|
|
1475
|
-
dest_fd = None
|
|
1476
|
-
success = False
|
|
1462
|
+
dest_fd = None; success = False; dest_stat = None
|
|
1477
1463
|
try:
|
|
1478
1464
|
if expected is not None:
|
|
1479
1465
|
source_stat = os.fstat(source_fd)
|
|
@@ -1482,6 +1468,7 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
|
|
|
1482
1468
|
dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
|
|
1483
1469
|
copy_bytes(source_fd, dest_fd)
|
|
1484
1470
|
os.fsync(dest_fd)
|
|
1471
|
+
dest_stat = os.fstat(dest_fd)
|
|
1485
1472
|
success = True
|
|
1486
1473
|
finally:
|
|
1487
1474
|
os.close(source_fd)
|
|
@@ -1497,19 +1484,40 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
|
|
|
1497
1484
|
try: os.unlink(basename, dir_fd=target_parent_fd)
|
|
1498
1485
|
except FileNotFoundError: pass
|
|
1499
1486
|
raise
|
|
1500
|
-
|
|
1501
|
-
def
|
|
1487
|
+
return dest_stat
|
|
1488
|
+
def same_identity(left, right):
|
|
1489
|
+
return left.st_dev == right.st_dev and left.st_ino == right.st_ino
|
|
1490
|
+
def verify_temp_name(parent_fd, temp_name, expected_stat):
|
|
1491
|
+
current_stat = os.lstat(temp_name, dir_fd=parent_fd)
|
|
1492
|
+
if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
|
|
1493
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
1494
|
+
def verify_committed_temp(parent_fd, basename, expected_stat):
|
|
1495
|
+
final_stat = os.lstat(basename, dir_fd=parent_fd)
|
|
1496
|
+
if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
|
|
1497
|
+
return final_stat
|
|
1498
|
+
try: os.unlink(basename, dir_fd=parent_fd)
|
|
1499
|
+
except FileNotFoundError: pass
|
|
1500
|
+
raise RuntimeError("fs-safe-temp-mismatch")
|
|
1501
|
+
def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
|
|
1502
|
+
verify_temp_name(parent_fd, temp_name, expected_stat)
|
|
1502
1503
|
if overwrite:
|
|
1503
1504
|
os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
|
|
1505
|
+
return verify_committed_temp(parent_fd, basename, expected_stat)
|
|
1504
1506
|
else:
|
|
1505
1507
|
try:
|
|
1506
1508
|
os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
|
|
1509
|
+
final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
|
|
1507
1510
|
os.unlink(temp_name, dir_fd=parent_fd)
|
|
1511
|
+
return final_stat
|
|
1508
1512
|
except OSError as exc:
|
|
1509
1513
|
if not link_unsupported(exc):
|
|
1510
1514
|
raise
|
|
1511
|
-
copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode,
|
|
1512
|
-
|
|
1515
|
+
return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
|
|
1516
|
+
def assert_expected_root(root_fd, payload):
|
|
1517
|
+
if "rootDev" in payload or "rootIno" in payload:
|
|
1518
|
+
root_stat = os.fstat(root_fd)
|
|
1519
|
+
if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
|
|
1520
|
+
raise RuntimeError("fs-safe-root-mismatch")
|
|
1513
1521
|
def stat_path(root_fd, payload):
|
|
1514
1522
|
relative = payload.get("relativePath", "")
|
|
1515
1523
|
segments = split_relative(relative)
|
|
@@ -1523,7 +1531,6 @@ def stat_path(root_fd, payload):
|
|
|
1523
1531
|
return encode_stat(st)
|
|
1524
1532
|
finally:
|
|
1525
1533
|
os.close(parent_fd)
|
|
1526
|
-
|
|
1527
1534
|
def readdir_path(root_fd, payload):
|
|
1528
1535
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
|
|
1529
1536
|
try:
|
|
@@ -1539,12 +1546,9 @@ def readdir_path(root_fd, payload):
|
|
|
1539
1546
|
return entries
|
|
1540
1547
|
finally:
|
|
1541
1548
|
os.close(dir_fd)
|
|
1542
|
-
|
|
1543
1549
|
def mkdirp_path(root_fd, payload):
|
|
1544
1550
|
dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
|
|
1545
|
-
os.close(dir_fd)
|
|
1546
|
-
return None
|
|
1547
|
-
|
|
1551
|
+
os.close(dir_fd); return None
|
|
1548
1552
|
def remove_tree(parent_fd, basename):
|
|
1549
1553
|
st = os.lstat(basename, dir_fd=parent_fd)
|
|
1550
1554
|
if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
|
|
@@ -1557,7 +1561,6 @@ def remove_tree(parent_fd, basename):
|
|
|
1557
1561
|
os.rmdir(basename, dir_fd=parent_fd)
|
|
1558
1562
|
else:
|
|
1559
1563
|
os.unlink(basename, dir_fd=parent_fd)
|
|
1560
|
-
|
|
1561
1564
|
def remove_path(root_fd, payload):
|
|
1562
1565
|
parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
|
|
1563
1566
|
try:
|
|
@@ -1640,14 +1643,15 @@ def write_path(root_fd, payload):
|
|
|
1640
1643
|
except FileNotFoundError:
|
|
1641
1644
|
pass
|
|
1642
1645
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
1646
|
+
os.fchmod(temp_fd, mode)
|
|
1643
1647
|
write_all(temp_fd, data)
|
|
1644
1648
|
os.fsync(temp_fd)
|
|
1649
|
+
temp_stat = os.fstat(temp_fd)
|
|
1645
1650
|
os.close(temp_fd)
|
|
1646
1651
|
temp_fd = None
|
|
1647
|
-
commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
|
|
1652
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
1648
1653
|
temp_name = None
|
|
1649
1654
|
os.fsync(parent_fd)
|
|
1650
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
1651
1655
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
1652
1656
|
finally:
|
|
1653
1657
|
if temp_fd is not None:
|
|
@@ -1678,6 +1682,7 @@ def copy_path(root_fd, payload):
|
|
|
1678
1682
|
raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
|
|
1679
1683
|
parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
|
|
1680
1684
|
temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
|
|
1685
|
+
os.fchmod(temp_fd, mode)
|
|
1681
1686
|
written_bytes = 0
|
|
1682
1687
|
while True:
|
|
1683
1688
|
chunk = os.read(source_fd, 65536)
|
|
@@ -1693,12 +1698,12 @@ def copy_path(root_fd, payload):
|
|
|
1693
1698
|
raise OSError(errno.EIO, "short write")
|
|
1694
1699
|
view = view[written:]
|
|
1695
1700
|
os.fsync(temp_fd)
|
|
1701
|
+
temp_stat = os.fstat(temp_fd)
|
|
1696
1702
|
os.close(temp_fd)
|
|
1697
1703
|
temp_fd = None
|
|
1698
|
-
commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
|
|
1704
|
+
result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
|
|
1699
1705
|
temp_name = None
|
|
1700
1706
|
os.fsync(parent_fd)
|
|
1701
|
-
result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
|
|
1702
1707
|
return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
|
|
1703
1708
|
finally:
|
|
1704
1709
|
os.close(source_fd)
|
|
@@ -1715,6 +1720,7 @@ def copy_path(root_fd, payload):
|
|
|
1715
1720
|
def run_operation(operation, root_path, payload):
|
|
1716
1721
|
root_fd = open_dir(root_path)
|
|
1717
1722
|
try:
|
|
1723
|
+
assert_expected_root(root_fd, payload)
|
|
1718
1724
|
if operation == "stat":
|
|
1719
1725
|
return stat_path(root_fd, payload)
|
|
1720
1726
|
if operation == "readdir":
|
|
@@ -1819,6 +1825,12 @@ function mapWorkerError(response) {
|
|
|
1819
1825
|
if (message.includes("fs-safe-source-mismatch")) {
|
|
1820
1826
|
return new errors_FsSafeError("path-mismatch", "source path changed during copy");
|
|
1821
1827
|
}
|
|
1828
|
+
if (message.includes("fs-safe-temp-mismatch")) {
|
|
1829
|
+
return new errors_FsSafeError("path-mismatch", "temp path changed during write");
|
|
1830
|
+
}
|
|
1831
|
+
if (message.includes("fs-safe-root-mismatch")) {
|
|
1832
|
+
return new errors_FsSafeError("path-mismatch", "root path changed during operation");
|
|
1833
|
+
}
|
|
1822
1834
|
if (message.includes("fs-safe-directory-noreplace-unsupported")) {
|
|
1823
1835
|
return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
|
|
1824
1836
|
}
|
|
@@ -1999,9 +2011,7 @@ function validatePinnedOperationPayload(payload) {
|
|
|
1999
2011
|
}
|
|
2000
2012
|
}
|
|
2001
2013
|
function isPinnedHelperUnavailable(error) {
|
|
2002
|
-
return error instanceof Error &&
|
|
2003
|
-
"code" in error &&
|
|
2004
|
-
error.code === "helper-unavailable";
|
|
2014
|
+
return error instanceof Error && "code" in error && error.code === "helper-unavailable";
|
|
2005
2015
|
}
|
|
2006
2016
|
function validatePinnedRelativePath(relativePath) {
|
|
2007
2017
|
if (relativePath.length === 0 || relativePath === ".") {
|
|
@@ -2099,29 +2109,21 @@ function assertWithinMaxBytes(bytes, maxBytes) {
|
|
|
2099
2109
|
throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
|
|
2100
2110
|
}
|
|
2101
2111
|
}
|
|
2102
|
-
function
|
|
2103
|
-
if (maxBytes === undefined) {
|
|
2104
|
-
return undefined;
|
|
2105
|
-
}
|
|
2112
|
+
async function writeStreamToHandle(stream, handle, maxBytes) {
|
|
2106
2113
|
let bytes = 0;
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2114
|
+
for await (const chunk of stream) {
|
|
2115
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2116
|
+
bytes += buffer.byteLength;
|
|
2117
|
+
assertWithinMaxBytes(bytes, maxBytes);
|
|
2118
|
+
let offset = 0;
|
|
2119
|
+
while (offset < buffer.byteLength) {
|
|
2120
|
+
const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
|
|
2121
|
+
if (bytesWritten <= 0) {
|
|
2122
|
+
throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
|
|
2113
2123
|
}
|
|
2114
|
-
|
|
2115
|
-
}
|
|
2116
|
-
});
|
|
2117
|
-
}
|
|
2118
|
-
async function pipelineWithMaxBytes(stream, destination, maxBytes) {
|
|
2119
|
-
const limiter = pinned_write_createMaxBytesTransform(maxBytes);
|
|
2120
|
-
if (limiter) {
|
|
2121
|
-
await (0,external_node_stream_promises_.pipeline)(stream, limiter, destination);
|
|
2122
|
-
return;
|
|
2124
|
+
offset += bytesWritten;
|
|
2125
|
+
}
|
|
2123
2126
|
}
|
|
2124
|
-
await (0,external_node_stream_promises_.pipeline)(stream, destination);
|
|
2125
2127
|
}
|
|
2126
2128
|
async function inputToBase64(input, maxBytes) {
|
|
2127
2129
|
if (input.kind === "buffer") {
|
|
@@ -2146,7 +2148,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
2146
2148
|
relativeParentPath: params.relativeParentPath,
|
|
2147
2149
|
});
|
|
2148
2150
|
if (getFsSafePythonConfig().mode === "off") {
|
|
2149
|
-
return await
|
|
2151
|
+
return await runPinnedWriteFallbackOrThrow(params);
|
|
2150
2152
|
}
|
|
2151
2153
|
if (params.input.kind === "stream") {
|
|
2152
2154
|
try {
|
|
@@ -2154,7 +2156,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
2154
2156
|
}
|
|
2155
2157
|
catch (error) {
|
|
2156
2158
|
if (canFallbackFromPythonError(error)) {
|
|
2157
|
-
return await
|
|
2159
|
+
return await runPinnedWriteFallbackOrThrow(params, error);
|
|
2158
2160
|
}
|
|
2159
2161
|
throw error;
|
|
2160
2162
|
}
|
|
@@ -2167,6 +2169,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
2167
2169
|
mode: params.mode || 0o600,
|
|
2168
2170
|
overwrite: params.overwrite !== false,
|
|
2169
2171
|
relativeParentPath: params.relativeParentPath,
|
|
2172
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
2170
2173
|
};
|
|
2171
2174
|
try {
|
|
2172
2175
|
return await runPinnedPythonOperation({
|
|
@@ -2177,7 +2180,7 @@ async function runPinnedWriteHelper(params) {
|
|
|
2177
2180
|
}
|
|
2178
2181
|
catch (error) {
|
|
2179
2182
|
if (canFallbackFromPythonError(error)) {
|
|
2180
|
-
return await
|
|
2183
|
+
return await runPinnedWriteFallbackOrThrow(params, error);
|
|
2181
2184
|
}
|
|
2182
2185
|
throw error;
|
|
2183
2186
|
}
|
|
@@ -2197,22 +2200,29 @@ async function runPinnedCopyHelper(params) {
|
|
|
2197
2200
|
mode: params.mode || 0o600,
|
|
2198
2201
|
overwrite: params.overwrite !== false,
|
|
2199
2202
|
relativeParentPath: params.relativeParentPath,
|
|
2203
|
+
...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
|
|
2200
2204
|
sourceDev: params.sourceIdentity.dev,
|
|
2201
2205
|
sourceIno: params.sourceIdentity.ino,
|
|
2202
2206
|
sourcePath: params.sourcePath,
|
|
2203
2207
|
},
|
|
2204
2208
|
});
|
|
2205
2209
|
}
|
|
2210
|
+
async function runPinnedWriteFallbackOrThrow(params, cause) {
|
|
2211
|
+
if (process.platform !== "win32") {
|
|
2212
|
+
throw new errors_FsSafeError("helper-unavailable", "Python helper is required for pinned writes on this platform", { cause });
|
|
2213
|
+
}
|
|
2214
|
+
return await runPinnedWriteFallback(params);
|
|
2215
|
+
}
|
|
2206
2216
|
async function runPinnedWriteFallback(params) {
|
|
2207
2217
|
const parentPath = params.relativeParentPath
|
|
2208
2218
|
? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
|
|
2209
2219
|
: params.rootPath;
|
|
2210
|
-
const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
2211
2220
|
if (params.mkdir) {
|
|
2212
|
-
await
|
|
2213
|
-
await promises_.mkdir(parentPath, { recursive: true });
|
|
2214
|
-
});
|
|
2221
|
+
await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
|
|
2215
2222
|
}
|
|
2223
|
+
const parentGuard = params.mkdir
|
|
2224
|
+
? await directory_guard_createAsyncDirectoryGuard(parentPath)
|
|
2225
|
+
: await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
|
|
2216
2226
|
const targetPath = external_node_path_.join(parentPath, params.basename);
|
|
2217
2227
|
if (params.overwrite === false) {
|
|
2218
2228
|
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), {
|
|
@@ -2234,7 +2244,7 @@ async function runPinnedWriteFallback(params) {
|
|
|
2234
2244
|
}
|
|
2235
2245
|
}
|
|
2236
2246
|
else {
|
|
2237
|
-
await
|
|
2247
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
2238
2248
|
}
|
|
2239
2249
|
const stat = await handle.stat();
|
|
2240
2250
|
created = false;
|
|
@@ -2255,7 +2265,9 @@ async function runPinnedWriteFallback(params) {
|
|
|
2255
2265
|
? external_node_fs_.constants.O_NOFOLLOW
|
|
2256
2266
|
: 0);
|
|
2257
2267
|
let handle;
|
|
2258
|
-
let
|
|
2268
|
+
let tempStat;
|
|
2269
|
+
let targetStat;
|
|
2270
|
+
let renamed = false;
|
|
2259
2271
|
try {
|
|
2260
2272
|
handle = await promises_.open(tempPath, tempFlags, params.mode);
|
|
2261
2273
|
if (params.input.kind === "buffer") {
|
|
@@ -2268,29 +2280,36 @@ async function runPinnedWriteFallback(params) {
|
|
|
2268
2280
|
}
|
|
2269
2281
|
}
|
|
2270
2282
|
else {
|
|
2271
|
-
|
|
2272
|
-
writable.once("close", () => {
|
|
2273
|
-
handleClosedByStream = true;
|
|
2274
|
-
});
|
|
2275
|
-
await pipelineWithMaxBytes(params.input.stream, writable, params.maxBytes);
|
|
2283
|
+
await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
|
|
2276
2284
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2285
|
+
tempStat = await handle.stat();
|
|
2286
|
+
const tempPathStat = await promises_.lstat(tempPath);
|
|
2287
|
+
if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
|
|
2288
|
+
throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
|
|
2280
2289
|
}
|
|
2290
|
+
const expectedTempStat = tempStat;
|
|
2291
|
+
await handle.close().catch(() => undefined);
|
|
2292
|
+
handle = undefined;
|
|
2281
2293
|
await withAsyncDirectoryGuards([parentGuard], async () => {
|
|
2282
2294
|
await promises_.rename(tempPath, targetPath);
|
|
2295
|
+
renamed = true;
|
|
2296
|
+
targetStat = await promises_.lstat(targetPath);
|
|
2297
|
+
if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
|
|
2298
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
|
|
2299
|
+
}
|
|
2283
2300
|
});
|
|
2284
2301
|
}
|
|
2285
2302
|
catch (error) {
|
|
2286
|
-
|
|
2287
|
-
|
|
2303
|
+
await handle?.close().catch(() => undefined);
|
|
2304
|
+
if (!renamed) {
|
|
2305
|
+
await promises_.rm(tempPath, { force: true }).catch(() => undefined);
|
|
2288
2306
|
}
|
|
2289
|
-
await promises_.rm(tempPath, { force: true }).catch(() => undefined);
|
|
2290
2307
|
throw error;
|
|
2291
2308
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2309
|
+
if (!targetStat) {
|
|
2310
|
+
throw new errors_FsSafeError("path-mismatch", "fallback target was not verified");
|
|
2311
|
+
}
|
|
2312
|
+
return { dev: targetStat.dev, ino: targetStat.ino };
|
|
2294
2313
|
}
|
|
2295
2314
|
|
|
2296
2315
|
// EXTERNAL MODULE: external "node:os"
|
|
@@ -2829,7 +2848,7 @@ function relativeInsideRoot(rootPath, targetPath) {
|
|
|
2829
2848
|
if (!relative || relative === ".") {
|
|
2830
2849
|
return "";
|
|
2831
2850
|
}
|
|
2832
|
-
if (
|
|
2851
|
+
if (isPathRelativeEscape(relative)) {
|
|
2833
2852
|
return "";
|
|
2834
2853
|
}
|
|
2835
2854
|
return relative;
|
package/package.json
CHANGED