@harperfast/harper 5.2.0-beta.4 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/bin/backup.ts +169 -0
  2. package/bin/cliOperations.ts +92 -73
  3. package/bin/harper.ts +25 -6
  4. package/components/Scope.ts +52 -6
  5. package/components/componentLoader.ts +107 -9
  6. package/components/mcp/toolRegistry.ts +10 -0
  7. package/components/mcp/tools/application.ts +12 -5
  8. package/components/mcp/tools/operations.ts +3 -0
  9. package/components/mcp/tools/schemas/operationDescriptions.ts +3 -0
  10. package/components/mcp/tools/schemas/operations.ts +9 -0
  11. package/components/operations.js +1 -0
  12. package/components/operationsValidation.js +32 -2
  13. package/components/scopeMount.ts +150 -0
  14. package/config-root.schema.json +4 -0
  15. package/dataLayer/backupManifest.ts +102 -0
  16. package/dataLayer/blobBackup.ts +286 -0
  17. package/dataLayer/harperBridge/ResourceBridge.ts +43 -0
  18. package/dataLayer/restoreMarker.ts +276 -0
  19. package/dataLayer/rocksdbBackup.ts +1100 -0
  20. package/dist/bin/backup.d.ts +9 -0
  21. package/dist/bin/backup.js +192 -0
  22. package/dist/bin/backup.js.map +1 -0
  23. package/dist/bin/cliOperations.d.ts +13 -0
  24. package/dist/bin/cliOperations.js +89 -70
  25. package/dist/bin/cliOperations.js.map +1 -1
  26. package/dist/bin/harper.d.ts +7 -0
  27. package/dist/bin/harper.js +27 -6
  28. package/dist/bin/harper.js.map +1 -1
  29. package/dist/components/Scope.d.ts +37 -1
  30. package/dist/components/Scope.js +48 -6
  31. package/dist/components/Scope.js.map +1 -1
  32. package/dist/components/componentLoader.d.ts +2 -6
  33. package/dist/components/componentLoader.js +91 -8
  34. package/dist/components/componentLoader.js.map +1 -1
  35. package/dist/components/mcp/toolRegistry.d.ts +7 -0
  36. package/dist/components/mcp/toolRegistry.js +10 -0
  37. package/dist/components/mcp/toolRegistry.js.map +1 -1
  38. package/dist/components/mcp/tools/application.js +11 -5
  39. package/dist/components/mcp/tools/application.js.map +1 -1
  40. package/dist/components/mcp/tools/operations.js +3 -0
  41. package/dist/components/mcp/tools/operations.js.map +1 -1
  42. package/dist/components/mcp/tools/schemas/operationDescriptions.js +2 -0
  43. package/dist/components/mcp/tools/schemas/operationDescriptions.js.map +1 -1
  44. package/dist/components/mcp/tools/schemas/operations.js +9 -0
  45. package/dist/components/mcp/tools/schemas/operations.js.map +1 -1
  46. package/dist/components/operations.js +2 -0
  47. package/dist/components/operations.js.map +1 -1
  48. package/dist/components/operationsValidation.js +34 -2
  49. package/dist/components/operationsValidation.js.map +1 -1
  50. package/dist/components/scopeMount.d.ts +86 -0
  51. package/dist/components/scopeMount.js +131 -0
  52. package/dist/components/scopeMount.js.map +1 -0
  53. package/dist/dataLayer/backupManifest.d.ts +26 -0
  54. package/dist/dataLayer/backupManifest.js +97 -0
  55. package/dist/dataLayer/backupManifest.js.map +1 -0
  56. package/dist/dataLayer/blobBackup.d.ts +87 -0
  57. package/dist/dataLayer/blobBackup.js +282 -0
  58. package/dist/dataLayer/blobBackup.js.map +1 -0
  59. package/dist/dataLayer/harperBridge/ResourceBridge.d.ts +3 -0
  60. package/dist/dataLayer/harperBridge/ResourceBridge.js +33 -0
  61. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  62. package/dist/dataLayer/restoreMarker.d.ts +122 -0
  63. package/dist/dataLayer/restoreMarker.js +261 -0
  64. package/dist/dataLayer/restoreMarker.js.map +1 -0
  65. package/dist/dataLayer/rocksdbBackup.d.ts +127 -0
  66. package/dist/dataLayer/rocksdbBackup.js +1039 -0
  67. package/dist/dataLayer/rocksdbBackup.js.map +1 -0
  68. package/dist/resources/DatabaseTransaction.js +0 -6
  69. package/dist/resources/DatabaseTransaction.js.map +1 -1
  70. package/dist/resources/ResourceInterface.d.ts +0 -3
  71. package/dist/resources/ResourceInterface.js.map +1 -1
  72. package/dist/resources/Table.js +22 -22
  73. package/dist/resources/Table.js.map +1 -1
  74. package/dist/resources/blob.d.ts +8 -0
  75. package/dist/resources/blob.js +16 -7
  76. package/dist/resources/blob.js.map +1 -1
  77. package/dist/resources/databases.d.ts +42 -1
  78. package/dist/resources/databases.js +276 -40
  79. package/dist/resources/databases.js.map +1 -1
  80. package/dist/resources/transaction.js +0 -3
  81. package/dist/resources/transaction.js.map +1 -1
  82. package/dist/server/REST.js +25 -9
  83. package/dist/server/REST.js.map +1 -1
  84. package/dist/server/fastifyRoutes.js +15 -1
  85. package/dist/server/fastifyRoutes.js.map +1 -1
  86. package/dist/server/itc/serverHandlers.js +7 -1
  87. package/dist/server/itc/serverHandlers.js.map +1 -1
  88. package/dist/server/jobs/jobProcess.js +20 -1
  89. package/dist/server/jobs/jobProcess.js.map +1 -1
  90. package/dist/server/jobs/jobRunner.js +10 -0
  91. package/dist/server/jobs/jobRunner.js.map +1 -1
  92. package/dist/server/jobs/jobs.js +11 -0
  93. package/dist/server/jobs/jobs.js.map +1 -1
  94. package/dist/server/middlewareChain.d.ts +10 -1
  95. package/dist/server/middlewareChain.js +81 -21
  96. package/dist/server/middlewareChain.js.map +1 -1
  97. package/dist/server/serverHelpers/serverHandlers.js +8 -4
  98. package/dist/server/serverHelpers/serverHandlers.js.map +1 -1
  99. package/dist/server/serverHelpers/serverUtilities.js +11 -0
  100. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  101. package/dist/server/static.js +11 -3
  102. package/dist/server/static.js.map +1 -1
  103. package/dist/utility/OperationFunctionCaller.js +25 -6
  104. package/dist/utility/OperationFunctionCaller.js.map +1 -1
  105. package/dist/utility/hdbTerms.d.ts +11 -1
  106. package/dist/utility/hdbTerms.js +11 -1
  107. package/dist/utility/hdbTerms.js.map +1 -1
  108. package/dist/utility/logging/harper_logger.d.ts +40 -0
  109. package/dist/utility/logging/harper_logger.js +746 -14
  110. package/dist/utility/logging/harper_logger.js.map +1 -1
  111. package/dist/utility/operation_authorization.js +13 -3
  112. package/dist/utility/operation_authorization.js.map +1 -1
  113. package/npm-shrinkwrap.json +3 -2
  114. package/package.json +4 -1
  115. package/resources/DESIGN.md +2 -0
  116. package/resources/DatabaseTransaction.ts +0 -3
  117. package/resources/ResourceInterface.ts +0 -3
  118. package/resources/Table.ts +20 -20
  119. package/resources/blob.ts +16 -6
  120. package/resources/databases.ts +272 -42
  121. package/resources/transaction.ts +0 -3
  122. package/server/DESIGN.md +16 -0
  123. package/server/REST.ts +25 -9
  124. package/server/fastifyRoutes.ts +20 -1
  125. package/server/itc/serverHandlers.js +7 -1
  126. package/server/jobs/jobProcess.ts +18 -1
  127. package/server/jobs/jobRunner.ts +10 -0
  128. package/server/jobs/jobs.ts +11 -0
  129. package/server/middlewareChain.ts +79 -20
  130. package/server/serverHelpers/serverHandlers.js +8 -4
  131. package/server/serverHelpers/serverUtilities.ts +19 -0
  132. package/server/static.ts +12 -3
  133. package/static/defaultConfig.yaml +1 -0
  134. package/studio/web/assets/{Chat-DK3GlWEb.js → Chat-DoVWScmq.js} +2 -2
  135. package/studio/web/assets/{Chat-DK3GlWEb.js.map → Chat-DoVWScmq.js.map} +1 -1
  136. package/studio/web/assets/{FloatingChat-fBcC1Ew_.js → FloatingChat-UZ2NsUOZ.js} +4 -4
  137. package/studio/web/assets/{FloatingChat-fBcC1Ew_.js.map → FloatingChat-UZ2NsUOZ.js.map} +1 -1
  138. package/studio/web/assets/{apiToken-DJo1nakA.js → apiToken-BUI_04o7.js} +2 -2
  139. package/studio/web/assets/{apiToken-DJo1nakA.js.map → apiToken-BUI_04o7.js.map} +1 -1
  140. package/studio/web/assets/{applications-BDfH8urd.js → applications-D03NA7wW.js} +2 -2
  141. package/studio/web/assets/{applications-BDfH8urd.js.map → applications-D03NA7wW.js.map} +1 -1
  142. package/studio/web/assets/{index-BHo3c2Gk.js → index-Bh_CNAHr.js} +6 -6
  143. package/studio/web/assets/index-Bh_CNAHr.js.map +1 -0
  144. package/studio/web/assets/{index.lazy-BTo0y6UM.js → index.lazy-Dx3MpyDC.js} +4 -4
  145. package/studio/web/assets/{index.lazy-BTo0y6UM.js.map → index.lazy-Dx3MpyDC.js.map} +1 -1
  146. package/studio/web/assets/{notifications-CMxvWNnz.js → notifications-0edoFTsb.js} +2 -2
  147. package/studio/web/assets/{notifications-CMxvWNnz.js.map → notifications-0edoFTsb.js.map} +1 -1
  148. package/studio/web/assets/{notifications-D3GoB26g.js → notifications-CwKhipK7.js} +2 -2
  149. package/studio/web/assets/{notifications-D3GoB26g.js.map → notifications-CwKhipK7.js.map} +1 -1
  150. package/studio/web/assets/{profile-Doj5FVDE.js → profile-DUfEPQtx.js} +2 -2
  151. package/studio/web/assets/{profile-Doj5FVDE.js.map → profile-DUfEPQtx.js.map} +1 -1
  152. package/studio/web/assets/{setComponentFile-yinsqJy0.js → setComponentFile-DMPo4UjC.js} +2 -2
  153. package/studio/web/assets/{setComponentFile-yinsqJy0.js.map → setComponentFile-DMPo4UjC.js.map} +1 -1
  154. package/studio/web/assets/{setup-DJwR0BHd.js → setup-B56Oz1_u.js} +2 -2
  155. package/studio/web/assets/{setup-DJwR0BHd.js.map → setup-B56Oz1_u.js.map} +1 -1
  156. package/studio/web/assets/{status-Br_AbsJs.js → status-BAod7p3o.js} +2 -2
  157. package/studio/web/assets/{status-Br_AbsJs.js.map → status-BAod7p3o.js.map} +1 -1
  158. package/studio/web/assets/{swagger-ui-react-02XH5sVf.js → swagger-ui-react-lQrBxfwM.js} +2 -2
  159. package/studio/web/assets/{swagger-ui-react-02XH5sVf.js.map → swagger-ui-react-lQrBxfwM.js.map} +1 -1
  160. package/studio/web/assets/{tsMode-D1DMKY7h.js → tsMode-CrHCRjTK.js} +2 -2
  161. package/studio/web/assets/{tsMode-D1DMKY7h.js.map → tsMode-CrHCRjTK.js.map} +1 -1
  162. package/studio/web/assets/{useEntityRestURL-DtRblRgw.js → useEntityRestURL-DoaBMEvU.js} +2 -2
  163. package/studio/web/assets/{useEntityRestURL-DtRblRgw.js.map → useEntityRestURL-DoaBMEvU.js.map} +1 -1
  164. package/studio/web/index.html +1 -1
  165. package/utility/OperationFunctionCaller.ts +24 -3
  166. package/utility/hdbTerms.ts +11 -1
  167. package/utility/logging/harper_logger.ts +729 -15
  168. package/utility/operation_authorization.ts +31 -3
  169. package/studio/web/assets/index-BHo3c2Gk.js.map +0 -1
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.forComponent = exports.getLogFilePath = exports.externalLogger = exports.logLevel = exports.OUTPUTS = void 0;
40
+ exports.isErrorLike = isErrorLike;
40
41
  exports.disableStdio = disableStdio;
