@lousy-agents/mcp 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.
Files changed (2) hide show
  1. package/dist/mcp-server.js +106 -87
  2. package/package.json +1 -1
@@ -26726,6 +26726,9 @@ function path_isPathInside(root, target) {
26726
26726
  const firstSegment = relative.split(external_node_path_.posix.sep)[0];
26727
26727
  return relative === "" || (firstSegment !== ".." && !external_node_path_.isAbsolute(relative));
26728
26728
  }
26729
+ function isPathRelativeEscape(relativePath) {
26730
+ return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
26731
+ }
26729
26732
  function resolveSafeBaseDir(rootDir) {
26730
26733
  const resolved = path.resolve(rootDir);
26731
26734
  return resolved.endsWith(path.sep) ? resolved : `${resolved}${path.sep}`;
@@ -26874,17 +26877,17 @@ function directory_guard_createNearestExistingSyncDirectoryGuard(rootReal, targe
26874
26877
 
26875
26878
 
26876
26879
 
26880
+
26877
26881
  function isSameOrChildPath(candidate, parent) {
26878
- return candidate === parent || candidate.startsWith(`${parent}${external_node_path_.sep}`);
26879
- }
26880
- function isPathEscape(relativePath) {
26881
- return relativePath === ".." || relativePath.startsWith(`..${external_node_path_.sep}`) || external_node_path_.isAbsolute(relativePath);
26882
+ const parentPrefix = parent.endsWith(external_node_path_.sep) ? parent : `${parent}${external_node_path_.sep}`;
26883
+ return candidate === parent || candidate.startsWith(parentPrefix);
26882
26884
  }
26883
26885
  async function mkdirPathComponentsWithGuards(params) {
26884
26886
  const root = external_node_path_.resolve(params.rootReal);
26887
+ const rootCanonical = external_node_path_.resolve(await promises_.realpath(root));
26885
26888
  const target = external_node_path_.resolve(params.targetPath);
26886
26889
  const relative = external_node_path_.relative(root, target);
26887
- if (isPathEscape(relative)) {
26890
+ if (isPathRelativeEscape(relative)) {
26888
26891
  throw new errors_FsSafeError("outside-workspace", "directory is outside workspace root");
26889
26892
  }
26890
26893
  let current = root;
@@ -26907,7 +26910,7 @@ async function mkdirPathComponentsWithGuards(params) {
26907
26910
  }
26908
26911
  // Node's recursive mkdir follows symlinks in missing components. Build one
26909
26912
  // segment at a time and realpath-check each segment before descending.
26910
- if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)), root)) {
26913
+ if (!isSameOrChildPath(external_node_path_.resolve(await promises_.realpath(next)), rootCanonical)) {
26911
26914
  throw new errors_FsSafeError("outside-workspace", "directory escaped workspace root");
26912
26915
  }
26913
26916
  await directory_guard_createAsyncDirectoryGuard(next);
@@ -27042,26 +27045,20 @@ var external_node_child_process_ = __webpack_require__(1421);
27042
27045
 
27043
27046
 
27044
27047
  const PINNED_PYTHON_WORKER_SOURCE = String.raw `
27045
- import base64
27046
- import errno
27047
- import json
27048
- import os
27049
- import secrets
27050
- import stat
27051
- import sys
27052
-
27048
+ import base64, errno, json, os, secrets, stat, sys
27053
27049
  DIR_FLAGS = os.O_RDONLY
27054
27050
  if hasattr(os, "O_DIRECTORY"):
27055
27051
  DIR_FLAGS |= os.O_DIRECTORY
27056
27052
  if hasattr(os, "O_NOFOLLOW"):
27057
27053
  DIR_FLAGS |= os.O_NOFOLLOW
27058
27054
  READ_FLAGS = os.O_RDONLY
27055
+ if hasattr(os, "O_NONBLOCK"):
27056
+ READ_FLAGS |= os.O_NONBLOCK
27059
27057
  if hasattr(os, "O_NOFOLLOW"):
27060
27058
  READ_FLAGS |= os.O_NOFOLLOW
27061
27059
  WRITE_FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
27062
27060
  if hasattr(os, "O_NOFOLLOW"):
27063
27061
  WRITE_FLAGS |= os.O_NOFOLLOW
27064
-
27065
27062
  def split_relative(value):
27066
27063
  if value in ("", "."):
27067
27064
  return []
@@ -27074,10 +27071,8 @@ def split_relative(value):
27074
27071
  if part == "..":
27075
27072
  raise OSError(errno.EPERM, "path traversal is not allowed")
27076
27073
  return parts
27077
-
27078
27074
  def open_dir(path_value, dir_fd=None):
27079
27075
  return os.open(path_value, DIR_FLAGS, dir_fd=dir_fd)
27080
-
27081
27076
  def walk_dir(root_fd, segments, mkdir_enabled=False):
27082
27077
  current_fd = os.dup(root_fd)
27083
27078
  try:
@@ -27095,14 +27090,12 @@ def walk_dir(root_fd, segments, mkdir_enabled=False):
27095
27090
  except Exception:
27096
27091
  os.close(current_fd)
27097
27092
  raise
27098
-
27099
27093
  def parent_and_basename(root_fd, relative):
27100
27094
  segments = split_relative(relative)
27101
27095
  if not segments:
27102
27096
  raise OSError(errno.EPERM, "operation requires a non-root path")
27103
27097
  parent_fd = walk_dir(root_fd, segments[:-1])
27104
27098
  return parent_fd, segments[-1]
27105
-
27106
27099
  def encode_stat(st):
27107
27100
  mode = st.st_mode
27108
27101
  return {
@@ -27118,14 +27111,12 @@ def encode_stat(st):
27118
27111
  "size": st.st_size,
27119
27112
  "uid": st.st_uid,
27120
27113
  }
27121
-
27122
27114
  def reject_unsafe_endpoint(st):
27123
27115
  mode = st.st_mode
27124
27116
  if stat.S_ISLNK(mode):
27125
27117
  raise OSError(errno.ELOOP, "symlink endpoint is not allowed")
27126
27118
  if stat.S_ISREG(mode) and st.st_nlink > 1:
27127
27119
  raise OSError(errno.EPERM, "hardlinked file endpoint is not allowed")
27128
-
27129
27120
  def copy_bytes(source_fd, dest_fd):
27130
27121
  while True:
27131
27122
  chunk = os.read(source_fd, 65536)
@@ -27137,7 +27128,6 @@ def copy_bytes(source_fd, dest_fd):
27137
27128
  if written <= 0:
27138
27129
  raise OSError(errno.EIO, "short write")
27139
27130
  view = view[written:]
27140
-
27141
27131
  def write_all(fd, data):
27142
27132
  view = memoryview(data)
27143
27133
  while view:
@@ -27145,11 +27135,9 @@ def write_all(fd, data):
27145
27135
  if written <= 0:
27146
27136
  raise OSError(errno.EIO, "short write")
27147
27137
  view = view[written:]
27148
-
27149
27138
  def link_unsupported(exc):
27150
27139
  unsupported = (errno.EPERM, errno.EOPNOTSUPP, getattr(errno, "ENOTSUP", errno.EOPNOTSUPP))
27151
27140
  return getattr(exc, "errno", None) in unsupported
27152
-
27153
27141
  def link_no_replace(name, new_name, source_fd, target_fd):
27154
27142
  linked = False
27155
27143
  try:
@@ -27164,11 +27152,9 @@ def link_no_replace(name, new_name, source_fd, target_fd):
27164
27152
  os.fsync(source_fd)
27165
27153
  if source_fd != target_fd:
27166
27154
  os.fsync(target_fd)
27167
-
27168
27155
  def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basename, mode, expected=None, unlink_source=False):
27169
27156
  source_fd = os.open(source_name, READ_FLAGS, dir_fd=source_parent_fd)
27170
- dest_fd = None
27171
- success = False
27157
+ dest_fd = None; success = False; dest_stat = None
27172
27158
  try:
27173
27159
  if expected is not None:
27174
27160
  source_stat = os.fstat(source_fd)
@@ -27177,6 +27163,7 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
27177
27163
  dest_fd = os.open(basename, WRITE_FLAGS, mode, dir_fd=target_parent_fd)
27178
27164
  copy_bytes(source_fd, dest_fd)
27179
27165
  os.fsync(dest_fd)
27166
+ dest_stat = os.fstat(dest_fd)
27180
27167
  success = True
27181
27168
  finally:
27182
27169
  os.close(source_fd)
@@ -27192,19 +27179,40 @@ def copy_file_no_replace(source_parent_fd, source_name, target_parent_fd, basena
27192
27179
  try: os.unlink(basename, dir_fd=target_parent_fd)
27193
27180
  except FileNotFoundError: pass
27194
27181
  raise
27195
-
27196
- def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode):
27182
+ return dest_stat
27183
+ def same_identity(left, right):
27184
+ return left.st_dev == right.st_dev and left.st_ino == right.st_ino
27185
+ def verify_temp_name(parent_fd, temp_name, expected_stat):
27186
+ current_stat = os.lstat(temp_name, dir_fd=parent_fd)
27187
+ if stat.S_ISLNK(current_stat.st_mode) or not same_identity(current_stat, expected_stat):
27188
+ raise RuntimeError("fs-safe-temp-mismatch")
27189
+ def verify_committed_temp(parent_fd, basename, expected_stat):
27190
+ final_stat = os.lstat(basename, dir_fd=parent_fd)
27191
+ if not stat.S_ISLNK(final_stat.st_mode) and same_identity(final_stat, expected_stat):
27192
+ return final_stat
27193
+ try: os.unlink(basename, dir_fd=parent_fd)
27194
+ except FileNotFoundError: pass
27195
+ raise RuntimeError("fs-safe-temp-mismatch")
27196
+ def commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, expected_stat):
27197
+ verify_temp_name(parent_fd, temp_name, expected_stat)
27197
27198
  if overwrite:
27198
27199
  os.replace(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd)
27200
+ return verify_committed_temp(parent_fd, basename, expected_stat)
27199
27201
  else:
27200
27202
  try:
27201
27203
  os.link(temp_name, basename, src_dir_fd=parent_fd, dst_dir_fd=parent_fd, follow_symlinks=False)
27204
+ final_stat = verify_committed_temp(parent_fd, basename, expected_stat)
27202
27205
  os.unlink(temp_name, dir_fd=parent_fd)
27206
+ return final_stat
27203
27207
  except OSError as exc:
27204
27208
  if not link_unsupported(exc):
27205
27209
  raise
27206
- copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, unlink_source=True)
27207
-
27210
+ return copy_file_no_replace(parent_fd, temp_name, parent_fd, basename, mode, expected_stat, True)
27211
+ def assert_expected_root(root_fd, payload):
27212
+ if "rootDev" in payload or "rootIno" in payload:
27213
+ root_stat = os.fstat(root_fd)
27214
+ if root_stat.st_dev != int(payload["rootDev"]) or root_stat.st_ino != int(payload["rootIno"]):
27215
+ raise RuntimeError("fs-safe-root-mismatch")
27208
27216
  def stat_path(root_fd, payload):
27209
27217
  relative = payload.get("relativePath", "")
27210
27218
  segments = split_relative(relative)
@@ -27218,7 +27226,6 @@ def stat_path(root_fd, payload):
27218
27226
  return encode_stat(st)
27219
27227
  finally:
27220
27228
  os.close(parent_fd)
27221
-
27222
27229
  def readdir_path(root_fd, payload):
27223
27230
  dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")))
27224
27231
  try:
@@ -27234,12 +27241,9 @@ def readdir_path(root_fd, payload):
27234
27241
  return entries
27235
27242
  finally:
27236
27243
  os.close(dir_fd)
27237
-
27238
27244
  def mkdirp_path(root_fd, payload):
27239
27245
  dir_fd = walk_dir(root_fd, split_relative(payload.get("relativePath", "")), mkdir_enabled=True)
27240
- os.close(dir_fd)
27241
- return None
27242
-
27246
+ os.close(dir_fd); return None
27243
27247
  def remove_tree(parent_fd, basename):
27244
27248
  st = os.lstat(basename, dir_fd=parent_fd)
27245
27249
  if stat.S_ISDIR(st.st_mode) and not stat.S_ISLNK(st.st_mode):
@@ -27252,7 +27256,6 @@ def remove_tree(parent_fd, basename):
27252
27256
  os.rmdir(basename, dir_fd=parent_fd)
27253
27257
  else:
27254
27258
  os.unlink(basename, dir_fd=parent_fd)
27255
-
27256
27259
  def remove_path(root_fd, payload):
27257
27260
  parent_fd, basename = parent_and_basename(root_fd, payload.get("relativePath", ""))
27258
27261
  try:
@@ -27335,14 +27338,15 @@ def write_path(root_fd, payload):
27335
27338
  except FileNotFoundError:
27336
27339
  pass
27337
27340
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
27341
+ os.fchmod(temp_fd, mode)
27338
27342
  write_all(temp_fd, data)
27339
27343
  os.fsync(temp_fd)
27344
+ temp_stat = os.fstat(temp_fd)
27340
27345
  os.close(temp_fd)
27341
27346
  temp_fd = None
27342
- commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
27347
+ result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
27343
27348
  temp_name = None
27344
27349
  os.fsync(parent_fd)
27345
- result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
27346
27350
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
27347
27351
  finally:
27348
27352
  if temp_fd is not None:
@@ -27373,6 +27377,7 @@ def copy_path(root_fd, payload):
27373
27377
  raise RuntimeError("fs-safe-too-large:%d:%d" % (max_bytes, source_stat.st_size))
27374
27378
  parent_fd = walk_dir(root_fd, split_relative(payload.get("relativeParentPath", "")), bool(payload.get("mkdir", True)))
27375
27379
  temp_name, temp_fd = create_temp_file(parent_fd, basename, mode)
27380
+ os.fchmod(temp_fd, mode)
27376
27381
  written_bytes = 0
27377
27382
  while True:
27378
27383
  chunk = os.read(source_fd, 65536)
@@ -27388,12 +27393,12 @@ def copy_path(root_fd, payload):
27388
27393
  raise OSError(errno.EIO, "short write")
27389
27394
  view = view[written:]
27390
27395
  os.fsync(temp_fd)
27396
+ temp_stat = os.fstat(temp_fd)
27391
27397
  os.close(temp_fd)
27392
27398
  temp_fd = None
27393
- commit_temp_file(parent_fd, temp_name, basename, overwrite, mode)
27399
+ result_stat = commit_temp_file(parent_fd, temp_name, basename, overwrite, mode, temp_stat)
27394
27400
  temp_name = None
27395
27401
  os.fsync(parent_fd)
27396
- result_stat = os.stat(basename, dir_fd=parent_fd, follow_symlinks=False)
27397
27402
  return {"dev": result_stat.st_dev, "ino": result_stat.st_ino}
27398
27403
  finally:
27399
27404
  os.close(source_fd)
@@ -27410,6 +27415,7 @@ def copy_path(root_fd, payload):
27410
27415
  def run_operation(operation, root_path, payload):
27411
27416
  root_fd = open_dir(root_path)
27412
27417
  try:
27418
+ assert_expected_root(root_fd, payload)
27413
27419
  if operation == "stat":
27414
27420
  return stat_path(root_fd, payload)
27415
27421
  if operation == "readdir":
@@ -27514,6 +27520,12 @@ function mapWorkerError(response) {
27514
27520
  if (message.includes("fs-safe-source-mismatch")) {
27515
27521
  return new errors_FsSafeError("path-mismatch", "source path changed during copy");
27516
27522
  }
27523
+ if (message.includes("fs-safe-temp-mismatch")) {
27524
+ return new errors_FsSafeError("path-mismatch", "temp path changed during write");
27525
+ }
27526
+ if (message.includes("fs-safe-root-mismatch")) {
27527
+ return new errors_FsSafeError("path-mismatch", "root path changed during operation");
27528
+ }
27517
27529
  if (message.includes("fs-safe-directory-noreplace-unsupported")) {
27518
27530
  return new errors_FsSafeError("invalid-path", "directory moves require overwrite: true");
27519
27531
  }
@@ -27694,9 +27706,7 @@ function validatePinnedOperationPayload(payload) {
27694
27706
  }
27695
27707
  }
27696
27708
  function isPinnedHelperUnavailable(error) {
27697
- return error instanceof Error &&
27698
- "code" in error &&
27699
- error.code === "helper-unavailable";
27709
+ return error instanceof Error && "code" in error && error.code === "helper-unavailable";
27700
27710
  }
27701
27711
  function validatePinnedRelativePath(relativePath) {
27702
27712
  if (relativePath.length === 0 || relativePath === ".") {
@@ -27794,29 +27804,21 @@ function assertWithinMaxBytes(bytes, maxBytes) {
27794
27804
  throw new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`);
