@lousy-agents/lint 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/116.js CHANGED
@@ -2,9 +2,6 @@ export const __rspack_esm_id = 116;
2
2
  export const __rspack_esm_ids = [116];
3
3
  export const __webpack_modules__ = {
4
4
  8711(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
5
- __webpack_require__.d(__webpack_exports__, {
6
- prompt: () => (prompt)
7
- });
8
5
  /* import */ var node_util__rspack_import_0 = __webpack_require__(7975);
9
6
  /* import */ var node_process__rspack_import_1 = __webpack_require__(1708);
10
7
  /* import */ var node_readline__rspack_import_2 = __webpack_require__(481);
@@ -290,6 +287,10 @@ async function prompt(message, opts = {}) {
290
287
 
291
288
 
292
289
 
290
+ __webpack_require__.d(__webpack_exports__, {
291
+ prompt: () => (prompt)
292
+ });
293
+
293
294
 
294
295
  },
295
296
 
package/dist/316.js CHANGED
@@ -4,9 +4,6 @@ export const __webpack_modules__ = {
4
4
  5375(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
5
5
  var zlib__rspack_import_11_namespace_cache;
6
6
  __webpack_require__.r(__webpack_exports__);
7
- __webpack_require__.d(__webpack_exports__, {
8
- t: () => (ce)
9
- });
10
7
  /* import */ var _rolldown_runtime_mjs__rspack_import_14 = __webpack_require__(421);
11
8
  /* import */ var node_fs__rspack_import_0 = __webpack_require__(3024);
12
9
  /* import */ var node_fs_promises__rspack_import_1 = __webpack_require__(1455);
@@ -2954,6 +2951,10 @@ var Da = (e) => {
2954
2951
  };
2955
2952
 
2956
2953
 
2954
+ __webpack_require__.d(__webpack_exports__, {
2955
+ t: () => (ce)
2956
+ });
2957
+
2957
2958
 
2958
2959
  },
2959
2960
 
package/dist/475.js CHANGED
@@ -1729,10 +1729,10 @@ class FSWatcher extends external_node_events_.EventEmitter {
1729
1729
  const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
1730
1730
  let stream = readdirp(root, options);
1731
1731
  this._streams.add(stream);
1732
- stream.once(STR_CLOSE, () => {
1732
+ stream.once((/* inlined export .STR_CLOSE */"close"), () => {
1733
1733
  stream = undefined;
1734
1734
  });
1735
- stream.once(STR_END, () => {
1735
+ stream.once((/* inlined export .STR_END */"end"), () => {
1736
1736
  if (stream) {
1737
1737
  this._streams.delete(stream);
1738
1738
  stream = undefined;
package/dist/653.js CHANGED
@@ -3,13 +3,6 @@ export const __rspack_esm_ids = [653];
3
3
  export const __webpack_modules__ = {
4
4
  292(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
5
5
  __webpack_require__.r(__webpack_exports__);
6
- __webpack_require__.d(__webpack_exports__, {
7
- a: () => (y),
8
- i: () => (S),
9
- n: () => (ne),
10
- r: () => (C),
11
- t: () => (oe)
12
- });
13
6
  /* import */ var _rolldown_runtime_mjs__rspack_import_8 = __webpack_require__(421);
14
7
  /* import */ var node_module__rspack_import_0 = __webpack_require__(8995);
15
8
  /* import */ var node_fs__rspack_import_1 = __webpack_require__(3024);
@@ -689,12 +682,18 @@ async function fe(e = {}) {
689
682
  }
690
683
 
691
684
 
685
+ __webpack_require__.d(__webpack_exports__, {
686
+ t: () => (oe)
687
+ }, {
688
+ a: y,
689
+ i: S,
690
+ n: ne,
691
+ r: C
692
+ });
693
+
692
694
 
693
695
  },
694
696
  421(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
695
- __webpack_require__.d(__webpack_exports__, {
696
- t: () => (__exportAll)
697
- });
698
697
  /* import */ var node_module__rspack_import_0 = __webpack_require__(8995);
699
698
 
700
699
  //#region \0rolldown/runtime.js
@@ -711,6 +710,10 @@ var __exportAll = (all, no_symbols) => {
711
710
  //#endregion
712
711
 
713
712
 
713
+ __webpack_require__.d(__webpack_exports__, {
714
+ t: () => (__exportAll)
715
+ });
716
+
714
717
 
715
718
  },
716
719
  8900(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
package/dist/index.js CHANGED
@@ -566,7 +566,7 @@ module.exports = webpackEmptyAsyncContext;
566
566
 
567
567
 
568
568
  },
569
- 4403(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
569
+ 9884(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
570
570
 
571
571
  // EXPORTS
572
572
  __webpack_require__.d(__webpack_exports__, {
@@ -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
- return candidate === parent || candidate.startsWith(`${parent}${external_node_path_.sep}`);
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 (isPathEscape(relative)) {
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)), root)) {
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);
@@ -1299,6 +1302,191 @@ function guardedRmSync(params) {
1299
1302
  }), { verifyAfter: params.verifyAfter });
1300
1303
  }
1301
1304
 
1305
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/deny-mutations.js
1306
+
1307
+
1308
+
1309
+
1310
+ async function pathExists(filePath) {
1311
+ try {
1312
+ await promises_.lstat(filePath);
1313
+ return true;
1314
+ }
1315
+ catch (err) {
1316
+ if (!path_isNotFoundPathError(err)) {
1317
+ throw err;
1318
+ }
1319
+ return false;
1320
+ }
1321
+ }
1322
+ async function resolvePathViaExistingAncestor(targetPath) {
1323
+ const normalized = external_node_path_.resolve(targetPath);
1324
+ let cursor = normalized;
1325
+ const missingSuffix = [];
1326
+ while (external_node_path_.dirname(cursor) !== cursor && !(await pathExists(cursor))) {
1327
+ missingSuffix.unshift(external_node_path_.basename(cursor));
1328
+ cursor = external_node_path_.dirname(cursor);
1329
+ }
1330
+ if (!(await pathExists(cursor))) {
1331
+ return normalized;
1332
+ }
1333
+ try {
1334
+ const resolvedAncestor = external_node_path_.resolve(await promises_.realpath(cursor));
1335
+ return missingSuffix.length === 0
1336
+ ? resolvedAncestor
1337
+ : external_node_path_.resolve(resolvedAncestor, ...missingSuffix);
1338
+ }
1339
+ catch {
1340
+ return normalized;
1341
+ }
1342
+ }
1343
+ async function comparablePaths(rawPath) {
1344
+ path_assertNoNulPathInput(rawPath, "path contains a NUL byte");
1345
+ const resolved = external_node_path_.resolve(rawPath);
1346
+ return new Set([resolved, await resolvePathViaExistingAncestor(resolved)]);
1347
+ }
1348
+ function isSamePath(left, right) {
1349
+ return path_isPathInside(left, right) && path_isPathInside(right, left);
1350
+ }
1351
+ function hasPolicyEntries(policy) {
1352
+ return Boolean(policy?.paths?.length || policy?.prefixes?.length);
1353
+ }
1354
+ function policyPathEntries(entries) {
1355
+ const paths = [];
1356
+ for (const entry of entries ?? []) {
1357
+ if (entry.length === 0) {
1358
+ throw new errors_FsSafeError("invalid-path", "deny mutation paths must be non-empty");
1359
+ }
1360
+ path_assertNoNulPathInput(entry, "deny mutation path contains a NUL byte");
1361
+ if (!external_node_path_.isAbsolute(entry)) {
1362
+ throw new errors_FsSafeError("invalid-path", "deny mutation paths must be absolute");
1363
+ }
1364
+ paths.push(entry);
1365
+ }
1366
+ return paths;
1367
+ }
1368
+ async function assertMutationNotDenied(filePath, policy, options = {}) {
1369
+ if (!hasPolicyEntries(policy)) {
1370
+ return;
1371
+ }
1372
+ const targetPaths = await comparablePaths(filePath);
1373
+ for (const deniedPath of policyPathEntries(policy.paths)) {
1374
+ const deniedPaths = await comparablePaths(deniedPath);
1375
+ for (const target of targetPaths) {
1376
+ for (const denied of deniedPaths) {
1377
+ if (isSamePath(denied, target) ||
1378
+ (options.protectAncestors === true && path_isPathInside(target, denied))) {
1379
+ throw new errors_FsSafeError("denied-path", "path is denied by denyMutations policy");
1380
+ }
1381
+ }
1382
+ }
1383
+ }
1384
+ for (const deniedPrefix of policyPathEntries(policy.prefixes)) {
1385
+ const deniedPaths = await comparablePaths(deniedPrefix);
1386
+ for (const target of targetPaths) {
1387
+ for (const denied of deniedPaths) {
1388
+ if (path_isPathInside(denied, target) ||
1389
+ (options.protectAncestors === true && path_isPathInside(target, denied))) {
1390
+ throw new errors_FsSafeError("denied-path", "path is denied by denyMutations policy");
1391
+ }
1392
+ }
1393
+ }
1394
+ }
1395
+ }
1396
+ function mergeDenyMutationPolicies(defaultPolicy, callPolicy) {
1397
+ if (!defaultPolicy) {
1398
+ return callPolicy;
1399
+ }
1400
+ if (!callPolicy) {
1401
+ return defaultPolicy;
1402
+ }
1403
+ return {
1404
+ paths: [...(defaultPolicy.paths ?? []), ...(callPolicy.paths ?? [])],
1405
+ prefixes: [...(defaultPolicy.prefixes ?? []), ...(callPolicy.prefixes ?? [])],
1406
+ };
1407
+ }
1408
+
1409
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/opened-realpath.js
1410
+
1411
+
1412
+
1413
+
1414
+
1415
+ async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
1416
+ const handleStat = await handle.stat();
1417
+ const fdCandidates = process.platform === "linux"
1418
+ ? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`]
1419
+ : process.platform === "win32"
1420
+ ? []
1421
+ : [`/dev/fd/${handle.fd}`];
1422
+ for (const fdPath of fdCandidates) {
1423
+ try {
1424
+ const fdRealPath = await promises_.realpath(fdPath);
1425
+ const fdRealStat = await promises_.stat(fdRealPath);
1426
+ if (file_identity_sameFileIdentity(handleStat, fdRealStat)) {
1427
+ return fdRealPath;
1428
+ }
1429
+ }
1430
+ catch {
1431
+ // try next fd path
1432
+ }
1433
+ }
1434
+ try {
1435
+ const ioRealPath = await promises_.realpath(ioPath);
1436
+ const ioRealStat = await promises_.stat(ioRealPath);
1437
+ if (file_identity_sameFileIdentity(handleStat, ioRealStat)) {
1438
+ return ioRealPath;
1439
+ }
1440
+ }
1441
+ catch (err) {
1442
+ if (!path_isNotFoundPathError(err)) {
1443
+ throw err;
1444
+ }
1445
+ }
1446
+ const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
1447
+ if (parentResolved) {
1448
+ return parentResolved;
1449
+ }
1450
+ throw new errors_FsSafeError("path-mismatch", "unable to resolve opened file path");
1451
+ }
1452
+ async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
1453
+ let parentReal;
1454
+ try {
1455
+ parentReal = await promises_.realpath(external_node_path_.dirname(ioPath));
1456
+ }
1457
+ catch (err) {
1458
+ if (path_isNotFoundPathError(err)) {
1459
+ return null;
1460
+ }
1461
+ throw err;
1462
+ }
1463
+ let entries;
1464
+ try {
1465
+ entries = await promises_.readdir(parentReal);
1466
+ }
1467
+ catch (err) {
1468
+ if (path_isNotFoundPathError(err)) {
1469
+ return null;
1470
+ }
1471
+ throw err;
1472
+ }
1473
+ for (const entry of entries.toSorted()) {
1474
+ const candidatePath = external_node_path_.join(parentReal, entry);
1475
+ try {
1476
+ const candidateStat = await promises_.lstat(candidatePath);
1477
+ if (candidateStat.isFile() && file_identity_sameFileIdentity(handleStat, candidateStat)) {
1478
+ return await promises_.realpath(candidatePath);
1479
+ }
1480
+ }
1481
+ catch (err) {
1482
+ if (!path_isNotFoundPathError(err)) {
1483
+ throw err;
1484
+ }
1485
+ }
1486
+ }
1487
+ return null;
1488
+ }
1489
+
1302
1490
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/pinned-python-config.js
1303
1491
  let overrideConfig = {};
1304
1492
  function parseMode(value) {
@@ -1347,26 +1535,20 @@ var external_node_child_process_ = __webpack_require__(1421);
1347
1535
 
1348
1536
 
1349
1537
  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
-
1538
+ import base64, errno, json, os, secrets, stat, sys
1358
1539
  DIR_FLAGS = os.O_RDONLY
1359
1540
  if hasattr(os, "O_DIRECTORY"):
1360
1541
  DIR_FLAGS |= os.O_DIRECTORY
1361
1542
  if hasattr(os, "O_NOFOLLOW"):
1362
1543
  DIR_FLAGS |= os.O_NOFOLLOW
1363
1544
  READ_FLAGS = os.O_RDONLY
1545
+ if hasattr(os, "O_NONBLOCK"):
1546
+ READ_FLAGS |= os.O_NONBLOCK
1364
1547
  if hasattr(os, "O_NOFOLLOW"):
1365
1548
  READ_FLAGS |= os.O_NOFOLLOW
1366
1549
  WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
1367
1550
  if hasattr(os, "O_NOFOLLOW"):
1368
1551
  WRITE_FLAGS |= os.O_NOFOLLOW
1369
-
1370
1552
  def split_relative(value):
1371
1553
  if value in ("", "."):
1372
1554
  return []
@@ -1379,10 +1561,8 @@ def split_relative(value):
1379
1561
  if part == "..":
1380
1562
  raise OSError(errno.EPERM, "path traversal is not allowed")
1381
1563
  return parts
1382
-
1383
1564
  def open_dir(path_value, dir_fd=None):
1384
1565
  return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
1385
-
1386
1566
  def walk_dir(root_fd, segments, mkdir_enabled=False):
1387
1567
  current_fd = os.dup(root_fd)
1388
1568
  try:
@@ -1400,14 +1580,12 @@ def walk_dir(root_fd, segments, mkdir_enabled=False):
1400
1580
  except Exception:
1401
1581
  os.close(current_fd)
1402
1582
  raise
1403
-
1404
1583
  def parent_and_basename(root_fd, relative):
1405
1584
  segments = split_relative(relative)
1406
1585
  if not segments:
1407
1586
  raise OSError(errno.EPERM, "operation requires a non-root path")
1408
1587
  parent_fd = walk_dir(root_fd, segments[:-1])
1409
1588
  return parent_fd, segments[-1]
1410
-
1411
1589
  def encode_stat(st):
1412
1590
  mode = st.st_mode
1413
1591
  return {
@@ -1423,14 +1601,12 @@ def encode_stat(st):
1423
1601
  "size": st.st_size,
1424
1602
  "uid": st.st_uid,
1425
1603
  }
1426
-
1427
1604
  def reject_unsafe_endpoint(st):
1428
1605
  mode = st.st_mode
1429
1606
  if stat.S_ISLNK(mode):
1430
1607
  raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
1431
1608
  if stat.S_ISREG(mode) and st.st_nlink > 1:
1432
1609
  raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
1433
-
1434
1610
  def copy_bytes(source_fd, dest_fd):
1435
1611
  while True:
1436
1612
  chunk = os.read(source_fd, 65536)
@@ -1442,7 +1618,6 @@ def copy_bytes(source_fd, dest_fd):
1442
1618
  if written <= 0:
1443
1619
  raise OSError(errno.EIO, "short write")
1444
1620
  view = view[written:]
1445
-
1446
1621
  def write_all(fd, data):
1447
1622
  view = memoryview(data)
1448
1623
  while view:
@@ -1450,11 +1625,9 @@ def write_all(fd, data):
1450
1625
  if written <= 0:
1451
1626
  raise OSError(errno.EIO, "short write")
1452
1627
  view = view[written:]
1453
-
1454
1628
  def link_unsupported(exc):
1455
1629
  unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
1456
1630
  return getattr(exc, "errno", None) in unsupported
1457
-
1458
1631
  def link_no_replace(name, new_name, source_fd, target_fd):
1459
1632
  linked = False
1460
1633
  try:
@@ -1469,11 +1642,9 @@ def link_no_replace(name, new_name, source_fd, target_fd):
1469
1642
  os.fsync(source_fd)
1470
1643
  if source_fd != target_fd:
1471
1644
  os.fsync(target_fd)
1472
-
1473
1645
  def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
1474
1646
  source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
1475
- dest_fd = None
1476
- success = False
1647
+ dest_fd = None; success = False; dest_stat = None
1477
1648
  try:
1478
1649
  if expected is not None:
1479
1650
  source_stat = os.fstat(source_fd)
@@ -1482,6 +1653,7 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
1482
1653
  dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
1483
1654
  copy_bytes(source_fd, dest_fd)
1484
1655
  os.fsync(dest_fd)
1656
+ dest_stat = os.fstat(dest_fd)
1485
1657
  success = True
1486
1658
  finally:
1487
1659
  os.close(source_fd)
@@ -1497,19 +1669,40 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
1497
1669
  try: os.unlink(basename, dir_fd=target_parent_fd)
1498
1670
  except FileNotFoundError: pass
1499
1671
  raise
1500
-
1501
- def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode):
1672
+ return dest_stat
1673
+ def same_identity(left, right):
1674
+ return left.st_dev == right.st_dev and left.st_ino == right.st_ino
1675
+ def verify_temp_name(parent_fd, temp_name, expected_stat):
1676
+ current_stat = os.lstat(temp_name, dir_fd=parent_fd)
1677
+ if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
1678
+ raise RuntimeError("fs-safe-temp-mismatch")
1679
+ def verify_committed_temp(parent_fd, basename, expected_stat):
1680
+ final_stat = os.lstat(basename, dir_fd=parent_fd)
1681
+ if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
1682
+ return final_stat
1683
+ try: os.unlink(basename, dir_fd=parent_fd)
1684
+ except FileNotFoundError: pass
1685
+ raise RuntimeError("fs-safe-temp-mismatch")
1686
+ def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
1687
+ verify_temp_name(parent_fd, temp_name, expected_stat)
1502
1688
  if overwrite:
1503
1689
  os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
1690
+ return verify_committed_temp(parent_fd, basename, expected_stat)
1504
1691
  else:
1505
1692
  try:
1506
1693
  os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
1694
+ final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
1507
1695
  os.unlink(temp_name, dir_fd=parent_fd)
1696
+ return final_stat
1508
1697
  except OSError as exc:
1509
1698
  if not link_unsupported(exc):
1510
1699
  raise
1511
- copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, unlink_source=True)
1512
-
1700
+ return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
1701
+ def assert_expected_root(root_fd, payload):
1702
+ if "rootDev" in payload or "rootIno" in payload:
1703
+ root_stat = os.fstat(root_fd)
1704
+ if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
1705
+ raise RuntimeError("fs-safe-root-mismatch")
1513
1706
  def stat_path(root_fd, payload):
1514
1707
  relative = payload.get("relativePath", "")
1515
1708
  segments = split_relative(relative)
@@ -1523,7 +1716,6 @@ def stat_path(root_fd, payload):
1523
1716
  return encode_stat(st)
1524
1717
  finally:
1525
1718
  os.close(parent_fd)
1526
-
1527
1719
  def readdir_path(root_fd, payload):
1528
1720
  dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
1529
1721
  try:
@@ -1539,12 +1731,9 @@ def readdir_path(root_fd, payload):
1539
1731
  return entries
1540
1732
  finally:
1541
1733
  os.close(dir_fd)
1542
-
1543
1734
  def mkdirp_path(root_fd, payload):
1544
1735
  dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
1545
- os.close(dir_fd)
1546
- return None
1547
-
1736
+ os.close(dir_fd); return None
1548
1737
  def remove_tree(parent_fd, basename):
1549
1738
  st = os.lstat(basename, dir_fd=parent_fd)
1550
1739
  if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
@@ -1557,7 +1746,6 @@ def remove_tree(parent_fd, basename):
1557
1746
  os.rmdir(basename, dir_fd=parent_fd)
1558
1747
  else:
1559
1748
  os.unlink(basename, dir_fd=parent_fd)
1560
-
1561
1749
  def remove_path(root_fd, payload):
1562
1750
  parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
1563
1751
  try:
@@ -1640,14 +1828,15 @@ def write_path(root_fd, payload):
1640
1828
  except FileNotFoundError:
1641
1829
  pass
1642
1830
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
1831
+ os.fchmod(temp_fd, mode)
1643
1832
  write_all(temp_fd, data)
1644
1833
  os.fsync(temp_fd)
1834
+ temp_stat = os.fstat(temp_fd)
1645
1835
  os.close(temp_fd)
1646
1836
  temp_fd = None
1647
- commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
1837
+ result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
1648
1838
  temp_name = None
1649
1839
  os.fsync(parent_fd)
1650
- result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
1651
1840
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
1652
1841
  finally:
1653
1842
  if temp_fd is not None:
@@ -1678,6 +1867,7 @@ def copy_path(root_fd, payload):
1678
1867
  raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
1679
1868
  parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
1680
1869
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
1870
+ os.fchmod(temp_fd, mode)
1681
1871
  written_bytes = 0
1682
1872
  while True:
1683
1873
  chunk = os.read(source_fd, 65536)
@@ -1693,12 +1883,12 @@ def copy_path(root_fd, payload):
1693
1883
  raise OSError(errno.EIO, "short write")
1694
1884
  view = view[written:]
1695
1885
  os.fsync(temp_fd)
1886
+ temp_stat = os.fstat(temp_fd)
1696
1887
  os.close(temp_fd)
1697
1888
  temp_fd = None
1698
- commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
1889
+ result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
1699
1890
  temp_name = None
1700
1891
  os.fsync(parent_fd)
1701
- result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
1702
1892
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
1703
1893
  finally:
1704
1894
  os.close(source_fd)
@@ -1715,6 +1905,7 @@ def copy_path(root_fd, payload):
1715
1905
  def run_operation(operation, root_path, payload):
1716
1906
  root_fd = open_dir(root_path)
1717
1907
  try:
1908
+ assert_expected_root(root_fd, payload)
1718
1909
  if operation == "stat":
1719
1910
  return stat_path(root_fd, payload)
1720
1911
  if operation == "readdir":
@@ -1819,6 +2010,12 @@ function mapWorkerError(response) {
1819
2010
  if (message.includes("fs-safe-source-mismatch")) {
1820
2011
  return new errors_FsSafeError("path-mismatch", "source path changed during copy");
1821
2012
  }
2013
+ if (message.includes("fs-safe-temp-mismatch")) {
2014
+ return new errors_FsSafeError("path-mismatch", "temp path changed during write");
2015
+ }
2016
+ if (message.includes("fs-safe-root-mismatch")) {
2017
+ return new errors_FsSafeError("path-mismatch", "root path changed during operation");
2018
+ }
1822
2019
  if (message.includes("fs-safe-directory-noreplace-unsupported")) {
1823
2020
  return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
1824
2021
  }
@@ -1999,9 +2196,7 @@ function validatePinnedOperationPayload(payload) {
1999
2196
  }
2000
2197
  }
2001
2198
  function isPinnedHelperUnavailable(error) {
2002
- return error instanceof Error &&
2003
- "code" in error &&
2004
- error.code === "helper-unavailable";
2199
+ return error instanceof Error && "code" in error && error.code === "helper-unavailable";
2005
2200
  }
2006
2201
  function validatePinnedRelativePath(relativePath) {
2007
2202
  if (relativePath.length === 0 || relativePath === ".") {
@@ -2099,29 +2294,21 @@ function assertWithinMaxBytes(bytes, maxBytes) {
2099
2294
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
2100
2295
  }
2101
2296
  }
2102
- function pinned_write_createMaxBytesTransform(maxBytes) {
2103
- if (maxBytes === undefined) {
2104
- return undefined;
2105
- }
2297
+ async function writeStreamToHandle(stream, handle, maxBytes) {
2106
2298
  let bytes = 0;
2107
- return new external_node_stream_.Transform({
2108
- transform(chunk, _encoding, callback) {
2109
- bytes += chunk.byteLength;
2110
- if (bytes > maxBytes) {
2111
- callback(new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
2112
- return;
2299
+ for await (const chunk of stream) {
2300
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
2301
+ bytes += buffer.byteLength;
2302
+ assertWithinMaxBytes(bytes, maxBytes);
2303
+ let offset = 0;
2304
+ while (offset < buffer.byteLength) {
2305
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
2306
+ if (bytesWritten <= 0) {
2307
+ throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
2113
2308
  }
2114
- callback(null, chunk);
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;
2309
+ offset += bytesWritten;
2310
+ }
2123
2311
  }
2124
- await (0,external_node_stream_promises_.pipeline)(stream, destination);
2125
2312
  }
2126
2313
  async function inputToBase64(input, maxBytes) {
2127
2314
  if (input.kind === "buffer") {
@@ -2167,6 +2354,7 @@ async function runPinnedWriteHelper(params) {
2167
2354
  mode: params.mode || 0o600,
2168
2355
  overwrite: params.overwrite !== false,
2169
2356
  relativeParentPath: params.relativeParentPath,
2357
+ ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
2170
2358
  };
2171
2359
  try {
2172
2360
  return await runPinnedPythonOperation({
@@ -2197,6 +2385,7 @@ async function runPinnedCopyHelper(params) {
2197
2385
  mode: params.mode || 0o600,
2198
2386
  overwrite: params.overwrite !== false,
2199
2387
  relativeParentPath: params.relativeParentPath,
2388
+ ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
2200
2389
  sourceDev: params.sourceIdentity.dev,
2201
2390
  sourceIno: params.sourceIdentity.ino,
2202
2391
  sourcePath: params.sourcePath,
@@ -2207,12 +2396,12 @@ async function runPinnedWriteFallback(params) {
2207
2396
  const parentPath = params.relativeParentPath
2208
2397
  ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
2209
2398
  : params.rootPath;
2210
- const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
2211
2399
  if (params.mkdir) {
2212
- await withAsyncDirectoryGuards([parentGuard], async () => {
2213
- await promises_.mkdir(parentPath, { recursive: true });
2214
- });
2400
+ await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
2215
2401
  }
2402
+ const parentGuard = params.mkdir
2403
+ ? await directory_guard_createAsyncDirectoryGuard(parentPath)
2404
+ : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
2216
2405
  const targetPath = external_node_path_.join(parentPath, params.basename);
2217
2406
  if (params.overwrite === false) {
2218
2407
  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 +2423,7 @@ async function runPinnedWriteFallback(params) {
2234
2423
  }
2235
2424
  }
2236
2425
  else {
2237
- await pipelineWithMaxBytes(params.input.stream, handle.createWriteStream(), params.maxBytes);
2426
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
2238
2427
  }
2239
2428
  const stat = await handle.stat();
2240
2429
  created = false;
@@ -2255,7 +2444,9 @@ async function runPinnedWriteFallback(params) {
2255
2444
  ? external_node_fs_.constants.O_NOFOLLOW
2256
2445
  : 0);
2257
2446
  let handle;
2258
- let handleClosedByStream = false;
2447
+ let tempStat;
2448
+ let targetStat;
2449
+ let renamed = false;
2259
2450
  try {
2260
2451
  handle = await promises_.open(tempPath, tempFlags, params.mode);
2261
2452
  if (params.input.kind === "buffer") {
@@ -2268,29 +2459,36 @@ async function runPinnedWriteFallback(params) {
2268
2459
  }
2269
2460
  }
2270
2461
  else {
2271
- const writable = handle.createWriteStream();
2272
- writable.once("close", () => {
2273
- handleClosedByStream = true;
2274
- });
2275
- await pipelineWithMaxBytes(params.input.stream, writable, params.maxBytes);
2462
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
2276
2463
  }
2277
- if (!handleClosedByStream) {
2278
- await handle.close().catch(() => undefined);
2279
- handle = undefined;
2464
+ tempStat = await handle.stat();
2465
+ const tempPathStat = await promises_.lstat(tempPath);
2466
+ if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
2467
+ throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
2280
2468
  }
2469
+ const expectedTempStat = tempStat;
2470
+ await handle.close().catch(() => undefined);
2471
+ handle = undefined;
2281
2472
  await withAsyncDirectoryGuards([parentGuard], async () => {
2282
2473
  await promises_.rename(tempPath, targetPath);
2474
+ renamed = true;
2475
+ targetStat = await promises_.lstat(targetPath);
2476
+ if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
2477
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
2478
+ }
2283
2479
  });
2284
2480
  }
2285
2481
  catch (error) {
2286
- if (handle && !handleClosedByStream) {
2287
- await handle.close().catch(() => undefined);
2482
+ await handle?.close().catch(() => undefined);
2483
+ if (!renamed) {
2484
+ await promises_.rm(tempPath, { force: true }).catch(() => undefined);
2288
2485
  }
2289
- await promises_.rm(tempPath, { force: true }).catch(() => undefined);
2290
2486
  throw error;
2291
2487
  }
2292
- const stat = await promises_.stat(targetPath);
2293
- return { dev: stat.dev, ino: stat.ino };
2488
+ if (!targetStat) {
2489
+ throw new errors_FsSafeError("path-mismatch", "fallback target was not verified");
2490
+ }
2491
+ return { dev: targetStat.dev, ino: targetStat.ino };
2294
2492
  }
2295
2493
 
2296
2494
  // EXTERNAL MODULE: external "node:os"
@@ -2316,7 +2514,7 @@ async function resolveRootPath(params) {
2316
2514
  const absolutePath = external_node_path_.resolve(params.absolutePath);
2317
2515
  const rootCanonicalPath = params.rootCanonicalPath
2318
2516
  ? external_node_path_.resolve(params.rootCanonicalPath)
2319
- : await resolvePathViaExistingAncestor(rootPath);
2517
+ : await root_path_resolvePathViaExistingAncestor(rootPath);
2320
2518
  const context = createBoundaryResolutionContext({
2321
2519
  resolveParams: params,
2322
2520
  rootPath,
@@ -2685,7 +2883,7 @@ async function resolveOutsideLexicalCanonicalPathAsync(params) {
2685
2883
  if (path_isPathInside(params.rootPath, params.absolutePath)) {
2686
2884
  return undefined;
2687
2885
  }
2688
- return await resolvePathViaExistingAncestor(params.absolutePath);
2886
+ return await root_path_resolvePathViaExistingAncestor(params.absolutePath);
2689
2887
  }
2690
2888
  function resolveOutsideLexicalCanonicalPathSync(params) {
2691
2889
  if (isPathInside(params.rootPath, params.absolutePath)) {
@@ -2732,11 +2930,11 @@ function buildResolvedRootPath(params) {
2732
2930
  kind: params.kind.kind,
2733
2931
  };
2734
2932
  }
2735
- async function resolvePathViaExistingAncestor(targetPath) {
2933
+ async function root_path_resolvePathViaExistingAncestor(targetPath) {
2736
2934
  const normalized = external_node_path_.resolve(targetPath);
2737
2935
  let cursor = normalized;
2738
2936
  const missingSuffix = [];
2739
- while (!isFilesystemRoot(cursor) && !(await pathExists(cursor))) {
2937
+ while (!isFilesystemRoot(cursor) && !(await root_path_pathExists(cursor))) {
2740
2938
  missingSuffix.unshift(external_node_path_.basename(cursor));
2741
2939
  const parent = external_node_path_.dirname(cursor);
2742
2940
  if (parent === cursor) {
@@ -2744,7 +2942,7 @@ async function resolvePathViaExistingAncestor(targetPath) {
2744
2942
  }
2745
2943
  cursor = parent;
2746
2944
  }
2747
- if (!(await pathExists(cursor))) {
2945
+ if (!(await root_path_pathExists(cursor))) {
2748
2946
  return normalized;
2749
2947
  }
2750
2948
  try {
@@ -2829,7 +3027,7 @@ function relativeInsideRoot(rootPath, targetPath) {
2829
3027
  if (!relative || relative === ".") {
2830
3028
  return "";
2831
3029
  }
2832
- if (relative.startsWith("..") || external_node_path_.isAbsolute(relative)) {
3030
+ if (isPathRelativeEscape(relative)) {
2833
3031
  return "";
2834
3032
  }
2835
3033
  return relative;
@@ -2856,7 +3054,7 @@ function shortPath(value) {
2856
3054
  function isFilesystemRoot(candidate) {
2857
3055
  return external_node_path_.parse(candidate).root === candidate;
2858
3056
  }
2859
- async function pathExists(targetPath) {
3057
+ async function root_path_pathExists(targetPath) {
2860
3058
  try {
2861
3059
  await promises_.lstat(targetPath);
2862
3060
  return true;
@@ -2878,7 +3076,7 @@ async function resolveSymlinkHopPath(symlinkPath) {
2878
3076
  }
2879
3077
  const linkTarget = await promises_.readlink(symlinkPath);
2880
3078
  const linkAbsolute = external_node_path_.resolve(external_node_path_.dirname(symlinkPath), linkTarget);
2881
- return resolvePathViaExistingAncestor(linkAbsolute);
3079
+ return root_path_resolvePathViaExistingAncestor(linkAbsolute);
2882
3080
  }
2883
3081
  }
2884
3082
  function resolveSymlinkHopPathSync(symlinkPath) {
@@ -2947,6 +3145,23 @@ function path_policy_shortPath(value) {
2947
3145
  return value;
2948
3146
  }
2949
3147
 
3148
+ ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/read-opened-file.js
3149
+
3150
+ async function read_opened_file_readOpenedFileSafely(params) {
3151
+ if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
3152
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
3153
+ }
3154
+ const buffer = await params.opened.handle.readFile();
3155
+ if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
3156
+ throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
3157
+ }
3158
+ return {
3159
+ buffer,
3160
+ realPath: params.opened.realPath,
3161
+ stat: params.opened.stat,
3162
+ };
3163
+ }
3164
+
2950
3165
  ;// CONCATENATED MODULE: ../../node_modules/@openclaw/fs-safe/dist/path-stat.js
2951
3166
  function pathStatFromStats(stat) {
2952
3167
  return {
@@ -3274,6 +3489,10 @@ async function serializePathWrite(key, run) {
3274
3489
 
3275
3490
 
3276
3491
 
3492
+
3493
+
3494
+
3495
+
3277
3496
 
3278
3497
 
3279
3498
 
@@ -3472,6 +3691,7 @@ class RootHandle {
3472
3691
  mkdir: this.defaults.mkdir,
3473
3692
  mode: this.defaults.mode,
3474
3693
  ...options,
3694
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3475
3695
  append: writeMode === "append",
3476
3696
  truncateExisting: writeMode === "replace",
3477
3697
  });
@@ -3483,18 +3703,29 @@ class RootHandle {
3483
3703
  mkdir: this.defaults.mkdir,
3484
3704
  mode: this.defaults.mode,
3485
3705
  ...options,
3706
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3486
3707
  });
3487
3708
  }
3488
- async remove(relativePath) {
3709
+ async remove(relativePath, options = {}) {
3489
3710
  assertValidRootRelativePath(relativePath);
3490
- await removePathInRoot(this.context, relativePath);
3711
+ await removePathInRoot(this.context, {
3712
+ relativePath,
3713
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3714
+ });
3491
3715
  }
3492
- async mkdir(relativePath) {
3716
+ async mkdir(relativePath, options = {}) {
3493
3717
  assertValidRootRelativePath(relativePath);
3494
- await mkdirPathInRoot(this.context, { relativePath });
3718
+ await mkdirPathInRoot(this.context, {
3719
+ relativePath,
3720
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3721
+ });
3495
3722
  }
3496
- async ensureRoot() {
3497
- await mkdirPathInRoot(this.context, { relativePath: "", allowRoot: true });
3723
+ async ensureRoot(options = {}) {
3724
+ await mkdirPathInRoot(this.context, {
3725
+ relativePath: "",
3726
+ allowRoot: true,
3727
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3728
+ });
3498
3729
  }
3499
3730
  async write(relativePath, data, options = {}) {
3500
3731
  await writeFileInRoot(this.context, {
@@ -3503,6 +3734,7 @@ class RootHandle {
3503
3734
  mkdir: this.defaults.mkdir,
3504
3735
  mode: this.defaults.mode,
3505
3736
  ...options,
3737
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3506
3738
  });
3507
3739
  }
3508
3740
  async create(relativePath, data, options = {}) {
@@ -3512,6 +3744,7 @@ class RootHandle {
3512
3744
  mkdir: this.defaults.mkdir,
3513
3745
  mode: this.defaults.mode,
3514
3746
  ...options,
3747
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3515
3748
  overwrite: false,
3516
3749
  });
3517
3750
  }
@@ -3534,6 +3767,7 @@ class RootHandle {
3534
3767
  mkdir: this.defaults.mkdir,
3535
3768
  mode: this.defaults.mode,
3536
3769
  ...options,
3770
+ denyMutations: mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations),
3537
3771
  });
3538
3772
  }
3539
3773
  async exists(relativePath) {
@@ -3577,6 +3811,12 @@ class RootHandle {
3577
3811
  async move(fromRelative, toRelative, options = {}) {
3578
3812
  assertValidRootRelativePath(fromRelative);
3579
3813
  assertValidRootRelativePath(toRelative);
3814
+ const denyMutations = mergeDenyMutationPolicies(this.defaults.denyMutations, options.denyMutations);
3815
+ await assertMoveMutationAllowed(this.context, {
3816
+ fromRelative,
3817
+ toRelative,
3818
+ denyMutations,
3819
+ });
3580
3820
  try {
3581
3821
  await runPinnedHelper("rename", this.rootReal, {
3582
3822
  from: fromRelative,
@@ -3588,6 +3828,7 @@ class RootHandle {
3588
3828
  if (canFallbackFromPythonError(error)) {
3589
3829
  await movePathFallback(this.context, {
3590
3830
  fromRelative,
3831
+ denyMutations,
3591
3832
  overwrite: options.overwrite ?? false,
3592
3833
  toRelative,
3593
3834
  });
@@ -3636,7 +3877,7 @@ async function openFileInRoot(root, params) {
3636
3877
  async function readFileInRoot(root, params) {
3637
3878
  const opened = await openFileInRoot(root, params);
3638
3879
  try {
3639
- return await readOpenedFileSafely({ opened, maxBytes: params.maxBytes });
3880
+ return await read_opened_file_readOpenedFileSafely({ opened, maxBytes: params.maxBytes });
3640
3881
  }
3641
3882
  finally {
3642
3883
  await opened.handle.close().catch(() => { });
@@ -3669,20 +3910,6 @@ async function openLocalFileSafely(params) {
3669
3910
  assertNoNulPathInput(params.filePath, "file path contains a NUL byte");
3670
3911
  return await openVerifiedLocalFile(params.filePath);
3671
3912
  }
3672
- async function readOpenedFileSafely(params) {
3673
- if (params.maxBytes !== undefined && params.opened.stat.size > params.maxBytes) {
3674
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${params.opened.stat.size})`);
3675
- }
3676
- const buffer = await params.opened.handle.readFile();
3677
- if (params.maxBytes !== undefined && buffer.byteLength > params.maxBytes) {
3678
- throw new errors_FsSafeError("too-large", `file exceeds limit of ${params.maxBytes} bytes (got ${buffer.byteLength})`);
3679
- }
3680
- return {
3681
- buffer,
3682
- realPath: params.opened.realPath,
3683
- stat: params.opened.stat,
3684
- };
3685
- }
3686
3913
  function emitWriteBoundaryWarning(reason) {
3687
3914
  logWarn(`security: fs-safe write boundary warning (${reason})`);
3688
3915
  }
@@ -3723,82 +3950,9 @@ async function verifyAtomicWriteResult(params) {
3723
3950
  await opened.handle.close().catch(() => { });
3724
3951
  }
3725
3952
  }
3726
- async function resolveOpenedFileRealPathForHandle(handle, ioPath) {
3727
- const handleStat = await handle.stat();
3728
- const fdCandidates = process.platform === "linux"
3729
- ? [`/proc/self/fd/${handle.fd}`, `/dev/fd/${handle.fd}`]
3730
- : process.platform === "win32"
3731
- ? []
3732
- : [`/dev/fd/${handle.fd}`];
3733
- for (const fdPath of fdCandidates) {
3734
- try {
3735
- const fdRealPath = await promises_.realpath(fdPath);
3736
- const fdRealStat = await promises_.stat(fdRealPath);
3737
- if (file_identity_sameFileIdentity(handleStat, fdRealStat)) {
3738
- return fdRealPath;
3739
- }
3740
- }
3741
- catch {
3742
- // try next fd path
3743
- }
3744
- }
3745
- try {
3746
- const ioRealPath = await promises_.realpath(ioPath);
3747
- const ioRealStat = await promises_.stat(ioRealPath);
3748
- if (file_identity_sameFileIdentity(handleStat, ioRealStat)) {
3749
- return ioRealPath;
3750
- }
3751
- }
3752
- catch (err) {
3753
- if (!path_isNotFoundPathError(err)) {
3754
- throw err;
3755
- }
3756
- }
3757
- const parentResolved = await resolveOpenedFileRealPathFromParent(handleStat, ioPath);
3758
- if (parentResolved) {
3759
- return parentResolved;
3760
- }
3761
- throw new errors_FsSafeError("path-mismatch", "unable to resolve opened file path");
3762
- }
3763
- async function resolveOpenedFileRealPathFromParent(handleStat, ioPath) {
3764
- let parentReal;
3765
- try {
3766
- parentReal = await promises_.realpath(external_node_path_.dirname(ioPath));
3767
- }
3768
- catch (err) {
3769
- if (path_isNotFoundPathError(err)) {
3770
- return null;
3771
- }
3772
- throw err;
3773
- }
3774
- let entries;
3775
- try {
3776
- entries = await promises_.readdir(parentReal);
3777
- }
3778
- catch (err) {
3779
- if (path_isNotFoundPathError(err)) {
3780
- return null;
3781
- }
3782
- throw err;
3783
- }
3784
- for (const entry of entries.toSorted()) {
3785
- const candidatePath = external_node_path_.join(parentReal, entry);
3786
- try {
3787
- const candidateStat = await promises_.lstat(candidatePath);
3788
- if (candidateStat.isFile() && file_identity_sameFileIdentity(handleStat, candidateStat)) {
3789
- return await promises_.realpath(candidatePath);
3790
- }
3791
- }
3792
- catch (err) {
3793
- if (!path_isNotFoundPathError(err)) {
3794
- throw err;
3795
- }
3796
- }
3797
- }
3798
- return null;
3799
- }
3800
3953
  async function openWritableFileInRoot(root, params) {
3801
3954
  const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, params.relativePath);
3955
+ await assertMutationNotDenied(resolved, params.denyMutations);
3802
3956
  try {
3803
3957
  await assertNoPathAliasEscape({
3804
3958
  absolutePath: resolved,
@@ -3925,6 +4079,7 @@ async function appendFileInRoot(root, params) {
3925
4079
  relativePath: params.relativePath,
3926
4080
  mkdir: params.mkdir,
3927
4081
  mode: params.mode,
4082
+ denyMutations: params.denyMutations,
3928
4083
  truncateExisting: false,
3929
4084
  append: true,
3930
4085
  });
@@ -3952,8 +4107,8 @@ async function appendFileInRoot(root, params) {
3952
4107
  await target.handle.close().catch(() => { });
3953
4108
  }
3954
4109
  }
3955
- async function removePathInRoot(root, relativePath) {
3956
- const resolved = await resolvePinnedRemovePathInRoot(root, relativePath);
4110
+ async function removePathInRoot(root, params) {
4111
+ const resolved = await resolvePinnedRemovePathInRoot(root, params.relativePath, params.denyMutations);
3957
4112
  if (process.platform === "win32") {
3958
4113
  await removePathFallback(resolved);
3959
4114
  return;
@@ -4001,7 +4156,7 @@ async function writeFileInRoot(root, params) {
4001
4156
  });
4002
4157
  return;
4003
4158
  }
4004
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode);
4159
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
4005
4160
  await serializePathWrite(pinned.targetPath, async () => {
4006
4161
  let identity;
4007
4162
  try {
@@ -4057,7 +4212,7 @@ async function copyFileInRoot(root, params) {
4057
4212
  });
4058
4213
  return;
4059
4214
  }
4060
- const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode);
4215
+ const pinned = await resolvePinnedWriteTargetInRoot(root, params.relativePath, params.mode, params.denyMutations);
4061
4216
  await serializePathWrite(pinned.targetPath, async () => {
4062
4217
  let identity;
4063
4218
  try {
@@ -4101,8 +4256,9 @@ async function copyFileInRoot(root, params) {
4101
4256
  await source.handle.close().catch(() => { });
4102
4257
  }
4103
4258
  }
4104
- async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode) {
4259
+ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode, denyMutations) {
4105
4260
  const { rootReal, rootWithSep, resolved } = await resolvePathInRoot(root, relativePath);
4261
+ await assertMutationNotDenied(resolved, denyMutations);
4106
4262
  try {
4107
4263
  await assertNoPathAliasEscape({
4108
4264
  absolutePath: resolved,
@@ -4159,12 +4315,16 @@ async function resolvePinnedWriteTargetInRoot(root, relativePath, requestedMode)
4159
4315
  async function resolvePinnedPathInRoot(root, params) {
4160
4316
  return await resolvePinnedOperationPathInRoot(root, {
4161
4317
  allowRoot: params.allowRoot,
4318
+ denyMutations: params.denyMutations,
4319
+ protectDenyMutationAncestors: false,
4162
4320
  relativePath: params.relativePath,
4163
4321
  policy: PATH_ALIAS_POLICIES.strict,
4164
4322
  });
4165
4323
  }
4166
- async function resolvePinnedRemovePathInRoot(root, relativePath) {
4324
+ async function resolvePinnedRemovePathInRoot(root, relativePath, denyMutations) {
4167
4325
  return await resolvePinnedOperationPathInRoot(root, {
4326
+ denyMutations,
4327
+ protectDenyMutationAncestors: true,
4168
4328
  relativePath,
4169
4329
  policy: PATH_ALIAS_POLICIES.unlinkTarget,
4170
4330
  });
@@ -4176,6 +4336,7 @@ async function resolvePinnedOperationPathInRoot(root, params) {
4176
4336
  });
4177
4337
  const relativeResolved = external_node_path_.relative(resolved.rootReal, resolved.canonicalPath);
4178
4338
  if ((relativeResolved === "" || relativeResolved === ".") && params.allowRoot === true) {
4339
+ await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations);
4179
4340
  return { rootReal: resolved.rootReal, resolved: resolved.canonicalPath, relativePosix: "" };
4180
4341
  }
4181
4342
  const firstSegment = relativeResolved.split(external_node_path_.sep)[0];
@@ -4189,6 +4350,9 @@ async function resolvePinnedOperationPathInRoot(root, params) {
4189
4350
  if (!path_isPathInside(resolved.rootWithSep, resolved.canonicalPath)) {
4190
4351
  throw new errors_FsSafeError("outside-workspace", "file is outside workspace root");
4191
4352
  }
4353
+ await assertMutationNotDenied(resolved.canonicalPath, params.denyMutations, {
4354
+ protectAncestors: params.protectDenyMutationAncestors,
4355
+ });
4192
4356
  return { rootReal: resolved.rootReal, resolved: resolved.canonicalPath, relativePosix };
4193
4357
  }
4194
4358
  async function resolvePinnedRootPathInRoot(root, params) {
@@ -4266,13 +4430,21 @@ async function listPathFallback(root, relativePath, withFileTypes) {
4266
4430
  throw error;
4267
4431
  }
4268
4432
  }
4433
+ async function assertMoveMutationAllowed(root, params) {
4434
+ const source = await resolvePathInRoot(root, params.fromRelative);
4435
+ await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
4436
+ const target = await resolvePathInRoot(root, params.toRelative);
4437
+ await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
4438
+ }
4269
4439
  async function movePathFallback(root, params) {
4270
4440
  const source = await resolvePathInRoot(root, params.fromRelative);
4441
+ await assertMutationNotDenied(source.resolved, params.denyMutations, { protectAncestors: true });
4271
4442
  await resolvePinnedRootPathInRoot(root, {
4272
4443
  relativePath: params.fromRelative,
4273
4444
  policy: PATH_ALIAS_POLICIES.strict,
4274
4445
  });
4275
4446
  const target = await resolvePathInRoot(root, params.toRelative);
4447
+ await assertMutationNotDenied(target.resolved, params.denyMutations, { protectAncestors: true });
4276
4448
  await resolvePinnedRootPathInRoot(root, {
4277
4449
  relativePath: params.toRelative,
4278
4450
  policy: PATH_ALIAS_POLICIES.unlinkTarget,
@@ -4356,6 +4528,7 @@ async function writeFileFallback(root, params) {
4356
4528
  relativePath: params.relativePath,
4357
4529
  mkdir: params.mkdir,
4358
4530
  mode: params.mode,
4531
+ denyMutations: params.denyMutations,
4359
4532
  truncateExisting: false,
4360
4533
  });
4361
4534
  const destinationPath = target.realPath;
@@ -4401,6 +4574,7 @@ async function writeFileFallback(root, params) {
4401
4574
  }
4402
4575
  async function writeMissingFileFallback(root, params) {
4403
4576
  const { rootReal, resolved } = await resolvePathInRoot(root, params.relativePath);
4577
+ await assertMutationNotDenied(resolved, params.denyMutations);
4404
4578
  try {
4405
4579
  await assertNoPathAliasEscape({
4406
4580
  absolutePath: resolved,
@@ -4474,6 +4648,7 @@ async function copyFileFallback(root, params, source) {
4474
4648
  relativePath: params.relativePath,
4475
4649
  mkdir: params.mkdir,
4476
4650
  mode: params.mode,
4651
+ denyMutations: params.denyMutations,
4477
4652
  truncateExisting: false,
4478
4653
  });
4479
4654
  const destinationPath = target.realPath;
@@ -4634,7 +4809,7 @@ async function pathExistsWithinRoot(targetDir, relativePath) {
4634
4809
  * FsSafeError as their `cause` and should be re-thrown rather than
4635
4810
  * silently swallowed by per-file error handlers.
4636
4811
  */ function isFsSafeViolation(error) {
4637
- return error instanceof Error && error.cause instanceof FsSafeError;
4812
+ return error instanceof Error && error.cause instanceof errors_FsSafeError;
4638
4813
  }
4639
4814
  async function statWithinRoot(targetDir, relativePath) {
4640
4815
  try {
@@ -19078,7 +19253,7 @@ function formatHeadingAsSetext(node, state) {
19078
19253
  node.type === 'break'
19079
19254
  ) {
19080
19255
  literalWithBreak = true
19081
- return EXIT
19256
+ return (/* inlined export .EXIT */false)
19082
19257
  }
19083
19258
  })
19084
19259
 
@@ -33401,6 +33576,7 @@ const PackageJsonSchema = schemas_object({
33401
33576
  */
33402
33577
 
33403
33578
 
33579
+
33404
33580
  /** Maximum skill file size: 1 MB */ const MAX_SKILL_FILE_BYTES = 1_048_576;
33405
33581
  /**
33406
33582
  * Skill directory locations to search for SKILL.md files.
@@ -33409,6 +33585,33 @@ const PackageJsonSchema = schemas_object({
33409
33585
  (0,external_node_path_.join)(".claude", "skills"),
33410
33586
  (0,external_node_path_.join)(".agents", "skills")
33411
33587
  ];
33588
+ /** Maximum lock file size: 256 KB */ const MAX_LOCK_FILE_BYTES = 262_144;
33589
+ /** Relative path to the skills lock file. */ const SKILLS_LOCK_PATH = "skills-lock.json";
33590
+ /** Zod schema for skills-lock.json structure */ const SkillsLockSchema = schemas_object({
33591
+ skills: record(schemas_string(), schemas_unknown()).optional()
33592
+ });
33593
+ /**
33594
+ * Reads the skills-lock.json file and returns the set of locked skill names.
33595
+ * Returns an empty set if the file does not exist or cannot be parsed.
33596
+ */ async function readLockedSkillNames(targetDir) {
33597
+ try {
33598
+ const exists = await pathExistsWithinRoot(targetDir, SKILLS_LOCK_PATH);
33599
+ if (!exists) {
33600
+ return new Set();
33601
+ }
33602
+ const content = await readTextWithinRoot(targetDir, SKILLS_LOCK_PATH, MAX_LOCK_FILE_BYTES);
33603
+ const result = SkillsLockSchema.safeParse(JSON.parse(content));
33604
+ if (result.success && Object.hasOwn(result.data, "skills") && result.data.skills != null) {
33605
+ return new Set(Object.keys(result.data.skills));
33606
+ }
33607
+ return new Set();
33608
+ } catch (error) {
33609
+ if (isFsSafeViolation(error)) {
33610
+ throw error;
33611
+ }
33612
+ return new Set();
33613
+ }
33614
+ }
33412
33615
  /**
33413
33616
  * File system implementation of the skill lint gateway.
33414
33617
  */ class FileSystemSkillLintGateway {
@@ -33418,7 +33621,11 @@ const PackageJsonSchema = schemas_object({
33418
33621
  const discovered = await this.discoverSkillsInDir(targetDir, relativeDir);
33419
33622
  skills.push(...discovered);
33420
33623
  }
33421
- return skills;
33624
+ const lockedNames = await readLockedSkillNames(targetDir);
33625
+ if (lockedNames.size === 0) {
33626
+ return skills;
33627
+ }
33628
+ return skills.filter((skill)=>!lockedNames.has(skill.skillName));
33422
33629
  }
33423
33630
  async discoverSkillsInDir(targetDir, skillsDir) {
33424
33631
  try {
@@ -45923,10 +46130,11 @@ exports.visitAsync = visitAsync;
45923
46130
 
45924
46131
  },
45925
46132
  5492(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
46133
+ const t=Symbol.for(`__confbox_fmt__`),n=/^(\s+)/,r=/(\s+)$/;function i(e,t={}){return{sample:t.indent===void 0&&t.preserveIndentation!==!1&&e.slice(0,t?.sampleSize||1024),whiteSpace:t.preserveWhitespace===!1?void 0:{start:n.exec(e)?.[0]||``,end:r.exec(e)?.[0]||``}}}function a(e,n,r){!n||typeof n!=`object`||Object.defineProperty(n,t,{enumerable:!1,configurable:!0,writable:!0,value:i(e,r)})}function o(n,r){if(!n||typeof n!=`object`||!(t in n))return{indent:r?.indent??2,whitespace:{start:``,end:``}};let i=n[t];return{indent:r?.indent||e(i.sample||``).indent,whitespace:i.whiteSpace||{start:``,end:``}}}
45926
46134
  __webpack_require__.d(__webpack_exports__, {
45927
46135
  n: () => (a)
45928
46136
  });
45929
- const t=Symbol.for(`__confbox_fmt__`),n=/^(\s+)/,r=/(\s+)$/;function i(e,t={}){return{sample:t.indent===void 0&&t.preserveIndentation!==!1&&e.slice(0,t?.sampleSize||1024),whiteSpace:t.preserveWhitespace===!1?void 0:{start:n.exec(e)?.[0]||``,end:r.exec(e)?.[0]||``}}}function a(e,n,r){!n||typeof n!=`object`||Object.defineProperty(n,t,{enumerable:!1,configurable:!0,writable:!0,value:i(e,r)})}function o(n,r){if(!n||typeof n!=`object`||!(t in n))return{indent:r?.indent??2,whitespace:{start:``,end:``}};let i=n[t];return{indent:r?.indent||e(i.sample||``).indent,whitespace:i.whiteSpace||{start:``,end:``}}}
46137
+
45930
46138
 
45931
46139
  },
45932
46140
  5975(__unused_rspack___webpack_module__, __webpack_exports__, __webpack_require__) {
@@ -46011,12 +46219,16 @@ __webpack_require__.t = function(value, mode) {
46011
46219
  })();
46012
46220
  // webpack/runtime/define_property_getters
46013
46221
  (() => {
46014
- __webpack_require__.d = (exports, definition) => {
46015
- for(var key in definition) {
46016
- if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
46017
- Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
46018
- }
46019
- }
46222
+ __webpack_require__.d = (exports, getters, values) => {
46223
+ var define = (defs, kind) => {
46224
+ for(var key in defs) {
46225
+ if(__webpack_require__.o(defs, key) && !__webpack_require__.o(exports, key)) {
46226
+ Object.defineProperty(exports, key, { enumerable: true, [kind]: defs[key] });
46227
+ }
46228
+ }
46229
+ };
46230
+ define(getters, "get");
46231
+ define(values, "value");
46020
46232
  };
46021
46233
  })();
46022
46234
  // webpack/runtime/ensure_chunk
@@ -46118,7 +46330,7 @@ if (installedChunkData !== 0) { // 0 means "already installed".'
46118
46330
  // module factories are used so entry inlining is disabled
46119
46331
  // startup
46120
46332
  // Load entry module and return exports
46121
- var __webpack_exports__ = __webpack_require__(4403);
46333
+ var __webpack_exports__ = __webpack_require__(9884);
46122
46334
  var __webpack_exports__DEFAULT_LINT_RULES = __webpack_exports__.Kd;
46123
46335
  var __webpack_exports__LintValidationError = __webpack_exports__.F5;
46124
46336
  var __webpack_exports__createFormatter = __webpack_exports__.xi;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/lint",
3
- "version": "5.14.9",
3
+ "version": "5.15.0",
4
4
  "description": "Programmatic lint API for validating AI coding assistant configurations — skills, agents, hooks, and instructions",
5
5
  "type": "module",
6
6
  "repository": {