41
42
  exports.logsAtLevel = logsAtLevel;
42
43
  exports.initLogSettings = initLogSettings;
@@ -54,6 +55,7 @@ exports.logCustomLevel = logCustomLevel;
54
55
  exports.getPropsFilePath = getPropsFilePath;
55
56
  exports.errorToString = errorToString;
56
57
  exports.errorForLog = errorForLog;
58
+ exports.inspectForLog = inspectForLog;
57
59
  exports.setMainLogger = setMainLogger;
58
60
  exports.AuthAuditLog = AuthAuditLog;
59
61
  // Note - do not import/use commonUtils.js in this module, it will cause circular dependencies.
@@ -238,35 +240,68 @@ async function updateLogSettings() {
238
240
  }
239
241
  }
240
242
  /**
241
- * True when the argument is an Error (same-realm or native cross-realm). The try/catch guards
242
- * exotic objects whose prototype is unreachable (e.g. a revoked Proxy, where `instanceof`
243
- * throws) the logger must never throw on any input, and util.format renders those fine raw.
243
+ * True when the argument is an Error (same-realm or native cross-realm component code runs
244
+ * through node:vm, so a VM-created Error fails `instanceof Error` but passes `isNativeError`).
245
+ * Classified via `util.types.isNativeError` alone, never `instanceof Error`: `instanceof` walks
246
+ * the prototype chain via `[[GetPrototypeOf]]`, which for a value like
247
+ * `Object.create(Object.create(proxyAncestor))` eventually reaches `proxyAncestor` and invokes
248
+ * ITS `getPrototypeOf` trap, even though `arg` itself is an ordinary object and not a Proxy - a
249
+ * trap that hangs (an infinite loop) or has side effects would run to completion on whatever path
250
+ * called isErrorLike (e.g. logging a DIFFERENT operation's failure). `isNativeError` checks an
251
+ * internal slot directly, the same realm-independent, prototype-chain-independent mechanism
252
+ * `types.isMap`/`types.isDate`/etc. use elsewhere in this file, so it recognizes same-realm,
253
+ * cross-realm (VM), and Error-subclass instances without ever walking a prototype chain. The
254
+ * try/catch below still guards other exotic objects for other reasons — the logger must never
255
+ * throw on any input, and util.format renders those fine raw. Exported so call sites outside this
256
+ * module (e.g. OperationFunctionCaller, deciding how to log an error-shaped value it didn't itself
257
+ * catch as an Error) can reuse the same classification instead of a weaker local `instanceof`
258
+ * check.
244
259
  */
245
260
  function isErrorLike(arg) {
246
261
  try {
247
- return arg instanceof Error || isNativeError(arg);
262
+ return isNativeError(arg);
248
263
  }
249
264
  catch {
250
265
  return false;
251
266
  }
252
267
  }
