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