27795
27805
  }
27796
27806
  }
27797
- function pinned_write_createMaxBytesTransform(maxBytes) {
27798
- if (maxBytes === undefined) {
27799
- return undefined;
27800
- }
27807
+ async function writeStreamToHandle(stream, handle, maxBytes) {
27801
27808
  let bytes = 0;
27802
- return new external_node_stream_.Transform({
27803
- transform(chunk, _encoding, callback) {
27804
- bytes += chunk.byteLength;
27805
- if (bytes > maxBytes) {
27806
- callback(new errors_FsSafeError("too-large", `file exceeds limit of ${maxBytes} bytes (got at least ${bytes})`));
27807
- return;
27809
+ for await (const chunk of stream) {
27810
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
27811
+ bytes += buffer.byteLength;
27812
+ assertWithinMaxBytes(bytes, maxBytes);
27813
+ let offset = 0;
27814
+ while (offset < buffer.byteLength) {
27815
+ const { bytesWritten } = await handle.write(buffer, offset, buffer.byteLength - offset);
27816
+ if (bytesWritten <= 0) {
27817
+ throw new errors_FsSafeError("helper-failed", "fallback stream write made no progress");
27808
27818
  }
27809
- callback(null, chunk);
27810
- },
27811
- });
27812
- }
27813
- async function pipelineWithMaxBytes(stream, destination, maxBytes) {
27814
- const limiter = pinned_write_createMaxBytesTransform(maxBytes);
27815
- if (limiter) {
27816
- await (0,external_node_stream_promises_.pipeline)(stream, limiter, destination);
27817
- return;
27819
+ offset += bytesWritten;
27820
+ }
27818
27821
  }
27819
- await (0,external_node_stream_promises_.pipeline)(stream, destination);
27820
27822
  }
27821
27823
  async function inputToBase64(input, maxBytes) {
27822
27824
  if (input.kind === "buffer") {
@@ -27841,7 +27843,7 @@ async function runPinnedWriteHelper(params) {
27841
27843
  relativeParentPath: params.relativeParentPath,
27842
27844
  });
27843
27845
  if (getFsSafePythonConfig().mode === "off") {
27844
- return await runPinnedWriteFallback(params);
27846
+ return await runPinnedWriteFallbackOrThrow(params);
27845
27847
  }
27846
27848
  if (params.input.kind === "stream") {
27847
27849
  try {
@@ -27849,7 +27851,7 @@ async function runPinnedWriteHelper(params) {
27849
27851
  }
27850
27852
  catch (error) {
27851
27853
  if (canFallbackFromPythonError(error)) {
27852
- return await runPinnedWriteFallback(params);
27854
+ return await runPinnedWriteFallbackOrThrow(params, error);
27853
27855
  }
27854
27856
  throw error;
27855
27857
  }
@@ -27862,6 +27864,7 @@ async function runPinnedWriteHelper(params) {
27862
27864
  mode: params.mode || 0o600,
27863
27865
  overwrite: params.overwrite !== false,
27864
27866
  relativeParentPath: params.relativeParentPath,
27867
+ ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
27865
27868
  };
27866
27869
  try {
27867
27870
  return await runPinnedPythonOperation({
@@ -27872,7 +27875,7 @@ async function runPinnedWriteHelper(params) {
27872
27875
  }
27873
27876
  catch (error) {
27874
27877
  if (canFallbackFromPythonError(error)) {
27875
- return await runPinnedWriteFallback(params);
27878
+ return await runPinnedWriteFallbackOrThrow(params, error);
27876
27879
  }
27877
27880
  throw error;
27878
27881
  }
@@ -27892,22 +27895,29 @@ async function runPinnedCopyHelper(params) {
27892
27895
  mode: params.mode || 0o600,
27893
27896
  overwrite: params.overwrite !== false,
27894
27897
  relativeParentPath: params.relativeParentPath,
27898
+ ...(params.rootIdentity ? { rootDev: params.rootIdentity.dev, rootIno: params.rootIdentity.ino } : {}),
27895
27899
  sourceDev: params.sourceIdentity.dev,
27896
27900
  sourceIno: params.sourceIdentity.ino,
27897
27901
  sourcePath: params.sourcePath,
27898
27902
  },
27899
27903
  });
27900
27904
  }
27905
+ async function runPinnedWriteFallbackOrThrow(params, cause) {
27906
+ if (process.platform !== "win32") {
27907
+ throw new errors_FsSafeError("helper-unavailable", "Python helper is required for pinned writes on this platform", { cause });
27908
+ }
27909
+ return await runPinnedWriteFallback(params);
27910
+ }
27901
27911
  async function runPinnedWriteFallback(params) {
27902
27912
  const parentPath = params.relativeParentPath
27903
27913
  ? external_node_path_.join(params.rootPath, ...params.relativeParentPath.split("/"))
27904
27914
  : params.rootPath;
27905
- const parentGuard = await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
27906
27915
  if (params.mkdir) {
27907
- await withAsyncDirectoryGuards([parentGuard], async () => {
27908
- await promises_.mkdir(parentPath, { recursive: true });
27909
- });
27916
+ await mkdirPathComponentsWithGuards({ rootReal: params.rootPath, targetPath: parentPath });
27910
27917
  }
27918
+ const parentGuard = params.mkdir
27919
+ ? await directory_guard_createAsyncDirectoryGuard(parentPath)
27920
+ : await directory_guard_createNearestExistingDirectoryGuard(params.rootPath, parentPath);
27911
27921
  const targetPath = external_node_path_.join(parentPath, params.basename);
27912
27922
  if (params.overwrite === false) {
27913
27923
  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), {
@@ -27929,7 +27939,7 @@ async function runPinnedWriteFallback(params) {
27929
27939
  }
27930
27940
  }
27931
27941
  else {
27932
- await pipelineWithMaxBytes(params.input.stream, handle.createWriteStream(), params.maxBytes);
27942
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
27933
27943
  }
27934
27944
  const stat = await handle.stat();
27935
27945
  created = false;
@@ -27950,7 +27960,9 @@ async function runPinnedWriteFallback(params) {
27950
27960
  ? external_node_fs_.constants.O_NOFOLLOW
27951
27961
  : 0);
27952
27962
  let handle;
27953
- let handleClosedByStream = false;
27963
+ let tempStat;
27964
+ let targetStat;
27965
+ let renamed = false;
27954
27966
  try {
27955
27967
  handle = await promises_.open(tempPath, tempFlags, params.mode);
27956
27968
  if (params.input.kind === "buffer") {
@@ -27963,29 +27975,36 @@ async function runPinnedWriteFallback(params) {
27963
27975
  }
27964
27976
  }
27965
27977
  else {
27966
- const writable = handle.createWriteStream();
27967
- writable.once("close", () => {
27968
- handleClosedByStream = true;
27969
- });
27970
- await pipelineWithMaxBytes(params.input.stream, writable, params.maxBytes);
27978
+ await writeStreamToHandle(params.input.stream, handle, params.maxBytes);
27971
27979
  }
27972
- if (!handleClosedByStream) {
27973
- await handle.close().catch(() => undefined);
27974
- handle = undefined;
27980
+ tempStat = await handle.stat();
27981
+ const tempPathStat = await promises_.lstat(tempPath);
27982
+ if (tempPathStat.isSymbolicLink() || !file_identity_sameFileIdentity(tempPathStat, tempStat)) {
27983
+ throw new errors_FsSafeError("path-mismatch", "fallback temp path changed during write");
27975
27984
  }
27985
+ const expectedTempStat = tempStat;
27986
+ await handle.close().catch(() => undefined);
27987
+ handle = undefined;
27976
27988
  await withAsyncDirectoryGuards([parentGuard], async () => {
27977
27989
  await promises_.rename(tempPath, targetPath);
27990
+ renamed = true;
27991
+ targetStat = await promises_.lstat(targetPath);
27992
+ if (targetStat.isSymbolicLink() || !file_identity_sameFileIdentity(targetStat, expectedTempStat)) {
27993
+ throw new errors_FsSafeError("path-mismatch", "fallback target changed during write");
27994
+ }
27978
27995
  });
27979
27996
  }
27980
27997
  catch (error) {
27981
- if (handle && !handleClosedByStream) {
27982
- await handle.close().catch(() => undefined);
27998
+ await handle?.close().catch(() => undefined);
27999
+ if (!renamed) {
28000
+ await promises_.rm(tempPath, { force: true }).catch(() => undefined);
27983
28001
  }
27984
- await promises_.rm(tempPath, { force: true }).catch(() => undefined);
27985
28002
  throw error;
27986
28003
  }
27987
- const stat = await promises_.stat(targetPath);
27988
- return { dev: stat.dev, ino: stat.ino };
28004
+ if (!targetStat) {
28005
+ throw new errors_FsSafeError("path-mismatch", "fallback target was not verified");
28006
+ }
28007
+ return { dev: targetStat.dev, ino: targetStat.ino };
27989
28008
  }
27990
28009
 
27991
28010
  // EXTERNAL MODULE: external "node:os"
@@ -28524,7 +28543,7 @@ function relativeInsideRoot(rootPath, targetPath) {
28524
28543
  if (!relative || relative === ".") {
28525
28544
  return "";
28526
28545
  }
28527
- if (relative.startsWith("..") || external_node_path_.isAbsolute(relative)) {
28546
+ if (isPathRelativeEscape(relative)) {
28528
28547
  return "";
28529
28548
  }
28530
28549
  return relative;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lousy-agents/mcp",
3
- "version": "5.14.9",
3
+ "version": "5.14.10",
4
4
  "description": "MCP server for lousy-agents - provides AI coding assistant tools via the Model Context Protocol",
5
5
  "type": "module",
6
6
  "repository": {