268
+ /**
269
+ * True when `arg` needs sanitizeErrorArg's replacement before reaching Console's raw formatting:
270
+ * either it's error-like, or it's a live Proxy that isErrorLike deliberately reports as false (see
271
+ * isErrorLike above) but that Console's default inspect would still reflect on directly - which,
272
+ * if the Proxy happens to wrap an Error, dumps its own-enumerable properties unsanitized (#1734).
273
+ */
274
+ function needsSanitizing(arg) {
275
+ return node_util_1.types.isProxy(arg) || isErrorLike(arg);
276
+ }
277
+ /**
278
+ * Replaces an Error (or a Proxy - live-or-revoked, whatever it wraps) with a safe placeholder
279
+ * before it reaches Console's util.inspect formatting; same Proxy-first ordering and rationale as
280
+ * deepSanitizeErrors' `types.isProxy` check (see there) - reflecting on a Proxy at all, even just
281
+ * to classify it, risks running a hostile trap on the shallow logging path too.
282
+ */
283
+ function sanitizeErrorArg(arg) {
284
+ if (node_util_1.types.isProxy(arg))
285
+ return labelPlaceholder('[Proxy]');
286
+ return isErrorLike(arg) ? errorForLog(arg) : arg;
287
+ }
253
288
  /**
254
289
  * Replaces every Error argument with its log-safe errorForLog wrapper before the args reach
255
290
  * Console's util.inspect formatting, which would otherwise dump the error's own-enumerable
256
291
  * properties — where libraries and app code stash credentials (axios config headers, an
257
292
  * hdb_secret for an outbound Authorization header) — into hdb.log (see #1734 and errorForLog).
258
293
  * Called inside each level gate so filtered-out log calls pay nothing beyond the arg scan,
259
- * and only allocates when an Error is actually present. Deliberately shallow: an Error nested
260
- * inside a logged object/array is not rewritten (deep-walking every logged structure is not
261
- * worth the per-call cost, and the #1734 threat is raw thrown errors).
294
+ * and only allocates when an Error (or Proxy needing the same treatment) is actually present.
295
+ * Deliberately shallow: an Error nested inside a logged object/array is not rewritten (deep-
296
+ * walking every logged structure is not worth the per-call cost, and the #1734 threat is raw
297
+ * thrown errors).
262
298
  */
