@zero-transfer/classic 0.4.7 → 0.4.8

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.cjs CHANGED
@@ -61,6 +61,7 @@ __export(classic_exports, {
61
61
  copyBetween: () => copyBetween,
62
62
  createAtomicDeployPlan: () => createAtomicDeployPlan,
63
63
  createBandwidthThrottle: () => createBandwidthThrottle,
64
+ createDefaultRetryPolicy: () => createDefaultRetryPolicy,
64
65
  createFtpProviderFactory: () => createFtpProviderFactory,
65
66
  createFtpsProviderFactory: () => createFtpsProviderFactory,
66
67
  createLocalProviderFactory: () => createLocalProviderFactory,
@@ -106,8 +107,10 @@ __export(classic_exports, {
106
107
  parseUnixListLine: () => parseUnixListLine,
107
108
  redactCommand: () => redactCommand,
108
109
  redactConnectionProfile: () => redactConnectionProfile,
110
+ redactErrorForLogging: () => redactErrorForLogging,
109
111
  redactObject: () => redactObject,
110
112
  redactSecretSource: () => redactSecretSource,
113
+ redactUrlForLogging: () => redactUrlForLogging,
111
114
  redactValue: () => redactValue,
112
115
  resolveConnectionProfileSecrets: () => resolveConnectionProfileSecrets,
113
116
  resolveOpenSshHost: () => resolveOpenSshHost,
@@ -128,6 +131,68 @@ module.exports = __toCommonJS(classic_exports);
128
131
  // src/client/ZeroTransfer.ts
129
132
  var import_node_events = require("events");
130
133
 
134
+ // src/logging/redaction.ts
135
+ var REDACTED = "[REDACTED]";
136
+ var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
137
+ var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
138
+ var URL_KEY_PATTERN = /(?:url|uri|href)$/i;
139
+ function isSensitiveKey(key) {
140
+ return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
141
+ }
142
+ function redactCommand(command) {
143
+ return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
144
+ return `${commandName.toUpperCase()} ${REDACTED}`;
145
+ });
146
+ }
147
+ function redactValue(value) {
148
+ if (typeof value === "string") {
149
+ return redactCommand(value);
150
+ }
151
+ if (Array.isArray(value)) {
152
+ return value.map((item) => redactValue(item));
153
+ }
154
+ if (value !== null && typeof value === "object") {
155
+ return redactObject(value);
156
+ }
157
+ return value;
158
+ }
159
+ function redactObject(input) {
160
+ return Object.fromEntries(
161
+ Object.entries(input).map(([key, value]) => {
162
+ if (isSensitiveKey(key)) {
163
+ return [key, REDACTED];
164
+ }
165
+ if (URL_KEY_PATTERN.test(key) && typeof value === "string") {
166
+ return [key, redactUrlForLogging(value)];
167
+ }
168
+ return [key, redactValue(value)];
169
+ })
170
+ );
171
+ }
172
+ function redactUrlForLogging(url) {
173
+ let parsed;
174
+ try {
175
+ parsed = typeof url === "string" ? new URL(url) : url;
176
+ } catch {
177
+ return REDACTED;
178
+ }
179
+ const origin = parsed.host.length > 0 ? `${parsed.protocol}//${parsed.host}` : parsed.protocol;
180
+ const query = parsed.search.length > 0 ? `?${REDACTED}` : "";
181
+ return `${origin}${parsed.pathname}${query}`;
182
+ }
183
+ function redactErrorForLogging(error) {
184
+ if (error !== null && typeof error === "object") {
185
+ const candidate = error;
186
+ if (typeof candidate.toJSON === "function") {
187
+ return redactObject(candidate.toJSON());
188
+ }
189
+ }
190
+ if (error instanceof Error) {
191
+ return redactObject({ message: error.message, name: error.name });
192
+ }
193
+ return { message: redactValue(typeof error === "string" ? error : String(error)) };
194
+ }
195
+
131
196
  // src/errors/ZeroTransferError.ts
132
197
  var ZeroTransferError = class extends Error {
133
198
  /** Stable machine-readable error code. */
@@ -169,6 +234,11 @@ var ZeroTransferError = class extends Error {
169
234
  /**
170
235
  * Serializes the error into a plain object suitable for logs or API responses.
171
236
  *
237
+ * `details` and `command` are passed through secret redaction so serialized
238
+ * errors never leak credentials, signed URLs, or raw protocol commands. The
239
+ * live {@link ZeroTransferError.details | details} property stays unredacted
240
+ * for programmatic consumers.
241
+ *
172
242
  * @returns A JSON-safe object containing public structured error fields.
173
243
  */
174
244
  toJSON() {
@@ -178,12 +248,12 @@ var ZeroTransferError = class extends Error {
178
248
  message: this.message,
179
249
  protocol: this.protocol,
180
250
  host: this.host,
181
- command: this.command,
251
+ command: this.command === void 0 ? void 0 : redactCommand(this.command),
182
252
  ftpCode: this.ftpCode,
183
253
  sftpCode: this.sftpCode,
184
254
  path: this.path,
185
255
  retryable: this.retryable,
186
- details: this.details
256
+ details: this.details === void 0 ? void 0 : redactObject(this.details)
187
257
  };
188
258
  }
189
259
  };
@@ -706,15 +776,20 @@ var ProviderRegistry = class {
706
776
  var TransferClient = class {
707
777
  /** Provider registry used by this client. */
708
778
  registry;
779
+ /** Execution defaults applied when call sites omit their own values. */
780
+ defaults;
709
781
  logger;
710
782
  /**
711
783
  * Creates a transfer client without opening any provider connections.
712
784
  *
713
- * @param options - Optional registry, provider factories, and logger.
785
+ * @param options - Optional registry, provider factories, logger, and execution defaults.
714
786
  */
715
787
  constructor(options = {}) {
716
788
  this.registry = options.registry ?? new ProviderRegistry();
717
789
  this.logger = options.logger ?? noopLogger;
790
+ if (options.defaults !== void 0) {
791
+ this.defaults = { ...options.defaults };
792
+ }
718
793
  for (const provider of options.providers ?? []) {
719
794
  this.registry.register(provider);
720
795
  }
@@ -1287,18 +1362,25 @@ var TransferEngine = class {
1287
1362
  for (let attemptNumber = 1; attemptNumber <= maxAttempts; attemptNumber += 1) {
1288
1363
  this.throwIfAborted(abortScope.signal, job);
1289
1364
  const attemptStartedAt = this.now();
1365
+ const attemptScope = createAttemptScope(
1366
+ abortScope.signal,
1367
+ options.timeout,
1368
+ job,
1369
+ attemptNumber
1370
+ );
1290
1371
  const context = this.createExecutionContext(
1291
1372
  job,
1292
1373
  attemptNumber,
1293
1374
  attemptStartedAt,
1294
1375
  options,
1295
- abortScope.signal,
1376
+ attemptScope.signal,
1296
1377
  (bytesTransferred) => {
1297
1378
  latestBytesTransferred = bytesTransferred;
1298
- }
1379
+ },
1380
+ attemptScope.notifyProgress
1299
1381
  );
1300
1382
  try {
1301
- const result = await runExecutor(executor, context, abortScope.signal, job);
1383
+ const result = await runExecutor(executor, context, attemptScope.signal, job);
1302
1384
  context.throwIfAborted();
1303
1385
  latestBytesTransferred = result.bytesTransferred;
1304
1386
  const completedAt = this.now();
@@ -1316,16 +1398,27 @@ var TransferEngine = class {
1316
1398
  summarizeError(error)
1317
1399
  );
1318
1400
  attempts.push(attempt);
1319
- if (error instanceof AbortError || error instanceof TimeoutError) {
1401
+ if (error instanceof AbortError || abortScope.signal?.aborted === true) {
1320
1402
  throw error;
1321
1403
  }
1322
- const retryInput = { attempt: attemptNumber, error, job };
1404
+ const retryInput = {
1405
+ attempt: attemptNumber,
1406
+ elapsedMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
1407
+ error,
1408
+ job
1409
+ };
1323
1410
  const shouldRetry = attemptNumber < maxAttempts && (options.retry?.shouldRetry?.(retryInput) ?? isRetryable(error));
1324
1411
  if (shouldRetry) {
1325
1412
  options.retry?.onRetry?.(retryInput);
1413
+ const delayMs = normalizeDelayMs(options.retry?.getDelayMs?.(retryInput));
1414
+ if (delayMs > 0) {
1415
+ await sleepWithAbort(delayMs, abortScope.signal, job);
1416
+ }
1326
1417
  continue;
1327
1418
  }
1328
1419
  throw createTransferFailure(job, error, attempts);
1420
+ } finally {
1421
+ attemptScope.dispose();
1329
1422
  }
1330
1423
  }
1331
1424
  throw createTransferFailure(job, void 0, attempts);
@@ -1333,12 +1426,13 @@ var TransferEngine = class {
1333
1426
  abortScope.dispose();
1334
1427
  }
1335
1428
  }
1336
- createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred) {
1429
+ createExecutionContext(job, attempt, startedAt, options, signal, updateBytesTransferred, notifyProgress) {
1337
1430
  const context = {
1338
1431
  attempt,
1339
1432
  job,
1340
1433
  reportProgress: (bytesTransferred, totalBytes) => {
1341
1434
  this.throwIfAborted(signal, job);
1435
+ notifyProgress();
1342
1436
  updateBytesTransferred(bytesTransferred);
1343
1437
  const progressInput = {
1344
1438
  bytesTransferred,
@@ -1407,6 +1501,96 @@ function createAbortScope(parentSignal, timeout, job) {
1407
1501
  signal: controller.signal
1408
1502
  };
1409
1503
  }
1504
+ function createAttemptScope(parentSignal, timeout, job, attempt) {
1505
+ const attemptTimeoutMs = normalizeTimeoutMs(timeout?.attemptTimeoutMs);
1506
+ const stallTimeoutMs = normalizeTimeoutMs(timeout?.stallTimeoutMs);
1507
+ if (attemptTimeoutMs === void 0 && stallTimeoutMs === void 0) {
1508
+ const scope = {
1509
+ dispose: () => void 0,
1510
+ notifyProgress: () => void 0
1511
+ };
1512
+ if (parentSignal !== void 0) scope.signal = parentSignal;
1513
+ return scope;
1514
+ }
1515
+ const controller = new AbortController();
1516
+ const retryable = timeout?.retryable ?? true;
1517
+ const abortFromParent = () => controller.abort(parentSignal?.reason);
1518
+ if (parentSignal?.aborted === true) {
1519
+ abortFromParent();
1520
+ } else {
1521
+ parentSignal?.addEventListener("abort", abortFromParent, { once: true });
1522
+ }
1523
+ const attemptTimer = attemptTimeoutMs === void 0 ? void 0 : setTimeout(() => {
1524
+ controller.abort(
1525
+ new TimeoutError({
1526
+ details: { attempt, attemptTimeoutMs, jobId: job.id, operation: job.operation },
1527
+ message: `Transfer attempt ${String(attempt)} timed out after ${String(attemptTimeoutMs)}ms: ${job.id}`,
1528
+ retryable
1529
+ })
1530
+ );
1531
+ }, attemptTimeoutMs);
1532
+ let stallTimer;
1533
+ const armStallWatchdog = () => {
1534
+ if (stallTimeoutMs === void 0 || controller.signal.aborted) return;
1535
+ if (stallTimer !== void 0) clearTimeout(stallTimer);
1536
+ stallTimer = setTimeout(() => {
1537
+ controller.abort(
1538
+ new TimeoutError({
1539
+ details: { attempt, jobId: job.id, operation: job.operation, stallTimeoutMs },
1540
+ message: `Transfer attempt ${String(attempt)} stalled (no progress for ${String(stallTimeoutMs)}ms): ${job.id}`,
1541
+ retryable
1542
+ })
1543
+ );
1544
+ }, stallTimeoutMs);
1545
+ };
1546
+ armStallWatchdog();
1547
+ return {
1548
+ dispose: () => {
1549
+ if (attemptTimer !== void 0) clearTimeout(attemptTimer);
1550
+ if (stallTimer !== void 0) clearTimeout(stallTimer);
1551
+ parentSignal?.removeEventListener("abort", abortFromParent);
1552
+ },
1553
+ notifyProgress: armStallWatchdog,
1554
+ signal: controller.signal
1555
+ };
1556
+ }
1557
+ function sleepWithAbort(delayMs, signal, job) {
1558
+ return new Promise((resolve, reject) => {
1559
+ if (signal === void 0) {
1560
+ setTimeout(resolve, delayMs);
1561
+ return;
1562
+ }
1563
+ if (signal.aborted) {
1564
+ reject(toAbortFailure(signal, job));
1565
+ return;
1566
+ }
1567
+ const rejectAbort = () => {
1568
+ clearTimeout(timer);
1569
+ reject(toAbortFailure(signal, job));
1570
+ };
1571
+ const timer = setTimeout(() => {
1572
+ signal.removeEventListener("abort", rejectAbort);
1573
+ resolve();
1574
+ }, delayMs);
1575
+ signal.addEventListener("abort", rejectAbort, { once: true });
1576
+ });
1577
+ }
1578
+ function toAbortFailure(signal, job) {
1579
+ if (signal.reason instanceof ZeroTransferError) {
1580
+ return signal.reason;
1581
+ }
1582
+ return new AbortError({
1583
+ details: { jobId: job.id, operation: job.operation },
1584
+ message: `Transfer job aborted: ${job.id}`,
1585
+ retryable: false
1586
+ });
1587
+ }
1588
+ function normalizeDelayMs(value) {
1589
+ if (value === void 0 || !Number.isFinite(value) || value <= 0) {
1590
+ return 0;
1591
+ }
1592
+ return Math.floor(value);
1593
+ }
1410
1594
  function normalizeTimeoutMs(value) {
1411
1595
  if (value === void 0 || !Number.isFinite(value) || value <= 0) {
1412
1596
  return void 0;
@@ -1575,7 +1759,7 @@ async function runRoute(options) {
1575
1759
  const executor = createProviderTransferExecutor({
1576
1760
  resolveSession: ({ role }) => sessions.get(role)
1577
1761
  });
1578
- return await engine.execute(job, executor, buildExecuteOptions(options));
1762
+ return await engine.execute(job, executor, buildExecuteOptions(options, client));
1579
1763
  } finally {
1580
1764
  if (destinationSession !== void 0) {
1581
1765
  await destinationSession.disconnect();
@@ -1612,12 +1796,14 @@ function defaultJobId(route, now) {
1612
1796
  const timestamp = (now?.() ?? /* @__PURE__ */ new Date()).getTime();
1613
1797
  return `route:${route.id}:${timestamp.toString(36)}`;
1614
1798
  }
1615
- function buildExecuteOptions(options) {
1799
+ function buildExecuteOptions(options, client) {
1616
1800
  const execute = {};
1801
+ const retry = options.retry ?? client.defaults?.retry;
1802
+ const timeout = options.timeout ?? client.defaults?.timeout;
1617
1803
  if (options.signal !== void 0) execute.signal = options.signal;
1618
- if (options.retry !== void 0) execute.retry = options.retry;
1804
+ if (retry !== void 0) execute.retry = retry;
1619
1805
  if (options.onProgress !== void 0) execute.onProgress = options.onProgress;
1620
- if (options.timeout !== void 0) execute.timeout = options.timeout;
1806
+ if (timeout !== void 0) execute.timeout = timeout;
1621
1807
  if (options.bandwidthLimit !== void 0) execute.bandwidthLimit = options.bandwidthLimit;
1622
1808
  return execute;
1623
1809
  }
@@ -1674,41 +1860,6 @@ function defaultRouteSuffix(source, destination) {
1674
1860
  return `${source}->${destination}`;
1675
1861
  }
1676
1862
 
1677
- // src/logging/redaction.ts
1678
- var REDACTED = "[REDACTED]";
1679
- var SENSITIVE_KEY_PATTERN = /(?:password|passphrase|privatekey|token|secret|username|user)$/i;
1680
- var SECRET_COMMAND_PATTERN = /^(PASS|USER|ACCT)\s+(.+)$/i;
1681
- function isSensitiveKey(key) {
1682
- return SENSITIVE_KEY_PATTERN.test(key.replace(/[_-]/g, ""));
1683
- }
1684
- function redactCommand(command) {
1685
- return command.replace(SECRET_COMMAND_PATTERN, (_fullMatch, commandName) => {
1686
- return `${commandName.toUpperCase()} ${REDACTED}`;
1687
- });
1688
- }
1689
- function redactValue(value) {
1690
- if (typeof value === "string") {
1691
- return redactCommand(value);
1692
- }
1693
- if (Array.isArray(value)) {
1694
- return value.map((item) => redactValue(item));
1695
- }
1696
- if (value !== null && typeof value === "object") {
1697
- return redactObject(value);
1698
- }
1699
- return value;
1700
- }
1701
- function redactObject(input) {
1702
- return Object.fromEntries(
1703
- Object.entries(input).map(([key, value]) => {
1704
- if (isSensitiveKey(key)) {
1705
- return [key, REDACTED];
1706
- }
1707
- return [key, redactValue(value)];
1708
- })
1709
- );
1710
- }
1711
-
1712
1863
  // src/profiles/SecretSource.ts
1713
1864
  var import_node_buffer2 = require("buffer");
1714
1865
  var import_promises = require("fs/promises");
@@ -2080,11 +2231,11 @@ var import_promises2 = require("fs/promises");
2080
2231
  var import_node_path2 = __toESM(require("path"));
2081
2232
 
2082
2233
  // src/utils/path.ts
2083
- var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n]/;
2234
+ var UNSAFE_FTP_ARGUMENT_PATTERN = /[\r\n\0]/;
2084
2235
  function assertSafeFtpArgument(value, label = "path") {
2085
2236
  if (UNSAFE_FTP_ARGUMENT_PATTERN.test(value)) {
2086
2237
  throw new ConfigurationError({
2087
- message: `Unsafe FTP ${label}: CR and LF characters are not allowed`,
2238
+ message: `Unsafe FTP ${label}: CR, LF, and NUL characters are not allowed`,
2088
2239
  retryable: false,
2089
2240
  details: {
2090
2241
  label
@@ -3386,7 +3537,6 @@ function expandAlgorithms(values) {
3386
3537
  }
3387
3538
 
3388
3539
  // src/profiles/importers/FileZillaImporter.ts
3389
- var import_node_buffer5 = require("buffer");
3390
3540
  function importFileZillaSites(xml) {
3391
3541
  const events = tokenizeXml(xml);
3392
3542
  if (events.length === 0) {
@@ -3402,7 +3552,6 @@ function importFileZillaSites(xml) {
3402
3552
  const folderNamePending = [];
3403
3553
  let inServer = false;
3404
3554
  let serverFields = {};
3405
- let serverPasswordEncoding;
3406
3555
  let activeTag;
3407
3556
  let captureFolderName = false;
3408
3557
  for (const event of events) {
@@ -3415,13 +3564,9 @@ function importFileZillaSites(xml) {
3415
3564
  if (event.name === "Server") {
3416
3565
  inServer = true;
3417
3566
  serverFields = {};
3418
- serverPasswordEncoding = void 0;
3419
3567
  continue;
3420
3568
  }
3421
3569
  activeTag = event.name;
3422
- if (event.name === "Pass" && inServer) {
3423
- serverPasswordEncoding = event.attributes["encoding"];
3424
- }
3425
3570
  if (event.name === "Name" && !inServer && folderNamePending.length > 0) {
3426
3571
  captureFolderName = true;
3427
3572
  }
@@ -3447,7 +3592,7 @@ function importFileZillaSites(xml) {
3447
3592
  }
3448
3593
  if (event.name === "Server") {
3449
3594
  const folder = folderStack.filter((segment) => segment !== "");
3450
- const result = buildSiteFromFields(serverFields, serverPasswordEncoding);
3595
+ const result = buildSiteFromFields(serverFields);
3451
3596
  if (result.kind === "site") {
3452
3597
  sites.push({ ...result.site, folder });
3453
3598
  } else {
@@ -3459,7 +3604,6 @@ function importFileZillaSites(xml) {
3459
3604
  }
3460
3605
  inServer = false;
3461
3606
  serverFields = {};
3462
- serverPasswordEncoding = void 0;
3463
3607
  activeTag = void 0;
3464
3608
  continue;
3465
3609
  }
@@ -3468,7 +3612,7 @@ function importFileZillaSites(xml) {
3468
3612
  }
3469
3613
  return { sites, skipped };
3470
3614
  }
3471
- function buildSiteFromFields(fields, passwordEncoding) {
3615
+ function buildSiteFromFields(fields) {
3472
3616
  const name = (fields["Name"] ?? fields["Host"] ?? "Untitled").trim();
3473
3617
  const host = (fields["Host"] ?? "").trim();
3474
3618
  if (host === "") return { kind: "skipped", name };
@@ -3487,18 +3631,9 @@ function buildSiteFromFields(fields, passwordEncoding) {
3487
3631
  }
3488
3632
  const user = fields["User"]?.trim();
3489
3633
  if (user !== void 0 && user !== "") profile.username = { value: user };
3490
- let password;
3491
3634
  const rawPass = fields["Pass"];
3492
- if (rawPass !== void 0 && rawPass !== "") {
3493
- if (passwordEncoding === "base64") {
3494
- password = import_node_buffer5.Buffer.from(rawPass, "base64").toString("utf8");
3495
- } else {
3496
- password = rawPass;
3497
- }
3498
- if (password !== void 0 && password !== "") profile.password = { value: password };
3499
- }
3500
- const site = { name, profile };
3501
- if (password !== void 0) site.password = password;
3635
+ const hasStoredPassword = rawPass !== void 0 && rawPass !== "";
3636
+ const site = { hasStoredPassword, name, profile };
3502
3637
  const logonText = fields["Logontype"];
3503
3638
  if (logonText !== void 0) {
3504
3639
  const logonType = Number.parseInt(logonText.trim(), 10);
@@ -3741,6 +3876,62 @@ function mapFtp550(details) {
3741
3876
  return new PermissionDeniedError(details);
3742
3877
  }
3743
3878
 
3879
+ // src/transfers/createDefaultRetryPolicy.ts
3880
+ var DEFAULT_MAX_ATTEMPTS = 4;
3881
+ var DEFAULT_BASE_DELAY_MS = 250;
3882
+ var DEFAULT_MAX_DELAY_MS = 3e4;
3883
+ var DEFAULT_MAX_ELAPSED_MS = 3e5;
3884
+ function createDefaultRetryPolicy(options = {}) {
3885
+ const maxAttempts = normalizePositiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
3886
+ const baseDelayMs = normalizeNonNegative(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
3887
+ const maxDelayMs = normalizeNonNegative(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
3888
+ const maxElapsedMs = normalizeNonNegative(options.maxElapsedMs, DEFAULT_MAX_ELAPSED_MS);
3889
+ const random = options.random ?? Math.random;
3890
+ return {
3891
+ getDelayMs(input) {
3892
+ const retryAfterMs = readRetryAfterMs(input.error);
3893
+ if (retryAfterMs !== void 0) {
3894
+ return retryAfterMs;
3895
+ }
3896
+ const exponentialMs = baseDelayMs * 2 ** (input.attempt - 1);
3897
+ const cappedMs = Math.min(maxDelayMs, exponentialMs);
3898
+ return Math.floor(random() * cappedMs);
3899
+ },
3900
+ maxAttempts,
3901
+ shouldRetry(input) {
3902
+ if (!(input.error instanceof ZeroTransferError) || !input.error.retryable) {
3903
+ return false;
3904
+ }
3905
+ if (input.elapsedMs >= maxElapsedMs) {
3906
+ return false;
3907
+ }
3908
+ const retryAfterMs = readRetryAfterMs(input.error);
3909
+ if (retryAfterMs !== void 0 && input.elapsedMs + retryAfterMs > maxElapsedMs) {
3910
+ return false;
3911
+ }
3912
+ return true;
3913
+ }
3914
+ };
3915
+ }
3916
+ function readRetryAfterMs(error) {
3917
+ if (!(error instanceof ZeroTransferError)) return void 0;
3918
+ const value = error.details?.["retryAfterMs"];
3919
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return void 0;
3920
+ return Math.floor(value);
3921
+ }
3922
+ function normalizePositiveInteger(value, fallback) {
3923
+ if (value === void 0 || !Number.isFinite(value) || value < 1) {
3924
+ return fallback;
3925
+ }
3926
+ return Math.floor(value);
3927
+ }
3928
+ function normalizeNonNegative(value, fallback) {
3929
+ if (value === void 0 || !Number.isFinite(value) || value < 0) {
3930
+ return fallback;
3931
+ }
3932
+ return Math.floor(value);
3933
+ }
3934
+
3744
3935
  // src/transfers/TransferPlan.ts
3745
3936
  function createTransferPlan(input) {
3746
3937
  const plan = {
@@ -3838,8 +4029,8 @@ var TransferQueue = class {
3838
4029
  this.concurrency = normalizeConcurrency(options.concurrency);
3839
4030
  this.defaultExecutor = options.executor;
3840
4031
  this.resolveExecutor = options.resolveExecutor;
3841
- this.retry = options.retry;
3842
- this.timeout = options.timeout;
4032
+ this.retry = options.retry ?? options.client?.defaults?.retry;
4033
+ this.timeout = options.timeout ?? options.client?.defaults?.timeout;
3843
4034
  this.bandwidthLimit = options.bandwidthLimit;
3844
4035
  this.onProgress = options.onProgress;
3845
4036
  this.onReceipt = options.onReceipt;
@@ -4916,7 +5107,7 @@ function isMainModule(importMetaUrl) {
4916
5107
  }
4917
5108
 
4918
5109
  // src/providers/classic/ftp/FtpProvider.ts
4919
- var import_node_buffer6 = require("buffer");
5110
+ var import_node_buffer5 = require("buffer");
4920
5111
  var import_node_net = require("net");
4921
5112
  var import_node_tls = require("tls");
4922
5113
 
@@ -5834,38 +6025,53 @@ async function expectCompletion(control, command, path2) {
5834
6025
  const response = await control.sendCommand(command);
5835
6026
  assertPathCommandSucceeded(response, command, path2, control.providerId);
5836
6027
  }
5837
- async function readPassiveDataCommand(control, command, path2, options = {}) {
5838
- const dataConnection = await openPassiveDataCommand(control, command, path2, options);
6028
+ async function readPassiveLinesCommand(control, command, path2, onLine) {
6029
+ const dataConnection = await openPassiveDataCommand(control, command, path2);
5839
6030
  try {
5840
- const payload = await collectPassiveData(
5841
- dataConnection,
5842
- control.operationTimeoutMs,
5843
- path2,
5844
- control.providerId
5845
- );
6031
+ const failure = await consumePassiveLines(dataConnection, control.operationTimeoutMs, {
6032
+ command,
6033
+ onLine,
6034
+ path: path2,
6035
+ providerId: control.providerId
6036
+ });
5846
6037
  const finalResponse = await control.readFinalResponse({
5847
6038
  command,
5848
6039
  operation: "data command completion",
5849
6040
  path: path2
5850
6041
  });
5851
6042
  assertPathCommandSucceeded(finalResponse, command, path2, control.providerId);
5852
- return payload;
6043
+ if (failure !== void 0) throw failure;
5853
6044
  } catch (error) {
5854
6045
  dataConnection.close();
5855
6046
  throw error;
5856
6047
  }
5857
6048
  }
5858
6049
  async function readDirectoryEntries(control, path2) {
6050
+ const entries = [];
6051
+ const collectEntry = (entry) => {
6052
+ if (entry.name === "." || entry.name === "..") return;
6053
+ entries.push(entry);
6054
+ };
5859
6055
  try {
5860
- const payload2 = await readPassiveDataCommand(control, `MLSD ${path2}`, path2);
5861
- return parseMlsdList(payload2.toString("utf8"), path2);
6056
+ await readPassiveLinesCommand(control, `MLSD ${path2}`, path2, (rawLine) => {
6057
+ const line = rawLine.trimEnd();
6058
+ if (line.length === 0) return;
6059
+ collectEntry(parseMlsdLine(line, path2));
6060
+ });
6061
+ return entries;
5862
6062
  } catch (error) {
5863
6063
  if (!isUnsupportedFtpCommandError(error, "MLSD")) {
5864
6064
  throw error;
5865
6065
  }
5866
6066
  }
5867
- const payload = await readPassiveDataCommand(control, `LIST ${path2}`, path2);
5868
- return parseUnixList(payload.toString("utf8"), path2);
6067
+ entries.length = 0;
6068
+ const now = /* @__PURE__ */ new Date();
6069
+ await readPassiveLinesCommand(control, `LIST ${path2}`, path2, (rawLine) => {
6070
+ const line = rawLine.trimEnd();
6071
+ if (line.length === 0 || line.toLowerCase().startsWith("total ")) return;
6072
+ collectEntry(parseUnixListLine(line, path2, now));
6073
+ });
6074
+ return entries;
5869
6075
  }
5870
6076
  async function openPassiveDataCommand(control, command, path2, options = {}) {
5871
6077
  const offset = normalizeOptionalByteCount3(options.offset, "offset", path2);
@@ -6038,22 +6244,58 @@ function openPassiveDataConnection(endpoint, timeoutMs, path2, control) {
6038
6244
  }
6039
6245
  };
6040
6246
  }
6041
- async function collectPassiveData(dataConnection, timeoutMs, path2, providerId) {
6042
- const chunks = [];
6247
+ var MAX_LIST_LINE_BYTES = 64 * 1024;
6248
+ async function consumePassiveLines(dataConnection, timeoutMs, input) {
6249
+ let carry = import_node_buffer5.Buffer.alloc(0);
6250
+ let failure;
6043
6251
  const clearIdleTimeout = setSocketTimeout(dataConnection.socket, timeoutMs, {
6044
6252
  host: dataConnection.endpoint.host,
6045
6253
  operation: "passive data transfer",
6046
- path: path2,
6047
- providerId
6254
+ path: input.path,
6255
+ providerId: input.providerId
6256
+ });
6257
+ const overlongLineFailure = () => new ParseError({
6258
+ details: { command: input.command, limitBytes: MAX_LIST_LINE_BYTES, path: input.path },
6259
+ message: `FTP listing line exceeded ${String(MAX_LIST_LINE_BYTES)} bytes for ${input.command}`,
6260
+ retryable: false
6048
6261
  });
6262
+ const emit = (lineBytes) => {
6263
+ if (failure !== void 0) return;
6264
+ let end = lineBytes.length;
6265
+ if (end > 0 && lineBytes[end - 1] === 13) end -= 1;
6266
+ if (end === 0) return;
6267
+ if (end > MAX_LIST_LINE_BYTES) {
6268
+ failure = overlongLineFailure();
6269
+ return;
6270
+ }
6271
+ try {
6272
+ input.onLine(lineBytes.toString("utf8", 0, end));
6273
+ } catch (error) {
6274
+ failure = error instanceof Error ? error : new Error(String(error));
6275
+ }
6276
+ };
6049
6277
  try {
6050
6278
  for await (const chunk of dataConnection.socket) {
6051
- chunks.push(import_node_buffer6.Buffer.from(chunk));
6052
- }
6279
+ if (failure !== void 0) continue;
6280
+ const data = carry.length > 0 ? import_node_buffer5.Buffer.concat([carry, chunk]) : chunk;
6281
+ let start = 0;
6282
+ let newline = data.indexOf(10, start);
6283
+ while (newline !== -1) {
6284
+ emit(data.subarray(start, newline));
6285
+ start = newline + 1;
6286
+ newline = data.indexOf(10, start);
6287
+ }
6288
+ carry = import_node_buffer5.Buffer.from(data.subarray(start));
6289
+ if (carry.length > MAX_LIST_LINE_BYTES && failure === void 0) {
6290
+ failure = overlongLineFailure();
6291
+ }
6292
+ if (failure !== void 0) carry = import_node_buffer5.Buffer.alloc(0);
6293
+ }
6294
+ if (carry.length > 0) emit(carry);
6053
6295
  } finally {
6054
6296
  clearIdleTimeout();
6055
6297
  }
6056
- return import_node_buffer6.Buffer.concat(chunks);
6298
+ return failure;
6057
6299
  }
6058
6300
  async function* createPassiveReadSource(control, dataConnection, command, path2, range, request) {
6059
6301
  let bytesEmitted = 0;
@@ -6070,7 +6312,7 @@ async function* createPassiveReadSource(control, dataConnection, command, path2,
6070
6312
  });
6071
6313
  for await (const chunk of dataConnection.socket) {
6072
6314
  request.throwIfAborted();
6073
- const buffer = import_node_buffer6.Buffer.from(chunk);
6315
+ const buffer = import_node_buffer5.Buffer.from(chunk);
6074
6316
  if (range.length === void 0) {
6075
6317
  bytesEmitted += buffer.byteLength;
6076
6318
  yield new Uint8Array(buffer);
@@ -6302,6 +6544,13 @@ function createTlsPinnedFingerprints(profile) {
6302
6544
  if (pinnedFingerprint256 === void 0) {
6303
6545
  return void 0;
6304
6546
  }
6547
+ if (profile.tls?.rejectUnauthorized === false) {
6548
+ throw new ConfigurationError({
6549
+ message: "FTPS tls.pinnedFingerprint256 cannot be combined with rejectUnauthorized: false; pin verification runs after the TLS handshake, so chain validation must stay enabled. For self-signed certificates supply tls.ca instead of disabling validation.",
6550
+ protocol: FTPS_PROVIDER_ID,
6551
+ retryable: false
6552
+ });
6553
+ }
6305
6554
  const fingerprints = Array.isArray(pinnedFingerprint256) ? pinnedFingerprint256 : [pinnedFingerprint256];
6306
6555
  if (fingerprints.length === 0) {
6307
6556
  throw new ConfigurationError({
@@ -6356,9 +6605,9 @@ function normalizeCertificateFingerprint256(certificate) {
6356
6605
  }
6357
6606
  function normalizeTlsSecretValue(value) {
6358
6607
  if (Array.isArray(value)) {
6359
- return value.map((item) => import_node_buffer6.Buffer.isBuffer(item) ? import_node_buffer6.Buffer.from(item) : item);
6608
+ return value.map((item) => import_node_buffer5.Buffer.isBuffer(item) ? import_node_buffer5.Buffer.from(item) : item);
6360
6609
  }
6361
- return import_node_buffer6.Buffer.isBuffer(value) ? import_node_buffer6.Buffer.from(value) : value;
6610
+ return import_node_buffer5.Buffer.isBuffer(value) ? import_node_buffer5.Buffer.from(value) : value;
6362
6611
  }
6363
6612
  async function authenticateFtpSession(control, username, password, host) {
6364
6613
  const safeUsername = assertSafeFtpArgument(username, "username");
@@ -6537,7 +6786,7 @@ function compareEntries5(left, right) {
6537
6786
  return left.path.localeCompare(right.path);
6538
6787
  }
6539
6788
  function secretToString(value) {
6540
- return import_node_buffer6.Buffer.isBuffer(value) ? value.toString("utf8") : value;
6789
+ return import_node_buffer5.Buffer.isBuffer(value) ? value.toString("utf8") : value;
6541
6790
  }
6542
6791
 
6543
6792
  // src/providers/classic/ftp/FtpFeatureParser.ts
@@ -6582,12 +6831,12 @@ function normalizeFeatureLines(input) {
6582
6831
  }
6583
6832
 
6584
6833
  // src/providers/native/sftp/NativeSftpProvider.ts
6585
- var import_node_buffer21 = require("buffer");
6834
+ var import_node_buffer20 = require("buffer");
6586
6835
  var import_node_crypto9 = require("crypto");
6587
6836
  var import_node_net2 = require("net");
6588
6837
 
6589
6838
  // src/protocols/ssh/binary/SshDataWriter.ts
6590
- var import_node_buffer7 = require("buffer");
6839
+ var import_node_buffer6 = require("buffer");
6591
6840
  var MAX_UINT32 = 4294967295;
6592
6841
  var MAX_UINT64 = (1n << 64n) - 1n;
6593
6842
  var SshDataWriter = class {
@@ -6595,7 +6844,7 @@ var SshDataWriter = class {
6595
6844
  length = 0;
6596
6845
  writeByte(value) {
6597
6846
  this.assertByte(value, "byte");
6598
- const chunk = import_node_buffer7.Buffer.alloc(1);
6847
+ const chunk = import_node_buffer6.Buffer.alloc(1);
6599
6848
  chunk.writeUInt8(value, 0);
6600
6849
  return this.push(chunk);
6601
6850
  }
@@ -6603,7 +6852,7 @@ var SshDataWriter = class {
6603
6852
  return this.writeByte(value ? 1 : 0);
6604
6853
  }
6605
6854
  writeBytes(value) {
6606
- return this.push(import_node_buffer7.Buffer.from(value));
6855
+ return this.push(import_node_buffer6.Buffer.from(value));
6607
6856
  }
6608
6857
  writeUint32(value) {
6609
6858
  if (!Number.isInteger(value) || value < 0 || value > MAX_UINT32) {
@@ -6613,7 +6862,7 @@ var SshDataWriter = class {
6613
6862
  retryable: false
6614
6863
  });
6615
6864
  }
6616
- const chunk = import_node_buffer7.Buffer.alloc(4);
6865
+ const chunk = import_node_buffer6.Buffer.alloc(4);
6617
6866
  chunk.writeUInt32BE(value, 0);
6618
6867
  return this.push(chunk);
6619
6868
  }
@@ -6625,12 +6874,12 @@ var SshDataWriter = class {
6625
6874
  retryable: false
6626
6875
  });
6627
6876
  }
6628
- const chunk = import_node_buffer7.Buffer.alloc(8);
6877
+ const chunk = import_node_buffer6.Buffer.alloc(8);
6629
6878
  chunk.writeBigUInt64BE(value, 0);
6630
6879
  return this.push(chunk);
6631
6880
  }
6632
6881
  writeString(value, encoding = "utf8") {
6633
- const payload = typeof value === "string" ? import_node_buffer7.Buffer.from(value, encoding) : import_node_buffer7.Buffer.from(value);
6882
+ const payload = typeof value === "string" ? import_node_buffer6.Buffer.from(value, encoding) : import_node_buffer6.Buffer.from(value);
6634
6883
  this.writeUint32(payload.length);
6635
6884
  return this.push(payload);
6636
6885
  }
@@ -6652,7 +6901,7 @@ var SshDataWriter = class {
6652
6901
  return this.writeString(values.join(","), "ascii");
6653
6902
  }
6654
6903
  toBuffer() {
6655
- return import_node_buffer7.Buffer.concat(this.chunks, this.length);
6904
+ return import_node_buffer6.Buffer.concat(this.chunks, this.length);
6656
6905
  }
6657
6906
  push(chunk) {
6658
6907
  this.chunks.push(chunk);
@@ -6670,23 +6919,23 @@ var SshDataWriter = class {
6670
6919
  }
6671
6920
  };
6672
6921
  function normalizePositiveMpint(value) {
6673
- const input = import_node_buffer7.Buffer.from(value);
6922
+ const input = import_node_buffer6.Buffer.from(value);
6674
6923
  let offset = 0;
6675
6924
  while (offset < input.length && input[offset] === 0) {
6676
6925
  offset += 1;
6677
6926
  }
6678
6927
  if (offset >= input.length) {
6679
- return import_node_buffer7.Buffer.alloc(0);
6928
+ return import_node_buffer6.Buffer.alloc(0);
6680
6929
  }
6681
6930
  const stripped = input.subarray(offset);
6682
6931
  if ((stripped[0] & 128) === 128) {
6683
- return import_node_buffer7.Buffer.concat([import_node_buffer7.Buffer.from([0]), stripped]);
6932
+ return import_node_buffer6.Buffer.concat([import_node_buffer6.Buffer.from([0]), stripped]);
6684
6933
  }
6685
6934
  return stripped;
6686
6935
  }
6687
6936
 
6688
6937
  // src/protocols/ssh/binary/SshDataReader.ts
6689
- var import_node_buffer8 = require("buffer");
6938
+ var import_node_buffer7 = require("buffer");
6690
6939
  var SshDataReader = class {
6691
6940
  constructor(source) {
6692
6941
  this.source = source;
@@ -6712,18 +6961,18 @@ var SshDataReader = class {
6712
6961
  this.ensureAvailable(length, "bytes");
6713
6962
  const data = this.source.subarray(this.offset, this.offset + length);
6714
6963
  this.offset += length;
6715
- return import_node_buffer8.Buffer.from(data);
6964
+ return import_node_buffer7.Buffer.from(data);
6716
6965
  }
6717
6966
  readUint32() {
6718
6967
  this.ensureAvailable(4, "uint32");
6719
- const buffer = import_node_buffer8.Buffer.from(this.source);
6968
+ const buffer = import_node_buffer7.Buffer.from(this.source);
6720
6969
  const value = buffer.readUInt32BE(this.offset);
6721
6970
  this.offset += 4;
6722
6971
  return value;
6723
6972
  }
6724
6973
  readUint64() {
6725
6974
  this.ensureAvailable(8, "uint64");
6726
- const buffer = import_node_buffer8.Buffer.from(this.source);
6975
+ const buffer = import_node_buffer7.Buffer.from(this.source);
6727
6976
  const value = buffer.readBigUInt64BE(this.offset);
6728
6977
  this.offset += 8;
6729
6978
  return value;
@@ -6733,7 +6982,7 @@ var SshDataReader = class {
6733
6982
  this.ensureAvailable(length, "string");
6734
6983
  const data = this.source.subarray(this.offset, this.offset + length);
6735
6984
  this.offset += length;
6736
- return import_node_buffer8.Buffer.from(data);
6985
+ return import_node_buffer7.Buffer.from(data);
6737
6986
  }
6738
6987
  readUtf8String() {
6739
6988
  return this.readString().toString("utf8");
@@ -7088,7 +7337,7 @@ function buildKiRequest(args) {
7088
7337
  }
7089
7338
 
7090
7339
  // src/protocols/ssh/auth/SshPublickeyCredentialBuilder.ts
7091
- var import_node_buffer9 = require("buffer");
7340
+ var import_node_buffer8 = require("buffer");
7092
7341
  var import_node_crypto2 = require("crypto");
7093
7342
  var ED25519_RAW_KEY_LENGTH = 32;
7094
7343
  var ED25519_SPKI_PREFIX_LENGTH = 12;
@@ -7106,7 +7355,7 @@ function buildPublickeyCredential(options) {
7106
7355
  return {
7107
7356
  algorithmName: "ssh-ed25519",
7108
7357
  publicKeyBlob,
7109
- sign: (data) => (0, import_node_crypto2.sign)(null, import_node_buffer9.Buffer.from(data), privateKey),
7358
+ sign: (data) => (0, import_node_crypto2.sign)(null, import_node_buffer8.Buffer.from(data), privateKey),
7110
7359
  type: "publickey",
7111
7360
  username
7112
7361
  };
@@ -7124,7 +7373,7 @@ function buildPublickeyCredential(options) {
7124
7373
  return {
7125
7374
  algorithmName,
7126
7375
  publicKeyBlob,
7127
- sign: (data) => (0, import_node_crypto2.sign)(hash, import_node_buffer9.Buffer.from(data), privateKey),
7376
+ sign: (data) => (0, import_node_crypto2.sign)(hash, import_node_buffer8.Buffer.from(data), privateKey),
7128
7377
  type: "publickey",
7129
7378
  username
7130
7379
  };
@@ -7137,7 +7386,7 @@ function buildPublickeyCredential(options) {
7137
7386
  }
7138
7387
  function base64UrlToMpint(value) {
7139
7388
  const padded = value.replace(/-/g, "+").replace(/_/g, "/");
7140
- const buffer = import_node_buffer9.Buffer.from(padded, "base64");
7389
+ const buffer = import_node_buffer8.Buffer.from(padded, "base64");
7141
7390
  return buffer;
7142
7391
  }
7143
7392
  function createInvalidKeyError(message) {
@@ -7263,7 +7512,7 @@ function encodeSshChannelClose(recipientChannel) {
7263
7512
  }
7264
7513
 
7265
7514
  // src/protocols/ssh/connection/SshSessionChannel.ts
7266
- var import_node_buffer10 = require("buffer");
7515
+ var import_node_buffer9 = require("buffer");
7267
7516
  var INITIAL_WINDOW_SIZE = 256 * 1024;
7268
7517
  var MAX_PACKET_SIZE = 32 * 1024;
7269
7518
  var WINDOW_REFILL_THRESHOLD = 64 * 1024;
@@ -7421,7 +7670,7 @@ var SshSessionChannel = class {
7421
7670
  this.remoteWindowRemaining,
7422
7671
  this.remoteMaxPacketSize
7423
7672
  );
7424
- const chunk = import_node_buffer10.Buffer.from(data.subarray(offset, offset + chunkSize));
7673
+ const chunk = import_node_buffer9.Buffer.from(data.subarray(offset, offset + chunkSize));
7425
7674
  this.transport.sendPayload(
7426
7675
  encodeSshChannelData({ data: chunk, recipientChannel: this.remoteChannelId })
7427
7676
  );
@@ -7683,10 +7932,10 @@ var SshConnectionManager = class {
7683
7932
  };
7684
7933
 
7685
7934
  // src/protocols/ssh/transport/SshTransportConnection.ts
7686
- var import_node_buffer18 = require("buffer");
7935
+ var import_node_buffer17 = require("buffer");
7687
7936
 
7688
7937
  // src/protocols/ssh/transport/SshTransportHandshake.ts
7689
- var import_node_buffer16 = require("buffer");
7938
+ var import_node_buffer15 = require("buffer");
7690
7939
 
7691
7940
  // src/protocols/ssh/transport/SshAlgorithmNegotiation.ts
7692
7941
  var DEFAULT_SSH_ALGORITHM_PREFERENCES = {
@@ -7848,12 +8097,12 @@ function parseSshIdentificationLine(line) {
7848
8097
  }
7849
8098
 
7850
8099
  // src/protocols/ssh/transport/SshKexInit.ts
7851
- var import_node_buffer11 = require("buffer");
8100
+ var import_node_buffer10 = require("buffer");
7852
8101
  var import_node_crypto3 = require("crypto");
7853
8102
  var SSH_MSG_KEXINIT = 20;
7854
8103
  var KEXINIT_COOKIE_LENGTH = 16;
7855
8104
  function encodeSshKexInitMessage(options) {
7856
- const cookie = options.cookie === void 0 ? (0, import_node_crypto3.randomBytes)(KEXINIT_COOKIE_LENGTH) : import_node_buffer11.Buffer.from(options.cookie);
8105
+ const cookie = options.cookie === void 0 ? (0, import_node_crypto3.randomBytes)(KEXINIT_COOKIE_LENGTH) : import_node_buffer10.Buffer.from(options.cookie);
7857
8106
  if (cookie.length !== KEXINIT_COOKIE_LENGTH) {
7858
8107
  throw new ConfigurationError({
7859
8108
  details: { actualLength: cookie.length, expectedLength: KEXINIT_COOKIE_LENGTH },
@@ -7923,12 +8172,12 @@ function decodeSshKexInitMessage(payload) {
7923
8172
  }
7924
8173
 
7925
8174
  // src/protocols/ssh/transport/SshKexCurve25519.ts
7926
- var import_node_buffer12 = require("buffer");
8175
+ var import_node_buffer11 = require("buffer");
7927
8176
  var import_node_crypto4 = require("crypto");
7928
8177
  var SSH_MSG_KEX_ECDH_INIT = 30;
7929
8178
  var SSH_MSG_KEX_ECDH_REPLY = 31;
7930
8179
  var X25519_PUBLIC_KEY_LENGTH = 32;
7931
- var X25519_SPKI_PREFIX = import_node_buffer12.Buffer.from("302a300506032b656e032100", "hex");
8180
+ var X25519_SPKI_PREFIX = import_node_buffer11.Buffer.from("302a300506032b656e032100", "hex");
7932
8181
  function createCurve25519Ephemeral() {
7933
8182
  const { privateKey, publicKey } = (0, import_node_crypto4.generateKeyPairSync)("x25519");
7934
8183
  const encodedPublicKey = exportX25519PublicKeyRaw(publicKey);
@@ -7973,7 +8222,7 @@ function exportX25519PublicKeyRaw(publicKey) {
7973
8222
  }
7974
8223
  function importX25519PublicKeyRaw(raw) {
7975
8224
  const normalized = normalizeX25519PublicKey(raw, "server");
7976
- const der = import_node_buffer12.Buffer.concat([X25519_SPKI_PREFIX, normalized]);
8225
+ const der = import_node_buffer11.Buffer.concat([X25519_SPKI_PREFIX, normalized]);
7977
8226
  return (0, import_node_crypto4.createPublicKey)({
7978
8227
  format: "der",
7979
8228
  key: der,
@@ -7981,7 +8230,7 @@ function importX25519PublicKeyRaw(raw) {
7981
8230
  });
7982
8231
  }
7983
8232
  function normalizeX25519PublicKey(value, label) {
7984
- const key = import_node_buffer12.Buffer.from(value);
8233
+ const key = import_node_buffer11.Buffer.from(value);
7985
8234
  if (key.length !== X25519_PUBLIC_KEY_LENGTH) {
7986
8235
  throw new ConfigurationError({
7987
8236
  details: { keyLength: key.length, label },
@@ -7994,7 +8243,7 @@ function normalizeX25519PublicKey(value, label) {
7994
8243
  }
7995
8244
 
7996
8245
  // src/protocols/ssh/transport/SshKeyDerivation.ts
7997
- var import_node_buffer13 = require("buffer");
8246
+ var import_node_buffer12 = require("buffer");
7998
8247
  var import_node_crypto5 = require("crypto");
7999
8248
  function deriveSshSessionKeys(input) {
8000
8249
  const hashAlgorithm = resolveKexHashAlgorithm(input.kexAlgorithm);
@@ -8016,7 +8265,7 @@ function deriveSshSessionKeys(input) {
8016
8265
  input.negotiatedAlgorithms.encryptionServerToClient,
8017
8266
  input.negotiatedAlgorithms.macServerToClient
8018
8267
  );
8019
- const sharedSecret = import_node_buffer13.Buffer.from(input.sharedSecret);
8268
+ const sharedSecret = import_node_buffer12.Buffer.from(input.sharedSecret);
8020
8269
  return {
8021
8270
  clientToServer: {
8022
8271
  encryptionKey: deriveMaterial(
@@ -8066,21 +8315,21 @@ function computeCurve25519ExchangeHash(input, hashAlgorithm) {
8066
8315
  }
8067
8316
  function deriveMaterial(sharedSecret, exchangeHash, sessionId, letter, length, hashAlgorithm) {
8068
8317
  if (length <= 0) {
8069
- return import_node_buffer13.Buffer.alloc(0);
8318
+ return import_node_buffer12.Buffer.alloc(0);
8070
8319
  }
8071
8320
  const result = [];
8072
8321
  const first2 = (0, import_node_crypto5.createHash)(hashAlgorithm).update(
8073
8322
  new SshDataWriter().writeMpint(sharedSecret).writeBytes(exchangeHash).writeByte(letter.charCodeAt(0)).writeBytes(sessionId).toBuffer()
8074
8323
  ).digest();
8075
8324
  result.push(first2);
8076
- while (import_node_buffer13.Buffer.concat(result).length < length) {
8077
- const previous = import_node_buffer13.Buffer.concat(result);
8325
+ while (import_node_buffer12.Buffer.concat(result).length < length) {
8326
+ const previous = import_node_buffer12.Buffer.concat(result);
8078
8327
  const next = (0, import_node_crypto5.createHash)(hashAlgorithm).update(
8079
8328
  new SshDataWriter().writeMpint(sharedSecret).writeBytes(exchangeHash).writeBytes(previous).toBuffer()
8080
8329
  ).digest();
8081
8330
  result.push(next);
8082
8331
  }
8083
- return import_node_buffer13.Buffer.concat(result).subarray(0, length);
8332
+ return import_node_buffer12.Buffer.concat(result).subarray(0, length);
8084
8333
  }
8085
8334
  function resolveKexHashAlgorithm(kexAlgorithm) {
8086
8335
  if (kexAlgorithm === "curve25519-sha256" || kexAlgorithm === "curve25519-sha256@libssh.org") {
@@ -8168,20 +8417,21 @@ function decodeSshNewKeysMessage(payload) {
8168
8417
  }
8169
8418
 
8170
8419
  // src/protocols/ssh/transport/SshTransportPacket.ts
8171
- var import_node_buffer14 = require("buffer");
8420
+ var import_node_buffer13 = require("buffer");
8172
8421
  var import_node_crypto6 = require("crypto");
8173
8422
  var MIN_PADDING_LENGTH = 4;
8174
8423
  var MIN_PACKET_LENGTH = 1 + MIN_PADDING_LENGTH;
8424
+ var MAX_SSH_PACKET_LENGTH = 256 * 1024;
8175
8425
  function encodeSshTransportPacket(payload, options = {}) {
8176
- const body = import_node_buffer14.Buffer.from(payload);
8426
+ const body = import_node_buffer13.Buffer.from(payload);
8177
8427
  const blockSize = normalizeBlockSize(options.blockSize ?? 8);
8178
8428
  let paddingLength = MIN_PADDING_LENGTH;
8179
8429
  while ((1 + body.length + paddingLength + 4) % blockSize !== 0) {
8180
8430
  paddingLength += 1;
8181
8431
  }
8182
- const padding = options.randomPadding === false ? import_node_buffer14.Buffer.alloc(paddingLength) : (0, import_node_crypto6.randomBytes)(paddingLength);
8432
+ const padding = options.randomPadding === false ? import_node_buffer13.Buffer.alloc(paddingLength) : (0, import_node_crypto6.randomBytes)(paddingLength);
8183
8433
  const packetLength = 1 + body.length + paddingLength;
8184
- const frame = import_node_buffer14.Buffer.alloc(4 + packetLength);
8434
+ const frame = import_node_buffer13.Buffer.alloc(4 + packetLength);
8185
8435
  frame.writeUInt32BE(packetLength, 0);
8186
8436
  frame.writeUInt8(paddingLength, 4);
8187
8437
  body.copy(frame, 5);
@@ -8189,7 +8439,7 @@ function encodeSshTransportPacket(payload, options = {}) {
8189
8439
  return frame;
8190
8440
  }
8191
8441
  function decodeSshTransportPacket(frame) {
8192
- const bytes = import_node_buffer14.Buffer.from(frame);
8442
+ const bytes = import_node_buffer13.Buffer.from(frame);
8193
8443
  if (bytes.length < 4 + MIN_PACKET_LENGTH) {
8194
8444
  throw new ParseError({
8195
8445
  details: { length: bytes.length },
@@ -8243,12 +8493,20 @@ function decodeSshTransportPacket(frame) {
8243
8493
  };
8244
8494
  }
8245
8495
  var SshTransportPacketFramer = class {
8246
- pending = import_node_buffer14.Buffer.alloc(0);
8496
+ pending = import_node_buffer13.Buffer.alloc(0);
8247
8497
  push(chunk) {
8248
- this.pending = import_node_buffer14.Buffer.concat([this.pending, import_node_buffer14.Buffer.from(chunk)]);
8498
+ this.pending = import_node_buffer13.Buffer.concat([this.pending, import_node_buffer13.Buffer.from(chunk)]);
8249
8499
  const packets = [];
8250
8500
  while (this.pending.length >= 4) {
8251
8501
  const packetLength = this.pending.readUInt32BE(0);
8502
+ if (packetLength > MAX_SSH_PACKET_LENGTH) {
8503
+ throw new ParseError({
8504
+ details: { maxPacketLength: MAX_SSH_PACKET_LENGTH, packetLength },
8505
+ message: "SSH transport packet length exceeds the maximum accepted size",
8506
+ protocol: "sftp",
8507
+ retryable: false
8508
+ });
8509
+ }
8252
8510
  const frameLength = 4 + packetLength;
8253
8511
  if (this.pending.length < frameLength) {
8254
8512
  break;
@@ -8264,8 +8522,8 @@ var SshTransportPacketFramer = class {
8264
8522
  }
8265
8523
  /** Returns and clears any bytes buffered but not yet part of a complete packet. */
8266
8524
  takeRemainingBytes() {
8267
- const remaining = import_node_buffer14.Buffer.from(this.pending);
8268
- this.pending = import_node_buffer14.Buffer.alloc(0);
8525
+ const remaining = import_node_buffer13.Buffer.from(this.pending);
8526
+ this.pending = import_node_buffer13.Buffer.alloc(0);
8269
8527
  return remaining;
8270
8528
  }
8271
8529
  };
@@ -8282,10 +8540,10 @@ function normalizeBlockSize(blockSize) {
8282
8540
  }
8283
8541
 
8284
8542
  // src/protocols/ssh/transport/SshHostKeyVerification.ts
8285
- var import_node_buffer15 = require("buffer");
8543
+ var import_node_buffer14 = require("buffer");
8286
8544
  var import_node_crypto7 = require("crypto");
8287
8545
  var ED25519_RAW_KEY_LENGTH2 = 32;
8288
- var ED25519_SPKI_PREFIX = import_node_buffer15.Buffer.from("302a300506032b6570032100", "hex");
8546
+ var ED25519_SPKI_PREFIX = import_node_buffer14.Buffer.from("302a300506032b6570032100", "hex");
8289
8547
  function verifySshHostKeySignature(input) {
8290
8548
  const { algorithmName, publicKey } = parseHostKey(input.hostKeyBlob);
8291
8549
  const { signatureAlgorithm, signatureBytes } = parseSignatureBlob(input.signatureBlob);
@@ -8298,9 +8556,9 @@ function verifySshHostKeySignature(input) {
8298
8556
  });
8299
8557
  }
8300
8558
  const verified = verifySignature({
8301
- data: import_node_buffer15.Buffer.from(input.exchangeHash),
8559
+ data: import_node_buffer14.Buffer.from(input.exchangeHash),
8302
8560
  publicKey,
8303
- signature: import_node_buffer15.Buffer.from(signatureBytes),
8561
+ signature: import_node_buffer14.Buffer.from(signatureBytes),
8304
8562
  signatureAlgorithm
8305
8563
  });
8306
8564
  if (!verified) {
@@ -8329,7 +8587,7 @@ function parseHostKey(blob) {
8329
8587
  retryable: false
8330
8588
  });
8331
8589
  }
8332
- const spki = import_node_buffer15.Buffer.concat([ED25519_SPKI_PREFIX, raw]);
8590
+ const spki = import_node_buffer14.Buffer.concat([ED25519_SPKI_PREFIX, raw]);
8333
8591
  return {
8334
8592
  algorithmName,
8335
8593
  publicKey: (0, import_node_crypto7.createPublicKey)({ format: "der", key: spki, type: "spki" })
@@ -8435,37 +8693,37 @@ function verifySignature(input) {
8435
8693
  function rsaPublicKeyFromComponents(e, n) {
8436
8694
  const eDer = encodeAsn1Integer(e);
8437
8695
  const nDer = encodeAsn1Integer(n);
8438
- const rsaPublicKeyDer = encodeAsn1Sequence(import_node_buffer15.Buffer.concat([nDer, eDer]));
8439
- const bitStringContent = import_node_buffer15.Buffer.concat([import_node_buffer15.Buffer.from([0]), rsaPublicKeyDer]);
8440
- const bitString = import_node_buffer15.Buffer.concat([
8441
- import_node_buffer15.Buffer.from([3]),
8696
+ const rsaPublicKeyDer = encodeAsn1Sequence(import_node_buffer14.Buffer.concat([nDer, eDer]));
8697
+ const bitStringContent = import_node_buffer14.Buffer.concat([import_node_buffer14.Buffer.from([0]), rsaPublicKeyDer]);
8698
+ const bitString = import_node_buffer14.Buffer.concat([
8699
+ import_node_buffer14.Buffer.from([3]),
8442
8700
  encodeAsn1Length(bitStringContent.length),
8443
8701
  bitStringContent
8444
8702
  ]);
8445
- const algoId = import_node_buffer15.Buffer.from("300d06092a864886f70d010101 0500".replace(/\s+/g, ""), "hex");
8446
- const spki = encodeAsn1Sequence(import_node_buffer15.Buffer.concat([algoId, bitString]));
8703
+ const algoId = import_node_buffer14.Buffer.from("300d06092a864886f70d010101 0500".replace(/\s+/g, ""), "hex");
8704
+ const spki = encodeAsn1Sequence(import_node_buffer14.Buffer.concat([algoId, bitString]));
8447
8705
  return (0, import_node_crypto7.createPublicKey)({ format: "der", key: spki, type: "spki" });
8448
8706
  }
8449
8707
  function encodeAsn1Integer(value) {
8450
8708
  let body = value;
8451
8709
  while (body.length > 1 && body[0] === 0) body = body.subarray(1);
8452
8710
  if (body.length > 0 && (body[0] & 128) !== 0) {
8453
- body = import_node_buffer15.Buffer.concat([import_node_buffer15.Buffer.from([0]), body]);
8711
+ body = import_node_buffer14.Buffer.concat([import_node_buffer14.Buffer.from([0]), body]);
8454
8712
  }
8455
- return import_node_buffer15.Buffer.concat([import_node_buffer15.Buffer.from([2]), encodeAsn1Length(body.length), body]);
8713
+ return import_node_buffer14.Buffer.concat([import_node_buffer14.Buffer.from([2]), encodeAsn1Length(body.length), body]);
8456
8714
  }
8457
8715
  function encodeAsn1Sequence(content) {
8458
- return import_node_buffer15.Buffer.concat([import_node_buffer15.Buffer.from([48]), encodeAsn1Length(content.length), content]);
8716
+ return import_node_buffer14.Buffer.concat([import_node_buffer14.Buffer.from([48]), encodeAsn1Length(content.length), content]);
8459
8717
  }
8460
8718
  function encodeAsn1Length(length) {
8461
- if (length < 128) return import_node_buffer15.Buffer.from([length]);
8719
+ if (length < 128) return import_node_buffer14.Buffer.from([length]);
8462
8720
  const bytes = [];
8463
8721
  let n = length;
8464
8722
  while (n > 0) {
8465
8723
  bytes.unshift(n & 255);
8466
8724
  n >>>= 8;
8467
8725
  }
8468
- return import_node_buffer15.Buffer.from([128 | bytes.length, ...bytes]);
8726
+ return import_node_buffer14.Buffer.from([128 | bytes.length, ...bytes]);
8469
8727
  }
8470
8728
  var ECDSA_OID_BY_CURVE = {
8471
8729
  nistp256: "06082a8648ce3d030107",
@@ -8486,15 +8744,15 @@ function ecdsaPublicKeyFromPoint(curveIdentifier, point) {
8486
8744
  retryable: false
8487
8745
  });
8488
8746
  }
8489
- const algoIdContent = import_node_buffer15.Buffer.from(ECDSA_ALGORITHM_OID_HEX + oidHex, "hex");
8747
+ const algoIdContent = import_node_buffer14.Buffer.from(ECDSA_ALGORITHM_OID_HEX + oidHex, "hex");
8490
8748
  const algoId = encodeAsn1Sequence(algoIdContent);
8491
- const bitStringContent = import_node_buffer15.Buffer.concat([import_node_buffer15.Buffer.from([0]), point]);
8492
- const bitString = import_node_buffer15.Buffer.concat([
8493
- import_node_buffer15.Buffer.from([3]),
8749
+ const bitStringContent = import_node_buffer14.Buffer.concat([import_node_buffer14.Buffer.from([0]), point]);
8750
+ const bitString = import_node_buffer14.Buffer.concat([
8751
+ import_node_buffer14.Buffer.from([3]),
8494
8752
  encodeAsn1Length(bitStringContent.length),
8495
8753
  bitStringContent
8496
8754
  ]);
8497
- const spki = encodeAsn1Sequence(import_node_buffer15.Buffer.concat([algoId, bitString]));
8755
+ const spki = encodeAsn1Sequence(import_node_buffer14.Buffer.concat([algoId, bitString]));
8498
8756
  return (0, import_node_crypto7.createPublicKey)({ format: "der", key: spki, type: "spki" });
8499
8757
  }
8500
8758
  function sshEcdsaSignatureToDer(sshSignature) {
@@ -8503,7 +8761,7 @@ function sshEcdsaSignatureToDer(sshSignature) {
8503
8761
  const s = reader.readMpint();
8504
8762
  const rDer = encodeAsn1Integer(r);
8505
8763
  const sDer = encodeAsn1Integer(s);
8506
- return encodeAsn1Sequence(import_node_buffer15.Buffer.concat([rDer, sDer]));
8764
+ return encodeAsn1Sequence(import_node_buffer14.Buffer.concat([rDer, sDer]));
8507
8765
  }
8508
8766
 
8509
8767
  // src/protocols/ssh/transport/SshTransportHandshake.ts
@@ -8535,7 +8793,7 @@ var SshTransportHandshake = class {
8535
8793
  serverIdentification;
8536
8794
  /** Creates the first outbound bytes (client identification line). */
8537
8795
  createInitialClientBytes() {
8538
- return import_node_buffer16.Buffer.from(`${this.clientIdentificationLine}\r
8796
+ return import_node_buffer15.Buffer.from(`${this.clientIdentificationLine}\r
8539
8797
  `, "ascii");
8540
8798
  }
8541
8799
  /**
@@ -8559,7 +8817,7 @@ var SshTransportHandshake = class {
8559
8817
  }
8560
8818
  return { outbound };
8561
8819
  }
8562
- return this.pushServerBytesWithPhase(outbound, import_node_buffer16.Buffer.from(chunk));
8820
+ return this.pushServerBytesWithPhase(outbound, import_node_buffer15.Buffer.from(chunk));
8563
8821
  }
8564
8822
  getServerBannerLines() {
8565
8823
  return this.identificationLines;
@@ -8613,12 +8871,12 @@ var SshTransportHandshake = class {
8613
8871
  clientKexInitPayload: this.clientKexInitPayload,
8614
8872
  clientPublicKey: this.pendingCurve25519.publicKey,
8615
8873
  negotiatedAlgorithms,
8616
- serverHostKey: import_node_buffer16.Buffer.alloc(0),
8874
+ serverHostKey: import_node_buffer15.Buffer.alloc(0),
8617
8875
  serverIdentification: (this.serverIdentification ?? missingServerIdentificationError()).raw,
8618
- serverKexInitPayload: import_node_buffer16.Buffer.from(packet.payload),
8619
- serverPublicKey: import_node_buffer16.Buffer.alloc(0),
8620
- serverSignature: import_node_buffer16.Buffer.alloc(0),
8621
- sharedSecret: import_node_buffer16.Buffer.alloc(0)
8876
+ serverKexInitPayload: import_node_buffer15.Buffer.from(packet.payload),
8877
+ serverPublicKey: import_node_buffer15.Buffer.alloc(0),
8878
+ serverSignature: import_node_buffer15.Buffer.alloc(0),
8879
+ sharedSecret: import_node_buffer15.Buffer.alloc(0)
8622
8880
  };
8623
8881
  continue;
8624
8882
  }
@@ -8726,24 +8984,54 @@ var SshTransportHandshake = class {
8726
8984
  return { outbound };
8727
8985
  }
8728
8986
  };
8987
+ var MAX_IDENTIFICATION_LINE_BYTES = 8192;
8988
+ var MAX_PRE_IDENTIFICATION_LINES = 1024;
8729
8989
  var SshIdentificationAccumulator = class {
8730
- pending = import_node_buffer16.Buffer.alloc(0);
8990
+ pending = import_node_buffer15.Buffer.alloc(0);
8991
+ bannerLineCount = 0;
8731
8992
  push(chunk) {
8732
- this.pending = import_node_buffer16.Buffer.concat([this.pending, import_node_buffer16.Buffer.from(chunk)]);
8993
+ this.pending = import_node_buffer15.Buffer.concat([this.pending, import_node_buffer15.Buffer.from(chunk)]);
8733
8994
  const bannerLines = [];
8734
8995
  while (true) {
8735
8996
  const lfIndex = this.pending.indexOf(10);
8736
- if (lfIndex < 0) break;
8997
+ if (lfIndex < 0) {
8998
+ if (this.pending.length > MAX_IDENTIFICATION_LINE_BYTES) {
8999
+ throw new ProtocolError({
9000
+ details: { limitBytes: MAX_IDENTIFICATION_LINE_BYTES },
9001
+ message: "SSH identification line exceeds the maximum accepted length",
9002
+ protocol: "sftp",
9003
+ retryable: false
9004
+ });
9005
+ }
9006
+ break;
9007
+ }
9008
+ if (lfIndex > MAX_IDENTIFICATION_LINE_BYTES) {
9009
+ throw new ProtocolError({
9010
+ details: { limitBytes: MAX_IDENTIFICATION_LINE_BYTES },
9011
+ message: "SSH identification line exceeds the maximum accepted length",
9012
+ protocol: "sftp",
9013
+ retryable: false
9014
+ });
9015
+ }
8737
9016
  const lineText = trimLineEndings(this.pending.subarray(0, lfIndex + 1).toString("ascii"));
8738
- const remainder = import_node_buffer16.Buffer.from(this.pending.subarray(lfIndex + 1));
9017
+ const remainder = import_node_buffer15.Buffer.from(this.pending.subarray(lfIndex + 1));
8739
9018
  this.pending = remainder;
8740
9019
  if (lineText.startsWith("SSH-")) {
8741
- this.pending = import_node_buffer16.Buffer.alloc(0);
9020
+ this.pending = import_node_buffer15.Buffer.alloc(0);
8742
9021
  return { bannerLines, identLine: lineText, remainder };
8743
9022
  }
9023
+ this.bannerLineCount += 1;
9024
+ if (this.bannerLineCount > MAX_PRE_IDENTIFICATION_LINES) {
9025
+ throw new ProtocolError({
9026
+ details: { limitLines: MAX_PRE_IDENTIFICATION_LINES },
9027
+ message: "SSH server sent too many pre-identification banner lines",
9028
+ protocol: "sftp",
9029
+ retryable: false
9030
+ });
9031
+ }
8744
9032
  bannerLines.push(lineText);
8745
9033
  }
8746
- return { bannerLines, remainder: import_node_buffer16.Buffer.alloc(0) };
9034
+ return { bannerLines, remainder: import_node_buffer15.Buffer.alloc(0) };
8747
9035
  }
8748
9036
  };
8749
9037
  function trimLineEndings(value) {
@@ -8771,7 +9059,7 @@ function missingPendingKeyExchangeError() {
8771
9059
  }
8772
9060
 
8773
9061
  // src/protocols/ssh/transport/SshTransportProtection.ts
8774
- var import_node_buffer17 = require("buffer");
9062
+ var import_node_buffer16 = require("buffer");
8775
9063
  var import_node_crypto8 = require("crypto");
8776
9064
  function createSshTransportProtectionContext(input) {
8777
9065
  return {
@@ -8828,7 +9116,7 @@ var SshTransportPacketProtector = class {
8828
9116
  );
8829
9117
  const encrypted = this.cipher === void 0 ? clearPacket : this.cipher.update(clearPacket);
8830
9118
  this.sequenceNumber = this.sequenceNumber + 1 >>> 0;
8831
- return import_node_buffer17.Buffer.concat([encrypted, mac]);
9119
+ return import_node_buffer16.Buffer.concat([encrypted, mac]);
8832
9120
  }
8833
9121
  };
8834
9122
  var SshTransportPacketUnprotector = class {
@@ -8854,7 +9142,7 @@ var SshTransportPacketUnprotector = class {
8854
9142
  sequenceNumber;
8855
9143
  // Streaming framing state for pushBytes()
8856
9144
  framePartialDecrypted;
8857
- framePendingRaw = import_node_buffer17.Buffer.alloc(0);
9145
+ framePendingRaw = import_node_buffer16.Buffer.alloc(0);
8858
9146
  frameRemainingNeeded;
8859
9147
  getSequenceNumber() {
8860
9148
  return this.sequenceNumber;
@@ -8864,15 +9152,23 @@ var SshTransportPacketUnprotector = class {
8864
9152
  * Maintains internal framing state across calls - pass each `data` event chunk directly.
8865
9153
  */
8866
9154
  pushBytes(chunk) {
8867
- this.framePendingRaw = import_node_buffer17.Buffer.concat([this.framePendingRaw, chunk]);
9155
+ this.framePendingRaw = import_node_buffer16.Buffer.concat([this.framePendingRaw, chunk]);
8868
9156
  const results = [];
8869
9157
  while (true) {
8870
9158
  if (this.framePartialDecrypted === void 0) {
8871
9159
  if (this.framePendingRaw.length < this.blockLength) break;
8872
9160
  const firstBlock = this.framePendingRaw.subarray(0, this.blockLength);
8873
- this.framePendingRaw = import_node_buffer17.Buffer.from(this.framePendingRaw.subarray(this.blockLength));
8874
- this.framePartialDecrypted = this.decipher ? import_node_buffer17.Buffer.from(this.decipher.update(firstBlock)) : import_node_buffer17.Buffer.from(firstBlock);
9161
+ this.framePendingRaw = import_node_buffer16.Buffer.from(this.framePendingRaw.subarray(this.blockLength));
9162
+ this.framePartialDecrypted = this.decipher ? import_node_buffer16.Buffer.from(this.decipher.update(firstBlock)) : import_node_buffer16.Buffer.from(firstBlock);
8875
9163
  const packetLength = this.framePartialDecrypted.readUInt32BE(0);
9164
+ if (packetLength > MAX_SSH_PACKET_LENGTH) {
9165
+ throw new ProtocolError({
9166
+ details: { maxPacketLength: MAX_SSH_PACKET_LENGTH, packetLength },
9167
+ message: "SSH encrypted packet length exceeds the maximum accepted size",
9168
+ protocol: "sftp",
9169
+ retryable: false
9170
+ });
9171
+ }
8876
9172
  const remaining = 4 + packetLength - this.blockLength + this.macLength;
8877
9173
  if (remaining < 0) {
8878
9174
  throw new ProtocolError({
@@ -8888,9 +9184,9 @@ var SshTransportPacketUnprotector = class {
8888
9184
  if (this.framePendingRaw.length < needed) break;
8889
9185
  const encryptedRest = this.framePendingRaw.subarray(0, needed - this.macLength);
8890
9186
  const receivedMac = this.framePendingRaw.subarray(needed - this.macLength, needed);
8891
- this.framePendingRaw = import_node_buffer17.Buffer.from(this.framePendingRaw.subarray(needed));
8892
- const decryptedRest = encryptedRest.length > 0 ? this.decipher ? import_node_buffer17.Buffer.from(this.decipher.update(encryptedRest)) : import_node_buffer17.Buffer.from(encryptedRest) : import_node_buffer17.Buffer.alloc(0);
8893
- const clearPacket = import_node_buffer17.Buffer.concat([this.framePartialDecrypted, decryptedRest]);
9187
+ this.framePendingRaw = import_node_buffer16.Buffer.from(this.framePendingRaw.subarray(needed));
9188
+ const decryptedRest = encryptedRest.length > 0 ? this.decipher ? import_node_buffer16.Buffer.from(this.decipher.update(encryptedRest)) : import_node_buffer16.Buffer.from(encryptedRest) : import_node_buffer16.Buffer.alloc(0);
9189
+ const clearPacket = import_node_buffer16.Buffer.concat([this.framePartialDecrypted, decryptedRest]);
8894
9190
  const expectedMac = computeMac(
8895
9191
  this.macAlgorithm,
8896
9192
  this.options.keys.macKey,
@@ -8913,7 +9209,7 @@ var SshTransportPacketUnprotector = class {
8913
9209
  return results;
8914
9210
  }
8915
9211
  unprotectPayload(packet) {
8916
- const frame = import_node_buffer17.Buffer.from(packet);
9212
+ const frame = import_node_buffer16.Buffer.from(packet);
8917
9213
  if (frame.length < this.macLength) {
8918
9214
  throw new ProtocolError({
8919
9215
  details: { length: frame.length, macLength: this.macLength },
@@ -9054,10 +9350,10 @@ function resolveMacLength(encryptionAlgorithm, macAlgorithm) {
9054
9350
  }
9055
9351
  function computeMac(macAlgorithm, macKey, sequence, packet, macLength) {
9056
9352
  if (macLength === 0) {
9057
- return import_node_buffer17.Buffer.alloc(0);
9353
+ return import_node_buffer16.Buffer.alloc(0);
9058
9354
  }
9059
9355
  const hashName = macAlgorithm === "hmac-sha2-512" ? "sha512" : "sha256";
9060
- const sequenceBuffer = import_node_buffer17.Buffer.alloc(4);
9356
+ const sequenceBuffer = import_node_buffer16.Buffer.alloc(4);
9061
9357
  sequenceBuffer.writeUInt32BE(sequence >>> 0, 0);
9062
9358
  return (0, import_node_crypto8.createHmac)(hashName, macKey).update(sequenceBuffer).update(packet).digest().subarray(0, macLength);
9063
9359
  }
@@ -9264,7 +9560,7 @@ var SshTransportConnection = class {
9264
9560
  */
9265
9561
  sendPayload(payload) {
9266
9562
  this.assertConnected();
9267
- const frame = this.protector.protectPayload(import_node_buffer18.Buffer.from(payload));
9563
+ const frame = this.protector.protectPayload(import_node_buffer17.Buffer.from(payload));
9268
9564
  this.socket.write(frame);
9269
9565
  this.resetKeepaliveTimer();
9270
9566
  }
@@ -9422,7 +9718,7 @@ function parseDisconnectPayload(payload) {
9422
9718
  }
9423
9719
 
9424
9720
  // src/protocols/sftp/v3/SftpSession.ts
9425
- var import_node_buffer20 = require("buffer");
9721
+ var import_node_buffer19 = require("buffer");
9426
9722
 
9427
9723
  // src/protocols/sftp/v3/SftpAttributes.ts
9428
9724
  var SFTP_ATTR_FLAG = {
@@ -9495,7 +9791,8 @@ function decodeSftpAttributesFromReader(reader) {
9495
9791
  }
9496
9792
 
9497
9793
  // src/protocols/sftp/v3/SftpPacket.ts
9498
- var import_node_buffer19 = require("buffer");
9794
+ var import_node_buffer18 = require("buffer");
9795
+ var MAX_SFTP_PACKET_LENGTH = 256 * 1024;
9499
9796
  var SFTP_PACKET_TYPE = {
9500
9797
  ATTRS: 105,
9501
9798
  CLOSE: 4,
@@ -9526,7 +9823,7 @@ var SFTP_PACKET_TYPE = {
9526
9823
  WRITE: 6
9527
9824
  };
9528
9825
  function decodeSftpPacket(frame) {
9529
- const bytes = import_node_buffer19.Buffer.from(frame);
9826
+ const bytes = import_node_buffer18.Buffer.from(frame);
9530
9827
  if (bytes.length < 5) {
9531
9828
  throw new ParseError({
9532
9829
  details: { length: bytes.length },
@@ -9549,12 +9846,19 @@ function decodeSftpPacket(frame) {
9549
9846
  };
9550
9847
  }
9551
9848
  var SftpPacketFramer = class {
9552
- pending = import_node_buffer19.Buffer.alloc(0);
9849
+ pending = import_node_buffer18.Buffer.alloc(0);
9553
9850
  push(chunk) {
9554
- this.pending = import_node_buffer19.Buffer.concat([this.pending, import_node_buffer19.Buffer.from(chunk)]);
9851
+ this.pending = import_node_buffer18.Buffer.concat([this.pending, import_node_buffer18.Buffer.from(chunk)]);
9555
9852
  const packets = [];
9556
9853
  while (this.pending.length >= 4) {
9557
9854
  const bodyLength = this.pending.readUInt32BE(0);
9855
+ if (bodyLength > MAX_SFTP_PACKET_LENGTH) {
9856
+ throw new ParseError({
9857
+ details: { bodyLength, maxPacketLength: MAX_SFTP_PACKET_LENGTH },
9858
+ message: "SFTP packet length exceeds the maximum accepted size",
9859
+ retryable: false
9860
+ });
9861
+ }
9558
9862
  const frameLength = 4 + bodyLength;
9559
9863
  if (this.pending.length < frameLength) {
9560
9864
  break;
@@ -10009,7 +10313,7 @@ var SftpSession = class {
10009
10313
  * serializes concurrent calls so byte ordering is preserved.
10010
10314
  */
10011
10315
  sendRaw(encodedMessage, requestId) {
10012
- const frame = import_node_buffer20.Buffer.alloc(4 + encodedMessage.length);
10316
+ const frame = import_node_buffer19.Buffer.alloc(4 + encodedMessage.length);
10013
10317
  frame.writeUInt32BE(encodedMessage.length, 0);
10014
10318
  encodedMessage.copy(frame, 4);
10015
10319
  this.channel.sendData(frame).catch((err) => {
@@ -10552,9 +10856,9 @@ function buildNativePublickeyCredential(profile, username) {
10552
10856
  const passphrase = profile.ssh?.passphrase;
10553
10857
  try {
10554
10858
  const privateKey = (0, import_node_crypto9.createPrivateKey)({
10555
- key: import_node_buffer21.Buffer.isBuffer(keyMaterial) ? keyMaterial : keyMaterial,
10859
+ key: import_node_buffer20.Buffer.isBuffer(keyMaterial) ? keyMaterial : keyMaterial,
10556
10860
  ...passphrase === void 0 ? {} : {
10557
- passphrase: import_node_buffer21.Buffer.isBuffer(passphrase) ? passphrase : passphrase
10861
+ passphrase: import_node_buffer20.Buffer.isBuffer(passphrase) ? passphrase : passphrase
10558
10862
  }
10559
10863
  });
10560
10864
  return buildPublickeyCredential({ privateKey, username });
@@ -10681,12 +10985,12 @@ function normalizeNativeHostKeyPins(value) {
10681
10985
  const trimmed = pin.trim();
10682
10986
  const hex = trimmed.replace(/:/g, "");
10683
10987
  if (hex.length === 64 && /^[a-f0-9]+$/i.test(hex)) {
10684
- normalized.add(import_node_buffer21.Buffer.from(hex, "hex").toString("base64").replace(/=+$/g, ""));
10988
+ normalized.add(import_node_buffer20.Buffer.from(hex, "hex").toString("base64").replace(/=+$/g, ""));
10685
10989
  continue;
10686
10990
  }
10687
10991
  const bare = trimmed.startsWith("SHA256:") ? trimmed.slice("SHA256:".length) : trimmed;
10688
10992
  const padded = bare.length % 4 === 0 ? bare : `${bare}${"=".repeat(4 - bare.length % 4)}`;
10689
- normalized.add(import_node_buffer21.Buffer.from(padded, "base64").toString("base64").replace(/=+$/g, ""));
10993
+ normalized.add(import_node_buffer20.Buffer.from(padded, "base64").toString("base64").replace(/=+$/g, ""));
10690
10994
  }
10691
10995
  return normalized;
10692
10996
  }
@@ -10696,7 +11000,7 @@ function parseNativeKnownHosts(source) {
10696
11000
  const entries = [];
10697
11001
  let sawNonEmpty = false;
10698
11002
  for (const value of sources) {
10699
- const text = import_node_buffer21.Buffer.isBuffer(value) ? value.toString("utf8") : String(value);
11003
+ const text = import_node_buffer20.Buffer.isBuffer(value) ? value.toString("utf8") : String(value);
10700
11004
  if (text.length === 0) continue;
10701
11005
  sawNonEmpty = true;
10702
11006
  entries.push(...parseKnownHosts(text));
@@ -10769,7 +11073,7 @@ function requireNativeSftpUsername(profile) {
10769
11073
  }
10770
11074
  function resolveNativeSftpTextSecret(value) {
10771
11075
  if (value === void 0) return void 0;
10772
- const text = import_node_buffer21.Buffer.isBuffer(value) ? value.toString("utf8") : value;
11076
+ const text = import_node_buffer20.Buffer.isBuffer(value) ? value.toString("utf8") : value;
10773
11077
  if (text.length === 0) return void 0;
10774
11078
  return text;
10775
11079
  }
@@ -10845,6 +11149,7 @@ function validateNativeSftpOptions(options) {
10845
11149
  copyBetween,
10846
11150
  createAtomicDeployPlan,
10847
11151
  createBandwidthThrottle,
11152
+ createDefaultRetryPolicy,
10848
11153
  createFtpProviderFactory,
10849
11154
  createFtpsProviderFactory,
10850
11155
  createLocalProviderFactory,
@@ -10890,8 +11195,10 @@ function validateNativeSftpOptions(options) {
10890
11195
  parseUnixListLine,
10891
11196
  redactCommand,
10892
11197
  redactConnectionProfile,
11198
+ redactErrorForLogging,
10893
11199
  redactObject,
10894
11200
  redactSecretSource,
11201
+ redactUrlForLogging,
10895
11202
  redactValue,
10896
11203
  resolveConnectionProfileSecrets,
10897
11204
  resolveOpenSshHost,