263
299
  function sanitizeErrorArgs(args) {
264
300
  for (let i = 0; i < args.length; i++) {
265
- if (isErrorLike(args[i])) {
301
+ if (needsSanitizing(args[i])) {
266
302
  const sanitized = args.slice(0, i);
267
303
  for (let j = i; j < args.length; j++) {
268
- const arg = args[j];
269
- sanitized[j] = isErrorLike(arg) ? errorForLog(arg) : arg;
304
+ sanitized[j] = sanitizeErrorArg(args[j]);
270
305
  }
271
306
  return sanitized;
272
307
  }
@@ -350,10 +385,10 @@ class HarperLogger extends console_1.Console {
350
385
  super.log(...sanitizeErrorArgs(args));
351
386
  }
352
387
  dir(item, options) {
353
- super.dir(isErrorLike(item) ? errorForLog(item) : item, options);
388
+ super.dir(sanitizeErrorArg(item), options);
354
389
  }
355
390
  table(data, columns) {
356
- super.table(Array.isArray(data) ? sanitizeErrorArgs(data) : isErrorLike(data) ? errorForLog(data) : data, columns);
391
+ super.table(Array.isArray(data) ? sanitizeErrorArgs(data) : sanitizeErrorArg(data), columns);
357
392
  }
358
393
  withTag(tag) {
359
394
  return loggerWithTag(tag, true, this);
@@ -397,6 +432,8 @@ module.exports = {
397
432
  startOnMainThread: updateLogSettings,
398
433
  errorToString,
399
434
  errorForLog,
435
+ inspectForLog,
436
+ isErrorLike,
400
437
  disableStdio,
401
438
  externalLogger: exports.externalLogger,
402
439
  };
@@ -1032,8 +1069,12 @@ function renderErrorLine(error) {
1032
1069
  return base + loggablePropsSuffix(error);
1033
1070
  }
1034
1071
  catch (err) {
1035
- // error?.stack itself can throw on a hostile object even though errorToString cannot.
1036
- return `[Unrenderable Error: ${err instanceof Error ? err.message : String(err)}]`;
1072
+ // error?.stack itself can throw on a hostile object (e.g. a getter on a `cause` chain member
1073
+ // that throws a revoked Proxy) - `err` is then whatever was thrown, so it must be rendered via
1074
+ // errorToString, the only renderer in this file explicitly guaranteed never to throw regardless
1075
+ // of input. `err instanceof Error`/`String(err)` are NOT safe here: both throw on a revoked
1076
+ // Proxy, which would defeat this catch's whole purpose.
1077
+ return `[Unrenderable Error: ${errorToString(err)}]`;
1037
1078
  }
1038
1079
  }
1039
1080
  /**
@@ -1089,6 +1130,695 @@ function errorForLog(error) {
1089
1130
  const render = () => errorToLogString(error);
1090
1131
  return { [node_util_1.inspect.custom]: render, toString: render };
1091
1132
  }
1133
+ // Bounds deepSanitizeErrors' walk: deep enough to reach any realistic diagnostic payload shape,
1134
+ // capped so a pathological/adversarial structure can't blow the stack.
1135
+ const MAX_SANITIZE_DEPTH = 20;
1136
+ // Default per-container breadth cap (used when inspectForLog's caller didn't request a specific
1137
+ // maxArrayLength) and an absolute ceiling on total nodes visited across the WHOLE walk. Per-
1138
+ // container breadth alone isn't enough: a structure that is merely wide at every one of
1139
+ // MAX_SANITIZE_DEPTH levels multiplies out to an astronomical node count, so a global counter is
1140
+ // the actual backstop. Both exist because sanitizing happens BEFORE util.inspect's own
1141
+ // maxArrayLength truncation runs - a huge/sparse array or a huge Map/Set in a structured error
1142
+ // (`new Array(0xffffffff)`, or a hostile component's crafted payload) would otherwise force this
1143
+ // walk to visit billions of entries and wedge the event loop while just trying to log the
1144
+ // *original* error, before inspect ever gets a chance to truncate the output.
1145
+ const DEFAULT_MAX_SANITIZE_ENTRIES = 1000;
1146
+ const MAX_SANITIZE_NODES = 50_000;
1147
+ // Hard ceiling on the per-container breadth budget, regardless of what a caller requests via
1148
+ // inspectForLog's `maxArrayLength` option. Without this, a caller passing an unbounded value
1149
+ // (`Infinity`, or just a very large finite one) sets a container's own loop limit that high too -
1150
+ // so even though deepSanitizeErrors itself starts returning cheap placeholders once
1151
+ // MAX_SANITIZE_NODES is exhausted, the PARENT loop (the one iterating a huge array/Map/Set's
1152
+ // entries) still runs for its full stated limit before that ever kicks in, defeating the budget as
1153
+ // a real ceiling on total work. The one caller in this codebase passes 250; this only bites a
1154
+ // caller that deliberately (or by bug) requests something far larger.
1155
+ const HARD_MAX_SANITIZE_ENTRIES = 10_000;
1156
+ /** A util.inspect-style placeholder rendered without invoking anything, used both for an unread
1157
+ * accessor property and for a budget-truncated container tail. */
1158
+ function labelPlaceholder(label) {
1159
+ return { [node_util_1.inspect.custom]: () => label, toString: () => label };
1160
+ }
1161
+ /** A util.inspect-style placeholder for an accessor property, describing it without invoking the
1162
+ * getter — see the getter-invocation note on deepSanitizeErrors below. */
1163
+ function accessorPlaceholder(descriptor) {
1164
+ return labelPlaceholder(descriptor.get && descriptor.set ? '[Getter/Setter]' : descriptor.get ? '[Getter]' : '[Setter]');
1165
+ }
1166
+ /**
1167
+ * Placeholder substituted when a recursive sanitize step on a child throws, instead of falling
1168
+ * back to that child's raw (unsanitized) value. A throw here is not a reason to skip sanitizing -
1169
+ * it is exactly the case a hostile value produces (e.g. a Proxy whose `ownKeys` trap throws once
1170
+ * reached one level down), and the child that triggered it may itself contain an unsanitized
1171
+ * Error. Falling back to raw would silently hand that Error to inspect() at the raised depth,
1172
+ * recreating the #1734 leak this whole walk exists to prevent.
1173
+ */
1174
+ function sanitizeFailurePlaceholder() {
1175
+ return labelPlaceholder('[Unrenderable value: sanitize failed]');
1176
+ }
1177
+ // Unique symbol keys for the plain-object breadth-cap markers below, rather than a string key
1178
+ // like the array/Map/Set truncation markers use - a hostile or just plain unlucky object could
1179
+ // have an own string property literally named the same as a string marker, silently colliding
1180
+ // with (and hiding) real data. A locally-scoped Symbol can never collide with an enumerable
1181
+ // string OR pre-existing symbol key on the original value.
1182
+ const KEYS_TRUNCATED_MARKER = Symbol('sanitize: string-keyed properties truncated');
1183
+ const SYMBOLS_TRUNCATED_MARKER = Symbol('sanitize: symbol-keyed properties truncated');
1184
+ /**
1185
+ * True for the specific built-ins whose actual data is NOT reachable through their own-enumerable
1186
+ * string/symbol keys, so rebuilding them via a property walk would silently corrupt their
1187
+ * rendering (Object.keys(new Date()) is `[]`; a Buffer's bytes live in a typed-array internal
1188
+ * slot, not enumerable own properties). Everything else - an object literal, a class instance, a
1189
+ * VM cross-realm object of either - IS walked and rebuilt: a class or custom-prototype instance is
1190
+ * just as capable of holding a nested Error as a plain object (`http_resp_msg` is a generic field,
1191
+ * not limited to the known deploy payload), and leaving instances raw would hand the raised inspect
1192
+ * depth below a real, generic secret-leak path. `types.is*` checks an internal slot, not the
1193
+ * prototype chain, so - like isNativeError / types.isMap / types.isSet elsewhere in this file -
1194
+ * this is realm-independent: a VM-created Date is still recognized as opaque.
1195
+ *
1196
+ * Promise is deliberately NOT included here (handled separately in deepSanitizeErrors, see below):
1197
+ * unlike the others, a Promise's resolved/rejected value is not merely internal-slot data with a
1198
+ * fixed rendering, it's arbitrary caller data - and util.inspect renders it directly, own-enumerable
1199
+ * properties and all (`util.inspect(Promise.resolve(errorWithSecretHeader), { depth: 8 })` prints
1200
+ * the header). There is no supported synchronous way to read that value in order to sanitize it, so
1201
+ * the only safe option is to never hand a Promise to inspect() raw.
1202
+ *
1203
+ * An *expando* own-enumerable property stashed directly on one of these (e.g. `const d = new
1204
+ * Date(); d.cause = secretError`) would otherwise leak the same way the Promise case above does -
1205
+ * inspect() renders own-enumerable properties on ANY object, opaque built-ins included. Handled by
1206
+ * hasEnumerableOwnProps/safeOpaqueBuiltinSummary below: the fast, zero-cost, common path (no
1207
+ * expando) returns the value raw and untouched; only a value actually carrying one pays for a safe
1208
+ * replacement.
1209
+ */
1210
+ function isOpaqueBuiltin(value) {
1211
+ return (node_util_1.types.isDate(value) ||
1212
+ node_util_1.types.isRegExp(value) ||
1213
+ node_util_1.types.isArrayBufferView(value) || // covers Buffer and every TypedArray/DataView
1214
+ node_util_1.types.isAnyArrayBuffer(value) ||
1215
+ node_util_1.types.isWeakMap(value) ||
1216
+ node_util_1.types.isWeakSet(value) ||
1217
+ node_util_1.types.isBoxedPrimitive(value) // a boxed Boolean/Number/String/Symbol/BigInt wrapper
1218
+ );
1219
+ }
1220
+ // %TypedArray%.prototype - the shared abstract superclass prototype every concrete TypedArray
1221
+ // (Uint8Array, Buffer, etc.) inherits `length` from. Read once so hasEnumerableOwnProps can bind
1222
+ // to it explicitly via Reflect.get, the same hijack-avoidance pattern as the Map/Set branches'
1223
+ // `Reflect.get(Map.prototype, 'size', value)` - the intrinsic `length` getter is spec-configurable,
1224
+ // so a hostile value can shadow it with an own `length` property reporting whatever it likes.
1225
+ const TYPED_ARRAY_PROTO = Object.getPrototypeOf(Uint8Array.prototype);
1226
+ // Above this many elements, hasEnumerableOwnProps skips its indexed-type check and fails closed
1227
+ // (assumes an expando IS present, safe direction) rather than paying for it - see the comment at
1228
+ // that check for why.
1229
+ const MAX_INDEXED_EXPANDO_CHECK_LENGTH = 10_000;
1230
+ /**
1231
+ * True if `value` has any own-enumerable string or symbol property beyond its own intrinsic data -
1232
+ * i.e. an expando - since none of isOpaqueBuiltin's types (nor a bare function) normally carry any.
1233
+ * Checked before deciding whether an opaque built-in/function is safe to hand to inspect() raw.
1234
+ * Errs conservative: if the check itself cannot be completed safely (a hostile `ownKeys`/descriptor
1235
+ * trap), treat that as "has an expando" rather than risk a false "clean" on something we couldn't
1236
+ * actually verify.
1237
+ *
1238
+ * A TypedArray/Buffer's own numeric indices ARE its intrinsic byte/element data, own-enumerable
1239
+ * exactly like any other array - `Object.keys(Buffer.from('hi'))` is `['0', '1']` - so those don't
1240
+ * count as expandos here; only a key beyond `[0, length)` does. A boxed String has the same shape
1241
+ * (`Object.keys(new String('hi'))` is the same `['0', '1']`) and gets the same carve-out; detected
1242
+ * via `types.isStringObject` (an internal-slot check, like `isOpaqueBuiltin`'s `types.is*` checks
1243
+ * above) rather than `Object.prototype.toString.call`, which reads the value's own
1244
+ * `Symbol.toStringTag` property - a hostile value defining that as a getter would have it run
1245
+ * during this supposedly passive classification. DataView has no such index
1246
+ * properties (its data is accessed only via get/set methods), so it's excluded from the carve-out
1247
+ * and goes through the plain `Object.keys(value).length > 0` check like any ordinary object.
1248
+ *
1249
+ * For either indexed shape, checking for an expando against `Object.keys` costs one array
1250
+ * allocation sized to the FULL length just to answer "clean or not" - fine for a small buffer, but
1251
+ * a multi-hundred-MB Buffer or huge boxed string would materialize a matching number of key strings
1252
+ * merely to decide this supposedly-cheap fast path applies, turning it into an attacker-sized scan
1253
+ * on the error-logging path itself. Above MAX_INDEXED_EXPANDO_CHECK_LENGTH elements, skip the check
1254
+ * and FAIL CLOSED (assume it HAS an expando) rather than skip it and assume clean: the safe
1255
+ * direction when we can't verify is always "has an expando" (same as the hostile-trap catch above),
1256
+ * never "verified clean" - failing open here would return e.g. a `Buffer.alloc(10_001)` carrying a
1257
+ * real, ordinary-sized expando raw, unsanitized. Failing closed still avoids the expensive scan
1258
+ * (that's the whole point of the size cap) - it just means a huge but genuinely expando-free
1259
+ * Buffer/string also renders as safeOpaqueBuiltinSummary's generic placeholder above this size,
1260
+ * rather than its full native format. Losing pretty-printing for a buffer/string that large is a
1261
+ * reasonable trade for never guessing "clean" on data we didn't actually check.
1262
+ */
1263
+ function hasEnumerableOwnProps(value) {
1264
+ try {
1265
+ const isTypedArray = ArrayBuffer.isView(value) && !node_util_1.types.isDataView(value);
1266
+ const isBoxedString = node_util_1.types.isStringObject(value);
1267
+ if (isTypedArray || isBoxedString) {
1268
+ // The typed-array getter is bound explicitly (see TYPED_ARRAY_PROTO above) to survive a
1269
+ // shadowed own `length`; a boxed String's `length` is a non-configurable, non-writable own
1270
+ // property per spec and can't be shadowed, so a direct read is already safe.
1271
+ const length = isTypedArray ? Reflect.get(TYPED_ARRAY_PROTO, 'length', value) : value.length;
1272
+ if (typeof length !== 'number')
1273
+ return true; // couldn't verify the intrinsic size - conservative
1274
+ if (length > MAX_INDEXED_EXPANDO_CHECK_LENGTH)
1275
+ return true; // too large to check - fail closed, not clean
1276
+ const keys = Object.keys(value);
1277
+ for (const key of keys) {
1278
+ const index = key === '' ? NaN : Number(key);
1279
+ if (!(Number.isInteger(index) && index >= 0 && index < length && String(index) === key))
1280
+ return true;
1281
+ }
1282
+ }
1283
+ else if (Object.keys(value).length > 0) {
1284
+ return true;
1285
+ }
1286
+ for (const sym of Object.getOwnPropertySymbols(value)) {
1287
+ if (Object.getOwnPropertyDescriptor(value, sym)?.enumerable)
1288
+ return true;
1289
+ }
1290
+ return false;
1291
+ }
1292
+ catch {
1293
+ return true;
1294
+ }
1295
+ }
1296
+ /**
1297
+ * Returns a value safe to hand to inspect() in place of an opaque built-in that (unusually) carries
1298
+ * an expando property - reached only via hasEnumerableOwnProps returning true, never on the common
1299
+ * expando-free path. Date/RegExp/WeakMap/WeakSet are cheap to reconstruct byte/value-for-value from
1300
+ * their intrinsic prototype (bound explicitly via .call/Reflect.get, not `value.getTime()` etc,
1301
+ * exactly so an own property shadowing that method - the same class of hijack the Map/Set branches
1302
+ * below guard against - can't run instead of the real accessor); WeakMap/WeakSet never expose their
1303
+ * entries via inspect regardless, so a fresh empty instance loses nothing. Buffer/TypedArray/
1304
+ * DataView/ArrayBuffer/boxed-primitives are left as a bounded type-tag summary instead: safely
1305
+ * reconstructing an exact byte-for-byte or value-for-value copy needs per-subtype branching that
1306
+ * isn't worth it for how rarely one of these ever carries an expando in the first place.
1307
+ */
1308
+ function safeOpaqueBuiltinSummary(value) {
1309
+ try {
1310
+ if (node_util_1.types.isDate(value))
1311
+ return new Date(Date.prototype.getTime.call(value));
1312
+ if (node_util_1.types.isRegExp(value)) {
1313
+ const source = Reflect.get(RegExp.prototype, 'source', value);
1314
+ // Built manually from each individual flag getter (bound via Reflect.get, same
1315
+ // hijack-avoidance pattern as source/Map/Set elsewhere in this file) rather than reading
1316
+ // the combined `RegExp.prototype.flags` getter: per spec, `flags` synthesizes its result by
1317
+ // reading `this.global`, `this.ignoreCase`, etc. as ordinary property gets on `value` -
1318
+ // each individual flag getter (`global`, `ignoreCase`, ...) instead reads the internal
1319
+ // [[OriginalFlags]] slot directly, the same as `source`. An expando shadowing e.g. `global`
1320
+ // with a hostile getter would otherwise have `flags` invoke it while merely trying to
1321
+ // reconstruct a safe copy of the RegExp.
1322
+ let flags = '';
1323
+ if (Reflect.get(RegExp.prototype, 'hasIndices', value))
1324
+ flags += 'd';
1325
+ if (Reflect.get(RegExp.prototype, 'global', value))
1326
+ flags += 'g';
1327
+ if (Reflect.get(RegExp.prototype, 'ignoreCase', value))
1328
+ flags += 'i';
1329
+ if (Reflect.get(RegExp.prototype, 'multiline', value))
1330
+ flags += 'm';
1331
+ if (Reflect.get(RegExp.prototype, 'dotAll', value))
1332
+ flags += 's';
1333
+ if (Reflect.get(RegExp.prototype, 'unicode', value))
1334
+ flags += 'u';
1335
+ if (Reflect.get(RegExp.prototype, 'unicodeSets', value))
1336
+ flags += 'v';
1337
+ if (Reflect.get(RegExp.prototype, 'sticky', value))
1338
+ flags += 'y';
1339
+ return new RegExp(source, flags);
1340
+ }
1341
+ if (node_util_1.types.isWeakMap(value))
1342
+ return new WeakMap();
1343
+ if (node_util_1.types.isWeakSet(value))
1344
+ return new WeakSet();
1345
+ }
1346
+ catch {
1347
+ // fall through to the generic tag-only summary below
1348
+ }
1349
+ // A fixed, types.is*-derived tag - never Object.prototype.toString.call(value), which reads the
1350
+ // value's own Symbol.toStringTag and would invoke a hostile getter defined there while merely
1351
+ // picking a label (the same class of bug fixed in isErrorLike's classification - see there). Every
1352
+ // branch here is an internal-slot check, so none of them can run caller-controlled code.
1353
+ let tag = 'value';
1354
+ if (node_util_1.types.isStringObject(value))
1355
+ tag = 'String';
1356
+ else if (node_util_1.types.isNumberObject(value))
1357
+ tag = 'Number';
1358
+ else if (node_util_1.types.isBooleanObject(value))
1359
+ tag = 'Boolean';
1360
+ else if (node_util_1.types.isSymbolObject(value))
1361
+ tag = 'Symbol';
1362
+ else if (node_util_1.types.isArrayBufferView(value))
1363
+ tag = 'ArrayBufferView';
1364
+ else if (node_util_1.types.isAnyArrayBuffer(value))
1365
+ tag = 'ArrayBuffer';
1366
+ return labelPlaceholder(`[${tag} with own properties omitted for safety]`);
1367
+ }
1368
+ /** Defines `key` as an own DATA property via defineProperty rather than `target[key] = value`.
1369
+ * Once a sanitized clone's prototype is restored to the original's (see deepSanitizeErrors), a
1370
+ * plain assignment for a key that has an inherited accessor further up that prototype chain would
1371
+ * invoke the INHERITED SETTER instead of creating an own property - running arbitrary code during
1372
+ * what should be a passive render - and a key literally named `__proto__` would hit the legacy
1373
+ * Object.prototype.__proto__ setter and reparent the clone instead of storing a property named
1374
+ * "__proto__". defineProperty always creates/replaces an own property directly, regardless of key
1375
+ * name or what the prototype chain declares. */
1376
+ function defineOwnProperty(target, key, value) {
1377
+ Object.defineProperty(target, key, { value, writable: true, enumerable: true, configurable: true });
1378
+ }
1379
+ /**
1380
+ * True if `proto` (and every prototype above it, up to but excluding Object.prototype/null) is
1381
+ * safe for a stateless sanitized clone to wear - i.e. util.inspect can later render a value with
1382
+ * this prototype without running any caller-controlled code.
1383
+ *
1384
+ * This used to be answered by actually calling `inspect()` on the empty clone as a probe - but
1385
+ * that IS running caller-controlled code, just earlier and silently: `inspect()` walks the full
1386
+ * prototype chain (not just the immediate level) doing its own class-name/tag detection, which
1387
+ * invokes a Proxy ANCESTOR's traps (`types.isProxy(proto)` alone only catches the immediate
1388
+ * level), reads an inherited `Symbol.toStringTag` getter, and - even with `customInspect: false`
1389
+ * suppressing the clone's OWN custom inspector - still leaves that same inspector to be invoked a
1390
+ * second time by the real render later. A hostile trap/getter that never returns wedges the
1391
+ * logging worker regardless of which of those two invocations reaches it.
1392
+ *
1393
+ * So this never invokes anything: `types.isProxy` and `Object.getOwnPropertyNames`/
1394
+ * `getOwnPropertyDescriptor`/`getPrototypeOf` are internal-slot/structural reads on each
1395
+ * non-Proxy level, same as the rest of this file's reflection. A level is rejected if it's a
1396
+ * Proxy (any trap, including on revocation) or defines any OWN accessor (getter/setter) property
1397
+ * - the exact shape of a branded built-in's internal-slot getter (e.g. URL.prototype's
1398
+ * `href`/`protocol`/...), and there is no way to tell "safe getter" from "hostile getter" without
1399
+ * invoking it, so any accessor anywhere in the chain fails closed. An ordinary class's prototype
1400
+ * (only a data `constructor` property, plus perhaps plain methods) passes, so a plain
1401
+ * custom-class instance still renders with its real class name (see the 'Diagnostic' test).
1402
+ */
1403
+ function isSafeToWearPrototype(proto) {
1404
+ let level = proto;
1405
+ while (level !== null && level !== Object.prototype) {
1406
+ if (node_util_1.types.isProxy(level))
1407
+ return false;
1408
+ let keys;
1409
+ try {
1410
+ keys = [...Object.getOwnPropertyNames(level), ...Object.getOwnPropertySymbols(level)];
1411
+ }
1412
+ catch {
1413
+ return false;
1414
+ }
1415
+ for (const key of keys) {
1416
+ if (key === 'constructor')
1417
+ continue; // an ordinary data property on every class prototype
1418
+ let descriptor;
1419
+ try {
1420
+ descriptor = Object.getOwnPropertyDescriptor(level, key);
1421
+ }
1422
+ catch {
1423
+ return false;
1424
+ }
1425
+ if (descriptor && (descriptor.get || descriptor.set))
1426
+ return false;
1427
+ }
1428
+ try {
1429
+ level = Object.getPrototypeOf(level);
1430
+ }
1431
+ catch {
1432
+ return false;
1433
+ }
1434
+ }
1435
+ return true;
1436
+ }
1437
+ /**
1438
+ * Recursively walks a plain object/array, replacing every Error-like value found at any depth
1439
+ * with its errorForLog wrapper (see isErrorLike/errorForLog and #1734). sanitizeErrorArgs is
1440
+ * deliberately shallow because it guards the hot, frequent top-level log-call path — this walk is
1441
+ * for inspectForLog's callers instead, which are low-frequency (a caught error's diagnostic
1442
+ * detail), so the extra traversal cost doesn't matter and full coverage does: an Error nested
1443
+ * anywhere inside a value later rendered with a raised inspect depth (see inspectForLog) would
1444
+ * otherwise surface its own-enumerable properties raw.
1445
+ *
1446
+ * Arrays, Map, Set, and every other object EXCEPT the isOpaqueBuiltin exclusions above are rebuilt
1447
+ * so an Error nested inside them at any depth is still reached and sanitized - a raw Error left
1448
+ * unsanitized anywhere in the tree would surface its own-enumerable properties (e.g. an axios
1449
+ * `config.headers.Authorization`) raw once the raised inspect depth below reaches it. A rebuilt
1450
+ * object/class-instance clone has the original's prototype restored (Object.setPrototypeOf) so
1451
+ * inspect() still shows its real class name and picks up any inspect.custom hook defined on the
1452
+ * class's prototype (not an own property, so the symbol walk below wouldn't otherwise see it) -
1453
+ * the clone differs from the original only in which of its OWN enumerable properties got swapped
1454
+ * for a sanitized/placeholder value, same as it would for a plain object.
1455
+ *
1456
+ * Cycle-safe via a WeakMap from original to its (in-progress) clone, registered before recursing
1457
+ * into children, so a cycle resolves to the clone in progress rather than falling back to the raw
1458
+ * original (which would bypass sanitization on the repeated branch) - and depth-capped so a
1459
+ * pathologically deep structure can't blow the stack. Every property read and recursive step is
1460
+ * individually guarded so one hostile getter or exotic nested value can only cost that one
1461
+ * field, not the whole render (inspectForLog's own try/catch around inspect() is still the final
1462
+ * backstop regardless).
1463
+ *
1464
+ * Never invokes an accessor (getter) property, and never resolves an overridable Symbol.iterator:
1465
+ * unlike util.inspect's default (which shows a getter as `[Getter]` without calling it), reading
1466
+ * `value[key]`/`value[sym]` for every own-enumerable key - or iterating an array/Map/Set with
1467
+ * `for...of`, which resolves the value's own-or-inherited `Symbol.iterator` - would run arbitrary
1468
+ * synchronous code, including a subclass instance's overridden iterator, while the logger is just
1469
+ * trying to report the *original* error (a hostile accessor/iterator that loops, blocks, or
1470
+ * mutates state runs regardless). Arrays are walked by own property descriptor per index instead
1471
+ * of `for...of`; Map/Set are walked via their intrinsic prototype methods bound with `.call`,
1472
+ * which reads the internal slot data directly rather than going through the instance's own (or an
1473
+ * overriding subclass's) iterator method. Objects read property descriptors and recurse only into
1474
+ * a data descriptor's value; an accessor gets accessorPlaceholder's label instead. Every own
1475
+ * property is written via defineOwnProperty rather than assignment, so a clone whose prototype was
1476
+ * restored to a class's prototype can't trigger an inherited setter (or the legacy `__proto__`
1477
+ * setter) partway through the walk.
1478
+ *
1479
+ * Breadth-bounded via `budget`, shared across the whole walk (not reset per container): each
1480
+ * container is capped at `budget.maxEntries` entries (with a placeholder noting what was
1481
+ * skipped), and the walk stops sanitizing entirely past `MAX_SANITIZE_NODES` total nodes visited,
1482
+ * regardless of per-container caps - see the constants' comment for why both are needed.
1483
+ */
1484
+ function deepSanitizeErrors(value, seen = new WeakMap(), depth = 0, budget = { nodes: 0, maxEntries: DEFAULT_MAX_SANITIZE_ENTRIES }) {
1485
+ // Functions are `typeof 'function'`, not 'object' - included here (rather than falling through
1486
+ // as if they were a harmless primitive) because a function is just as capable of carrying an
1487
+ // expando own-enumerable property (`fn.cause = secretError`, an ordinary and not-even-unusual
1488
+ // JS pattern) as a plain object is, and inspect() renders those own properties the same way.
1489
+ // Counted first, before even the primitive-leaf check below, so a primitive leaf (string/number/
1490
+ // etc in an array/object, the overwhelmingly common case) consumes exactly as much of the shared
1491
+ // budget as a container does. Without this, only container nodes counted towards
1492
+ // MAX_SANITIZE_NODES while each one's up-to-budget.maxEntries primitive children were free, so
1493
+ // e.g. 50,000 containers of 250 primitive fields each could still walk ~12.5 million values
1494
+ // before this cap ever engaged.
1495
+ if (++budget.nodes > MAX_SANITIZE_NODES)
1496
+ return labelPlaceholder('[Unrenderable value: sanitize budget exceeded]');
1497
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function'))
1498
+ return value;
1499
+ // Checked before isErrorLike/anything else that reflects on `value`: types.isProxy queries the
1500
+ // exotic object's internal slots directly (safe even on a revoked Proxy - it never throws), but
1501
+ // EVERY reflective operation past this point - `instanceof` inside isErrorLike, Array.isArray,
1502
+ // Object.getPrototypeOf, Object.keys - invokes whatever trap the Proxy's handler defines for it.
1503
+ // A throwing trap was already caught by the try/catch below, but a trap that hangs (an infinite
1504
+ // loop) or has side effects runs to completion regardless of any catch - on the very path meant
1505
+ // to log a DIFFERENT failure. There is no privileged way in userland to read a Proxy's target
1506
+ // without going through its traps (the exact reason native util.inspect's default rendering,
1507
+ // which does have that privileged access, isn't reusable here - see the Promise note above for
1508
+ // the same class of "can't safely introspect this" tradeoff), so a Proxy is never reflected on
1509
+ // at all: always a safe, static placeholder, trap or no trap.
1510
+ if (node_util_1.types.isProxy(value))
1511
+ return labelPlaceholder('[Proxy]');
1512
+ if (isErrorLike(value))
1513
+ return errorForLog(value);
1514
+ if (seen.has(value))
1515
+ return seen.get(value);
1516
+ // Depth-capped values are never handed back raw: an Error directly AT this depth is already
1517
+ // caught by isErrorLike above, but a plain container here could still hold an Error somewhere
1518
+ // inside it that we're choosing not to recurse into - handing it back unsanitized would let a
1519
+ // caller-requested depth greater than MAX_SANITIZE_DEPTH (the one caller in this codebase uses
1520
+ // 8, well under it) reach and print that Error's own-enumerable properties raw (#1734).
1521
+ if (depth >= MAX_SANITIZE_DEPTH)
1522
+ return labelPlaceholder('[Unrenderable value: sanitize depth exceeded]');
1523
+ let isArray, isMap, isSet, isFunction;
1524
+ try {
1525
+ isArray = Array.isArray(value);
1526
+ // types.isMap/isSet check an internal slot, not the prototype chain, so (like isNativeError)
1527
+ // they still recognize a Map/Set created in a different realm (component code runs through
1528
+ // node:vm) - `instanceof Map`/`Set` would not, silently leaving that container's contents
1529
+ // (and any Error nested inside) unsanitized at the raised inspect depth below.
1530
+ isMap = !isArray && node_util_1.types.isMap(value);
1531
+ isSet = !isArray && !isMap && node_util_1.types.isSet(value);
1532
+ isFunction = !isArray && !isMap && !isSet && typeof value === 'function';
1533
+ // See the Promise note on isOpaqueBuiltin above: its resolved/rejected value is arbitrary
1534
+ // caller data that inspect() renders directly (own-enumerable properties included), and there
1535
+ // is no supported synchronous way to read it in order to sanitize it - so unlike every other
1536
+ // opaque built-in, a Promise is never handed to inspect() raw, sanitized or not.
1537
+ if (!isArray && !isMap && !isSet && node_util_1.types.isPromise(value))
1538
+ return labelPlaceholder('[Promise]');
1539
+ if (!isArray && !isMap && !isSet && !isFunction && isOpaqueBuiltin(value)) {
1540
+ // Fast, faithful, zero-cost path for the overwhelming common case: no expando, so the
1541
+ // value is returned exactly as-is and inspect() renders its normal native format. Only a
1542
+ // value that actually carries an expando pays for safeOpaqueBuiltinSummary below.
1543
+ if (!hasEnumerableOwnProps(value))
1544
+ return value;
1545
+ return safeOpaqueBuiltinSummary(value);
1546
+ }
1547
+ if (isFunction && !hasEnumerableOwnProps(value))
1548
+ return value; // same fast path, for functions
1549
+ }
1550
+ catch {
1551
+ // The types.isProxy check above already routes every Proxy (revoked or not) to a placeholder
1552
+ // before this block ever runs, so nothing YET IDENTIFIED reaches this catch on real input -
1553
+ // but on the same "never fall back to raw" principle as every other catch in this walk, a
1554
+ // safe placeholder costs nothing here and closes the door on whatever unforeseen way a
1555
+ // future value might still throw here.
1556
+ return sanitizeFailurePlaceholder();
1557
+ }
1558
+ // A function reaching here carries an expando: fall through into the generic object walk below
1559
+ // (Object.keys/getOwnPropertyDescriptor/getOwnPropertySymbols all work the same on a function as
1560
+ // on a plain object) so that expando gets sanitized like any other object's - the clone won't be
1561
+ // callable and inspect() renders it as a plain `Function { ... }` rather than `[Function: name]`,
1562
+ // but that's a safe, honest trade for a case inspect() would otherwise render with a raw,
1563
+ // unsanitized secret alongside it.
1564
+ if (isArray) {
1565
+ const clone = [];
1566
+ seen.set(value, clone);
1567
+ const length = value.length;
1568
+ // Reserve one slot for the truncation marker when overflowing, so the clone's final size is
1569
+ // exactly budget.maxEntries - matching whatever maxArrayLength the caller will inspect() with
1570
+ // - rather than maxEntries + 1, which util.inspect's OWN truncation would then clip anyway,
1571
+ // silently hiding the marker (and the fact that anything was dropped at all) behind its
1572
+ // generic "... N more items" ellipsis.
1573
+ const overflow = length > budget.maxEntries;
1574
+ const limit = overflow ? budget.maxEntries - 1 : length;
1575
+ for (let i = 0; i < limit; i++) {
1576
+ let descriptor;
1577
+ try {
1578
+ descriptor = Object.getOwnPropertyDescriptor(value, i);
1579
+ }
1580
+ catch {
1581
+ continue; // leave index i a hole rather than risk a second throw
1582
+ }
1583
+ if (!descriptor)
1584
+ continue; // a genuine sparse-array hole - leave it, not `undefined`
1585
+ if (descriptor.get || descriptor.set) {
1586
+ clone[i] = accessorPlaceholder(descriptor);
1587
+ continue;
1588
+ }
1589
+ try {
1590
+ clone[i] = deepSanitizeErrors(descriptor.value, seen, depth + 1, budget);
1591
+ }
1592
+ catch {
1593
+ clone[i] = sanitizeFailurePlaceholder();
1594
+ }
1595
+ }
1596
+ if (overflow)
1597
+ clone[limit] = labelPlaceholder(`[${length - limit} more array entries omitted (sanitize budget)]`);
1598
+ return clone;
1599
+ }
1600
+ if (isMap) {
1601
+ const clone = new Map();
1602
+ seen.set(value, clone);
1603
+ // Reflect.get with an explicit receiver reads Map.prototype's intrinsic `size` getter bound
1604
+ // to value's internal slot, bypassing an overriding subclass's own `size` the same way the
1605
+ // .call-bound entries() below bypasses an overriding subclass's Symbol.iterator.
1606
+ const size = Reflect.get(Map.prototype, 'size', value);
1607
+ const overflow = size > budget.maxEntries;
1608
+ const limit = overflow ? budget.maxEntries - 1 : size;
1609
+ let count = 0;
1610
+ // Map.prototype.entries bound via .call reads the internal [[MapData]] slot directly,
1611
+ // rather than resolving value's own (or an overriding subclass's) Symbol.iterator.
1612
+ for (const [k, v] of Map.prototype.entries.call(value)) {
1613
+ if (count++ >= limit)
1614
+ break;
1615
+ // Sanitized independently (rather than in one combined try) so a throw sanitizing the key
1616
+ // doesn't also discard an already-sanitized value, or vice versa - each side falls back to
1617
+ // its own placeholder, never to the other's raw counterpart.
1618
+ let sanitizedKey, sanitizedValue;
1619
+ try {
1620
+ sanitizedKey = deepSanitizeErrors(k, seen, depth + 1, budget);
1621
+ }
1622
+ catch {
1623
+ sanitizedKey = sanitizeFailurePlaceholder();
1624
+ }
1625
+ try {
1626
+ sanitizedValue = deepSanitizeErrors(v, seen, depth + 1, budget);
1627
+ }
1628
+ catch {
1629
+ sanitizedValue = sanitizeFailurePlaceholder();
1630
+ }
1631
+ clone.set(sanitizedKey, sanitizedValue);
1632
+ }
1633
+ if (overflow)
1634
+ clone.set(labelPlaceholder('[truncated]'), labelPlaceholder(`[${size - limit} more Map entries omitted (sanitize budget)]`));
1635
+ return clone;
1636
+ }
1637
+ if (isSet) {
1638
+ const clone = new Set();
1639
+ seen.set(value, clone);
1640
+ const size = Reflect.get(Set.prototype, 'size', value);
1641
+ const overflow = size > budget.maxEntries;
1642
+ const limit = overflow ? budget.maxEntries - 1 : size;
1643
+ let count = 0;
1644
+ // Same rationale as the Map branch above: Set.prototype.values via .call, not for...of.
1645
+ for (const item of Set.prototype.values.call(value)) {
1646
+ if (count++ >= limit)
1647
+ break;
1648
+ try {
1649
+ clone.add(deepSanitizeErrors(item, seen, depth + 1, budget));
1650
+ }
1651
+ catch {
1652
+ clone.add(sanitizeFailurePlaceholder());
1653
+ }
1654
+ }
1655
+ if (overflow)
1656
+ clone.add(labelPlaceholder(`[${size - limit} more Set entries omitted (sanitize budget)]`));
1657
+ return clone;
1658
+ }
1659
+ const result = {};
1660
+ seen.set(value, result);
1661
+ try {
1662
+ const proto = Object.getPrototypeOf(value);
1663
+ // A branded built-in with private internal slots (URL, Headers, Request/Response, or any
1664
+ // custom class whose own accessors read `#private` state) throws when util.inspect later
1665
+ // renders a value wearing its prototype but lacking its real internal state - and losing it
1666
+ // unguarded would surface all the way up to inspectForLog's outer catch and replace the
1667
+ // ENTIRE structured payload - not just this one nested field - with a single "[Unrenderable
1668
+ // value]", losing phase/install_output/deployment_id and everything else alongside it, for
1669
+ // one URL-shaped field anywhere in the tree.
1670
+ //
1671
+ // This USED to be answered by actually calling `inspect()` on the empty clone as a probe, but
1672
+ // that runs the same caller-controlled code it's trying to guard against, just earlier and
1673
+ // silently: a Proxy ANYWHERE in the ancestor chain (not just the immediate `proto`) still gets
1674
+ // its traps invoked by inspect's own class-name/tag walk, an inherited `Symbol.toStringTag`
1675
+ // getter still gets read, and even `customInspect: false` only stops the PROBE's own call from
1676
+ // invoking a custom inspector - the real render right after still invokes it a second time,
1677
+ // corrupting a stateful/one-shot inspector's actual output. `isSafeToWearPrototype` answers the
1678
+ // same question without invoking anything (see its doc comment); `proto` is restored only when
1679
+ // it says so. Skipped for `null`/`Object.prototype`, the overwhelmingly common case for a
1680
+ // JSON-like diagnostic payload, where no exotic prototype is ever attached and this check would
1681
+ // be pure overhead for a class that could never fail it.
1682
+ if (proto !== null && proto !== Object.prototype && isSafeToWearPrototype(proto)) {
1683
+ Object.setPrototypeOf(result, proto);
1684
+ }
1685
+ }
1686
+ catch {
1687
+ // Fall through with the clone's default Object.prototype - inspect() renders it without the
1688
+ // class name, but every actual property attached below is still safe to render (see above for
1689
+ // why the prototype itself, not the data, is what's unsafe here).
1690
+ }
1691
+ // Object.keys/getOwnPropertySymbols themselves are one unavoidable O(n) pass (a plain object,
1692
+ // unlike Array/Map/Set, has no O(1) size to check before enumerating) - but everything AFTER
1693
+ // that (getOwnPropertyDescriptor + recurse + defineProperty per key) is the expensive part, and
1694
+ // that part IS capped at budget.maxEntries, same as every other container branch, so a
1695
+ // million-key object can't turn a single log call into a million-entry clone.
1696
+ const keys = Object.keys(value);
1697
+ const keysOverflow = keys.length > budget.maxEntries;
1698
+ const keysLimit = keysOverflow ? budget.maxEntries - 1 : keys.length;
1699
+ for (let i = 0; i < keysLimit; i++) {
1700
+ const key = keys[i];
1701
+ let descriptor;
1702
+ try {
1703
+ descriptor = Object.getOwnPropertyDescriptor(value, key);
1704
+ }
1705
+ catch {
1706
+ continue; // a hostile descriptor trap - omit rather than risk a second throw
1707
+ }
1708
+ if (!descriptor)
1709
+ continue; // removed mid-walk by another property's getter side effect
1710
+ if (descriptor.get || descriptor.set) {
1711
+ defineOwnProperty(result, key, accessorPlaceholder(descriptor));
1712
+ continue;
1713
+ }
1714
+ try {
1715
+ defineOwnProperty(result, key, deepSanitizeErrors(descriptor.value, seen, depth + 1, budget));
1716
+ }
1717
+ catch {
1718
+ defineOwnProperty(result, key, sanitizeFailurePlaceholder());
1719
+ }
1720
+ }
1721
+ if (keysOverflow)
1722
+ defineOwnProperty(result, KEYS_TRUNCATED_MARKER, labelPlaceholder(`[${keys.length - keysLimit} more properties omitted (sanitize budget)]`));
1723
+ const symbols = Object.getOwnPropertySymbols(value);
1724
+ const symbolsOverflow = symbols.length > budget.maxEntries;
1725
+ const symbolsLimit = symbolsOverflow ? budget.maxEntries - 1 : symbols.length;
1726
+ for (let i = 0; i < symbolsLimit; i++) {
1727
+ const sym = symbols[i];
1728
+ let descriptor;
1729
+ try {
1730
+ descriptor = Object.getOwnPropertyDescriptor(value, sym);
1731
+ }
1732
+ catch {
1733
+ continue;
1734
+ }
1735
+ if (!descriptor?.enumerable)
1736
+ continue;
1737
+ if (sym === node_util_1.inspect.custom) {
1738
+ // The custom renderer itself, not data - preserve unchanged (even if defined via an
1739
+ // accessor) rather than replace it with an accessor placeholder. util.inspect's own
1740
+ // top-level render finds this hook via the exact same `value[sym]` property access, so
1741
+ // reading it here isn't new arbitrary-code exposure the way an ordinary data getter
1742
+ // would be - it's the documented render-hook contract, just resolved one level earlier.
1743
+ let original;
1744
+ try {
1745
+ original = value[sym];
1746
+ }
1747
+ catch {
1748
+ continue; // leave unset - inspect() will render the rest of the object without a custom hook
1749
+ }
1750
+ if (typeof original !== 'function')
1751
+ continue; // not a valid hook - nothing to wrap or invoke
1752
+ // The hook itself runs at REAL render time, entirely outside this walk - so whatever it
1753
+ // RETURNS has never been through deepSanitizeErrors. Left as-is, a hook that returns a
1754
+ // closure-captured secret-bearing Error (or any value with one nested inside) would have
1755
+ // that Error rendered raw by the outer util.inspect call, bypassing this sanitizer entirely
1756
+ // (#1994 review). This wraps the hook so the CALL is unchanged (still invoked with `this`
1757
+ // bound to the original untrusted `value`, same as inspect would do directly) but its return
1758
+ // value is sanitized with a fresh depth/budget - independent of this walk's own counters,
1759
+ // which by render time are long since exhausted or out of scope.
1760
+ defineOwnProperty(result, sym, (...args) => {
1761
+ let rendered;
1762
+ try {
1763
+ rendered = original.apply(value, args);
1764
+ }
1765
+ catch (err) {
1766
+ return `[Unrenderable value: ${errorToString(err)}]`;
1767
+ }
1768
+ return deepSanitizeErrors(rendered, new WeakMap(), 0, { nodes: 0, maxEntries: DEFAULT_MAX_SANITIZE_ENTRIES });
1769
+ });
1770
+ continue;
1771
+ }
1772
+ if (descriptor.get || descriptor.set) {
1773
+ defineOwnProperty(result, sym, accessorPlaceholder(descriptor));
1774
+ continue;
1775
+ }
1776
+ try {
1777
+ defineOwnProperty(result, sym, deepSanitizeErrors(descriptor.value, seen, depth + 1, budget));
1778
+ }
1779
+ catch {
1780
+ defineOwnProperty(result, sym, sanitizeFailurePlaceholder());
1781
+ }
1782
+ }
1783
+ if (symbolsOverflow)
1784
+ defineOwnProperty(result, SYMBOLS_TRUNCATED_MARKER, labelPlaceholder(`[${symbols.length - symbolsLimit} more symbol properties omitted (sanitize budget)]`));
1785
+ return result;
1786
+ }
1787
+ /**
1788
+ * Returns a log-safe lazy wrapper around `util.inspect(value, options)`, for call sites that need
1789
+ * to log a structured, non-Error value with non-default inspect options (e.g. a deeper depth or
1790
+ * higher array/string limits than Console's defaults, to avoid flattening nested diagnostic data —
1791
+ * see harper#1982). Same rationale as errorForLog: the wrapper defers the (potentially expensive,
1792
+ * e.g. large-array) render until the logger's level gate actually writes the entry, and the render
1793
+ * itself can never throw regardless of what `value` is — including a hostile nested
1794
+ * `[util.inspect.custom]` hook — so a formatting failure can never mask the real thing being
1795
+ * logged (e.g. replace the caught operation error with an inspect error). Any Error nested inside
1796
+ * `value` is sanitized via deepSanitizeErrors before rendering, so a raised inspect depth here
1797
+ * can't surface a nested Error's own-enumerable properties (#1734) the way the raw value would.
1798
+ * `options.maxArrayLength`, if given, also bounds sanitization's own per-container breadth (see
1799
+ * deepSanitizeErrors' budget) - a caller raising the render limit is raising how much genuinely
1800
+ * needs to be walked, not just how much of an already-cheap walk gets displayed.
1801
+ */
1802
+ function inspectForLog(value, options) {
1803
+ const render = () => {
1804
+ try {
1805
+ const maxEntries = Number(options?.maxArrayLength);
1806
+ const budget = {
1807
+ nodes: 0,
1808
+ maxEntries: maxEntries > 0 ? Math.min(maxEntries, HARD_MAX_SANITIZE_ENTRIES) : DEFAULT_MAX_SANITIZE_ENTRIES,
1809
+ };
1810
+ return (0, node_util_1.inspect)(deepSanitizeErrors(value, new WeakMap(), 0, budget), options);
1811
+ }
1812
+ catch (err) {
1813
+ // errorToString is the guaranteed-never-throw stringifier (unlike `err instanceof Error` or
1814
+ // `String(err)` here, both of which can themselves throw on a hostile value - e.g. a revoked
1815
+ // Proxy thrown by a nested custom-inspect hook - which would otherwise escape this catch and
1816
+ // mask the real operation error being logged).
1817
+ return `[Unrenderable value: ${errorToString(err)}]`;
1818
+ }
1819
+ };
1820
+ return { [node_util_1.inspect.custom]: render, toString: render };
1821
+ }
1092
1822
  function setMainLogger(logger) {
1093
1823
  mainLogger = logger;
1094
1824
  }
@@ -1141,5 +1871,7 @@ exports.default = {
1141
1871
  AuthAuditLog,
1142
1872
  errorToString,
1143
1873
  errorForLog,
1874
+ inspectForLog,
1875
+ isErrorLike,
1144
1876
  };
1145
1877
  //# sourceMappingURL=harper_logger.js.map