@oneuptime/common 14.0.7 → 14.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. package/Models/DatabaseModels/Incident.ts +267 -1
  2. package/Models/DatabaseModels/IncidentCustomField.ts +202 -0
  3. package/Models/DatabaseModels/IncidentEpisode.ts +30 -0
  4. package/Models/DatabaseModels/IncidentTemplate.ts +126 -1
  5. package/Models/DatabaseModels/StatusPage.ts +55 -0
  6. package/Models/DatabaseModels/StatusPageSubscriber.ts +128 -0
  7. package/Server/API/IncidentAPI.ts +162 -0
  8. package/Server/API/StatusPageAPI.ts +487 -171
  9. package/Server/API/StatusPageSubscriberAPI.ts +63 -15
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.ts +49 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.ts +108 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.ts +33 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.ts +51 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.ts +33 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.ts +168 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.ts +44 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  18. package/Server/Infrastructure/Semaphore.ts +10 -0
  19. package/Server/Middleware/IdentityRateLimit.ts +108 -3
  20. package/Server/Middleware/SubscriberNotificationPreviewRateLimit.ts +237 -0
  21. package/Server/Middleware/SubscriberNotificationTestSendRateLimit.ts +304 -0
  22. package/Server/Services/DatabaseService.ts +93 -10
  23. package/Server/Services/IncidentCustomFieldService.ts +276 -1
  24. package/Server/Services/IncidentEpisodePublicNoteService.ts +28 -0
  25. package/Server/Services/IncidentEpisodeService.ts +15 -0
  26. package/Server/Services/IncidentEpisodeStateTimelineService.ts +27 -1
  27. package/Server/Services/IncidentPublicNoteService.ts +30 -1
  28. package/Server/Services/IncidentService.ts +1535 -65
  29. package/Server/Services/IncidentStateTimelineService.ts +13 -0
  30. package/Server/Services/IncidentTemplateService.ts +109 -0
  31. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +30 -1
  32. package/Server/Services/ScheduledMaintenanceService.ts +68 -18
  33. package/Server/Services/SmsService.ts +10 -0
  34. package/Server/Services/StatusPageService.ts +65 -8
  35. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +299 -37
  36. package/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.ts +129 -0
  37. package/Server/Services/StatusPageSubscriberService.ts +1161 -64
  38. package/Server/Types/Markdown.ts +153 -6
  39. package/Server/Types/Workflow/Components/Email.ts +61 -2
  40. package/Server/Utils/CustomField/CustomFieldRename.ts +378 -0
  41. package/Server/Utils/DataSource/EgressGuard.ts +66 -21
  42. package/Server/Utils/Mail/PinnedSmtpSocket.ts +130 -0
  43. package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +10 -0
  44. package/Server/Utils/SSRFProtection.ts +76 -16
  45. package/Server/Utils/StatusPage/IncidentStatusPageScope.ts +948 -0
  46. package/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.ts +656 -0
  47. package/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.ts +805 -0
  48. package/Server/Utils/StatusPage/StatusPageExclusion.ts +18 -0
  49. package/Server/Utils/StatusPage/StatusPageReadAccess.ts +125 -0
  50. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.ts +151 -0
  51. package/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.ts +70 -0
  52. package/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.ts +421 -0
  53. package/Server/Utils/StatusPage/SubscriberNotificationClaim.ts +107 -0
  54. package/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.ts +1049 -0
  55. package/Server/Utils/StatusPage/SubscriberNotificationFanOut.ts +180 -0
  56. package/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.ts +839 -0
  57. package/Server/Utils/StatusPage/SubscriberNotificationResendAccess.ts +393 -0
  58. package/Server/Utils/StatusPage/SubscriberNotificationRunLimit.ts +246 -0
  59. package/Server/Utils/StatusPage/SubscriberNotificationTiming.ts +133 -0
  60. package/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.ts +215 -0
  61. package/Server/Utils/StatusPageResource.ts +51 -20
  62. package/Server/Utils/VM/VMAPI.ts +1 -0
  63. package/Server/Utils/VM/VMRunner.ts +10 -0
  64. package/Server/Utils/Workflow/OAuth2TokenClient.ts +10 -0
  65. package/Server/Views/Partials/SensitiveUrlToken.ejs +8 -5
  66. package/Tests/App/Accounts/RegisterVerifyEmail.test.tsx +748 -0
  67. package/Tests/App/Accounts/ResendVerificationEmail.test.tsx +1222 -0
  68. package/Tests/App/Accounts/VerificationEmailResendUtils.test.ts +797 -0
  69. package/Tests/App/Accounts/VerifyEmailPending.test.tsx +538 -0
  70. package/Tests/App/Accounts/VerifyEmailResend.test.tsx +779 -0
  71. package/Tests/App/Dashboard/CustomFieldsSettingsIncidentOptions.test.tsx +357 -0
  72. package/Tests/App/Dashboard/EventNotes.test.tsx +776 -0
  73. package/Tests/App/Dashboard/EventNotesPages.test.tsx +119 -0
  74. package/Tests/App/Dashboard/EventNotesUtil.test.ts +116 -17
  75. package/Tests/App/Dashboard/EventOverviewPages.test.tsx +242 -5
  76. package/Tests/App/Dashboard/IncidentChangeStateNotifyDefault.test.tsx +91 -1
  77. package/Tests/App/Dashboard/IncidentCreateDetailsStep.test.tsx +643 -0
  78. package/Tests/App/Dashboard/IncidentCreateFromAlerts.test.tsx +8 -1
  79. package/Tests/App/Dashboard/IncidentCreateStatusPageScope.test.tsx +601 -0
  80. package/Tests/App/Dashboard/IncidentCustomFieldTemplateVariables.test.tsx +386 -0
  81. package/Tests/App/Dashboard/IncidentNoteTemplatePlaceholders.test.tsx +124 -0
  82. package/Tests/App/Dashboard/IncidentNoteTemplateVariablesLoader.test.ts +305 -0
  83. package/Tests/App/Dashboard/IncidentOverviewNotifyDefault.test.tsx +112 -0
  84. package/Tests/App/Dashboard/IncidentSettingsRenotifyOnPublish.test.tsx +674 -0
  85. package/Tests/App/Dashboard/IncidentSettingsStatusPageScope.test.tsx +1051 -0
  86. package/Tests/App/Dashboard/IncidentTemplateCustomFields.test.tsx +384 -0
  87. package/Tests/App/Dashboard/IncidentTemplateStatusPageScopeCard.test.tsx +215 -0
  88. package/Tests/App/Dashboard/PublicNotePagesNotifyDefault.test.tsx +113 -1
  89. package/Tests/App/Dashboard/SubscriberAudienceSummary.test.tsx +512 -0
  90. package/Tests/App/Dashboard/SubscriberNotificationPreviewModal.test.tsx +837 -0
  91. package/Tests/App/Dashboard/SubscriberNotificationStatusLabel.test.tsx +88 -0
  92. package/Tests/App/Dashboard/SubscriberNotificationStatusResend.test.tsx +473 -0
  93. package/Tests/App/Dashboard/SubscriberTemplateLivePreview.test.tsx +196 -0
  94. package/Tests/App/Dashboard/TeamAddedSubscribersUnsubscribedNotice.test.tsx +248 -0
  95. package/Tests/App/SensitiveUrlTokenBootstrap.test.ts +64 -3
  96. package/Tests/App/StatusPage/StatusPageUnsubscribePage.test.tsx +412 -0
  97. package/Tests/Models/IncidentCustomFieldApiSchemaContract.test.ts +217 -0
  98. package/Tests/Models/IncidentCustomFieldCreateSettingsColumns.test.ts +252 -0
  99. package/Tests/Models/StatusPageSubscriberUnsubscribeColumns.test.ts +218 -0
  100. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +7 -0
  101. package/Tests/Server/API/IncidentSubscriberAudienceAPI.test.ts +1408 -0
  102. package/Tests/Server/API/StatusPageIncidentScope.test.ts +1868 -0
  103. package/Tests/Server/API/StatusPageIncidentScopePostgres.test.ts +790 -0
  104. package/Tests/Server/API/StatusPageManageSubscriptionRecipient.test.ts +10 -1
  105. package/Tests/Server/API/StatusPageManageSubscriptionTemplateVariables.test.ts +206 -18
  106. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +14 -2
  107. package/Tests/Server/API/StatusPageSubscriberCreateResponse.test.ts +217 -0
  108. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +103 -2
  109. package/Tests/Server/API/StatusPageUnsubscribeAPI.test.ts +396 -0
  110. package/Tests/Server/Infrastructure/Postgres/AddIncidentStatusPageScopeMigration.test.ts +395 -0
  111. package/Tests/Server/Infrastructure/Postgres/AddIncidentTemplateStatusPageScopeFlagMigration.test.ts +150 -0
  112. package/Tests/Server/Infrastructure/Postgres/AddSubscriberNotificationClaimedAtMigration.test.ts +156 -0
  113. package/Tests/Server/Infrastructure/Postgres/SkipHiddenIncidentCreatedNotificationsMigration.test.ts +410 -0
  114. package/Tests/Server/Middleware/IdentityRateLimit.test.ts +551 -1
  115. package/Tests/Server/Middleware/SubscriberNotificationPreviewRateLimit.test.ts +238 -0
  116. package/Tests/Server/Middleware/SubscriberNotificationTestSendRateLimit.test.ts +245 -0
  117. package/Tests/Server/Services/AddStatusPageSubscriberUnsubscribeTokenMigration.test.ts +152 -0
  118. package/Tests/Server/Services/CrossProjectRelationWriteGuard.test.ts +221 -36
  119. package/Tests/Server/Services/DatabaseServiceCompareAndSetColumns.test.ts +191 -0
  120. package/Tests/Server/Services/IncidentCreatedRenotifyOnPublish.test.ts +681 -0
  121. package/Tests/Server/Services/IncidentCreatedResend.test.ts +775 -0
  122. package/Tests/Server/Services/IncidentCustomFieldRenamePostgres.test.ts +713 -0
  123. package/Tests/Server/Services/IncidentCustomFieldService.test.ts +751 -0
  124. package/Tests/Server/Services/IncidentCustomFieldValueValidation.test.ts +556 -0
  125. package/Tests/Server/Services/IncidentStatusPageScope.test.ts +2971 -0
  126. package/Tests/Server/Services/IncidentStatusPageScopeFeed.test.ts +131 -0
  127. package/Tests/Server/Services/IncidentStatusPageScopePostgres.test.ts +625 -0
  128. package/Tests/Server/Services/IncidentTemplateCustomFieldsCopy.test.ts +311 -0
  129. package/Tests/Server/Services/IncidentTemplateStatusPageScope.test.ts +363 -0
  130. package/Tests/Server/Services/PublicNoteEditFeedEventType.test.ts +213 -0
  131. package/Tests/Server/Services/PublicNoteResendAccess.test.ts +994 -0
  132. package/Tests/Server/Services/ScheduledMaintenanceSubscriberTemplateFormats.test.ts +289 -9
  133. package/Tests/Server/Services/SmsServiceFailIfNotSent.test.ts +67 -0
  134. package/Tests/Server/Services/StatusPageReportIncidentCounts.test.ts +610 -0
  135. package/Tests/Server/Services/StatusPageSubscriberAudienceCounts.test.ts +201 -0
  136. package/Tests/Server/Services/StatusPageSubscriberAudienceCountsPostgres.test.ts +271 -0
  137. package/Tests/Server/Services/StatusPageSubscriberConfirmationTemplateEscaping.test.ts +200 -0
  138. package/Tests/Server/Services/StatusPageSubscriberNotificationLogRedaction.test.ts +54 -0
  139. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateCompile.test.ts +37 -0
  140. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateIncidentRecordAccess.test.ts +772 -0
  141. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookup.test.ts +506 -0
  142. package/Tests/Server/Services/StatusPageSubscriberNotificationTemplateLookupPostgres.test.ts +261 -0
  143. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +21 -5
  144. package/Tests/Server/Services/StatusPageSubscriberUnsubscribe.test.ts +1618 -0
  145. package/Tests/Server/Services/StatusPageSubscriberUnsubscribeLinkSenders.test.ts +373 -0
  146. package/Tests/Server/Services/StatusPageSubscriberUnsubscribePostgres.test.ts +388 -0
  147. package/Tests/Server/Services/SubscriberNotificationInFlightRequeue.test.ts +500 -0
  148. package/Tests/Server/Types/MarkdownEmailLinks.test.ts +320 -0
  149. package/Tests/Server/Types/Workflow/Components/Email.test.ts +491 -1
  150. package/Tests/Server/Types/Workflow/Components/EmailSmtpPinning.test.ts +398 -0
  151. package/Tests/Server/Utils/CustomField/CustomFieldRename.test.ts +586 -0
  152. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +246 -10
  153. package/Tests/Server/Utils/DataSource/EgressGuardDeploymentConfig.test.ts +226 -0
  154. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +131 -0
  155. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +4 -0
  156. package/Tests/Server/Utils/EgressPolicyEnvironment.ts +45 -0
  157. package/Tests/Server/Utils/Mail/PinnedSmtpSocket.test.ts +409 -0
  158. package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +23 -0
  159. package/Tests/Server/Utils/SSRFProtection.test.ts +4 -0
  160. package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +4 -0
  161. package/Tests/Server/Utils/SSRFProtectionPrivateNetwork.test.ts +4 -0
  162. package/Tests/Server/Utils/SSRFProtectionResolutionDetail.test.ts +314 -0
  163. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScope.test.ts +1699 -0
  164. package/Tests/Server/Utils/StatusPage/IncidentStatusPageScopeQueriesPostgres.test.ts +508 -0
  165. package/Tests/Server/Utils/StatusPage/IncidentSubscriberAudienceCallerPermission.test.ts +246 -0
  166. package/Tests/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.test.ts +1348 -0
  167. package/Tests/Server/Utils/StatusPage/StatusPageReadAccess.test.ts +221 -0
  168. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.test.ts +132 -0
  169. package/Tests/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.test.ts +155 -0
  170. package/Tests/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.test.ts +708 -0
  171. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaim.test.ts +255 -0
  172. package/Tests/Server/Utils/StatusPage/SubscriberNotificationClaimPostgres.test.ts +528 -0
  173. package/Tests/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.test.ts +1238 -0
  174. package/Tests/Server/Utils/StatusPage/SubscriberNotificationFanOut.test.ts +472 -0
  175. package/Tests/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.test.ts +1168 -0
  176. package/Tests/Server/Utils/StatusPage/SubscriberNotificationRunLimit.test.ts +275 -0
  177. package/Tests/Server/Utils/StatusPage/SubscriberNotificationTiming.test.ts +131 -0
  178. package/Tests/Server/Utils/StatusPageResource.test.ts +98 -0
  179. package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +17 -0
  180. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +75 -0
  181. package/Tests/Types/CustomField/CustomFieldSavedViews.test.ts +247 -0
  182. package/Tests/Types/CustomField/CustomFieldTemplateValues.test.ts +186 -0
  183. package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +48 -0
  184. package/Tests/Types/CustomField/CustomFieldValueFormat.test.ts +215 -0
  185. package/Tests/Types/CustomField/CustomFieldValueValidator.test.ts +343 -0
  186. package/Tests/Types/CustomField/CustomFieldVariableKey.test.ts +364 -0
  187. package/Tests/Types/SafeHtml.test.ts +102 -0
  188. package/Tests/Types/StatusPage/IncidentCreatedRenotify.test.ts +419 -0
  189. package/Tests/Types/StatusPage/IncidentCreatedResend.test.ts +118 -0
  190. package/Tests/Types/StatusPage/IncidentScopeAddedPagesNotification.test.ts +558 -0
  191. package/Tests/Types/StatusPage/IncidentSubscriberAudience.test.ts +284 -0
  192. package/Tests/Types/StatusPage/StatusPageSubscriberUnsubscribe.test.ts +341 -0
  193. package/Tests/Types/StatusPage/SubscriberNotificationPreview.test.ts +196 -0
  194. package/Tests/Types/StatusPage/SubscriberNotificationResend.test.ts +201 -0
  195. package/Tests/Types/StatusPage/SubscriberNotificationTemplateCompiler.test.ts +277 -0
  196. package/Tests/Types/StatusPage/SubscriberNotificationTemplatePreview.test.ts +179 -0
  197. package/Tests/Types/StatusPage/SubscriberNotificationTemplateVariables.test.ts +324 -0
  198. package/Tests/Types/StatusPage/SubscriberUpdateNotification.test.ts +60 -13
  199. package/Tests/UI/Components/CustomFields/CustomFieldFormFields.test.ts +432 -0
  200. package/Tests/UI/Components/CustomFields/CustomFieldModelFormFields.test.tsx +685 -0
  201. package/Tests/UI/Components/CustomFields/CustomFieldsDetailOrder.test.tsx +469 -0
  202. package/Tests/UI/Components/Forms/ModelFormCustomFieldInputs.test.tsx +325 -0
  203. package/Tests/UI/Components/Forms/ModelFormDropdownOptionsRefused.test.tsx +358 -0
  204. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +22 -0
  205. package/Tests/UI/Monitor/NetworkPathView.test.tsx +289 -0
  206. package/Tests/Utils/Incident/IncidentNoteTemplateVariables.test.ts +623 -0
  207. package/Tests/Utils/Markdown/MarkdownEscape.test.ts +89 -0
  208. package/Tests/Utils/ProbeNetworkFailureUtil.test.ts +992 -0
  209. package/Tests/Utils/RequestFailedDetails.test.ts +575 -0
  210. package/Types/CustomField/CustomFieldOrder.ts +55 -0
  211. package/Types/CustomField/CustomFieldSavedViews.ts +225 -0
  212. package/Types/CustomField/CustomFieldTemplateMerge.ts +71 -0
  213. package/Types/CustomField/CustomFieldType.ts +14 -0
  214. package/Types/CustomField/CustomFieldValueFormat.ts +161 -0
  215. package/Types/CustomField/CustomFieldValueValidator.ts +488 -0
  216. package/Types/CustomField/CustomFieldVariableKey.ts +211 -0
  217. package/Types/SafeHtml.ts +93 -0
  218. package/Types/StatusPage/IncidentCreatedRenotify.ts +224 -0
  219. package/Types/StatusPage/IncidentCreatedResend.ts +108 -0
  220. package/Types/StatusPage/IncidentScopeAddedPagesNotification.ts +343 -0
  221. package/Types/StatusPage/IncidentSubscriberAudience.ts +332 -0
  222. package/Types/StatusPage/StatusPageSubscriberUnsubscribe.ts +405 -0
  223. package/Types/StatusPage/SubscriberNotificationInterruption.ts +32 -0
  224. package/Types/StatusPage/SubscriberNotificationPreview.ts +322 -0
  225. package/Types/StatusPage/SubscriberNotificationResend.ts +132 -0
  226. package/Types/StatusPage/SubscriberNotificationTemplateCompiler.ts +180 -0
  227. package/Types/StatusPage/SubscriberNotificationTemplatePreview.ts +301 -0
  228. package/Types/StatusPage/SubscriberNotificationTemplateVariables.ts +217 -4
  229. package/Types/StatusPage/SubscriberUpdateNotification.ts +32 -11
  230. package/UI/Components/CustomFields/CustomFieldFormFields.ts +317 -0
  231. package/UI/Components/CustomFields/CustomFieldModelFormFields.tsx +395 -0
  232. package/UI/Components/CustomFields/CustomFieldsDetail.tsx +95 -55
  233. package/UI/Components/Forms/ModelForm.tsx +70 -8
  234. package/UI/Components/Forms/Validation.ts +1 -1
  235. package/UI/Components/ModelFormModal/ModelFormModal.tsx +2 -4
  236. package/UI/Components/ModelTable/AnalyticsModelTable.tsx +2 -6
  237. package/UI/Components/ModelTable/BaseModelTable.tsx +9 -8
  238. package/UI/Components/ModelTable/CustomFieldColumns.tsx +14 -1
  239. package/UI/Components/ModelTable/ModelTable.tsx +6 -7
  240. package/UI/Utils/SensitiveUrlToken.ts +10 -5
  241. package/Utils/API.ts +247 -2
  242. package/Utils/Incident/IncidentNoteTemplateVariables.ts +304 -0
  243. package/Utils/Markdown/MarkdownEscape.ts +65 -0
  244. package/Utils/ProbeNetworkFailureUtil.ts +673 -0
  245. package/build/dist/Models/DatabaseModels/Incident.js +267 -1
  246. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  247. package/build/dist/Models/DatabaseModels/IncidentCustomField.js +207 -1
  248. package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
  249. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +31 -0
  250. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  251. package/build/dist/Models/DatabaseModels/IncidentTemplate.js +124 -1
  252. package/build/dist/Models/DatabaseModels/IncidentTemplate.js.map +1 -1
  253. package/build/dist/Models/DatabaseModels/StatusPage.js +56 -0
  254. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  255. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +131 -0
  256. package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
  257. package/build/dist/Server/API/IncidentAPI.js +100 -1
  258. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  259. package/build/dist/Server/API/StatusPageAPI.js +417 -176
  260. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  261. package/build/dist/Server/API/StatusPageSubscriberAPI.js +52 -13
  262. package/build/dist/Server/API/StatusPageSubscriberAPI.js.map +1 -1
  263. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js +41 -0
  264. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795300000000-SkipHiddenIncidentCreatedNotifications.js.map +1 -0
  265. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js +57 -0
  266. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795400000000-AddIncidentStatusPageScope.js.map +1 -0
  267. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js +23 -0
  268. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795500000000-AddIncidentTemplateStatusPageScopeFlag.js.map +1 -0
  269. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js +40 -0
  270. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795600000000-AddStatusPageSubscriberUnsubscribeToken.js.map +1 -0
  271. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js +26 -0
  272. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795700000000-AddStatusPageSubscriberIsAddedByTeam.js.map +1 -0
  273. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js +89 -0
  274. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795800000000-AddIncidentCustomFieldCreateAndNotificationSettings.js.map +1 -0
  275. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js +29 -0
  276. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1795900000000-AddSubscriberNotificationClaimedAt.js.map +1 -0
  277. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  278. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  279. package/build/dist/Server/Infrastructure/Semaphore.js +3 -0
  280. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  281. package/build/dist/Server/Middleware/IdentityRateLimit.js +89 -3
  282. package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
  283. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js +149 -0
  284. package/build/dist/Server/Middleware/SubscriberNotificationPreviewRateLimit.js.map +1 -0
  285. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js +190 -0
  286. package/build/dist/Server/Middleware/SubscriberNotificationTestSendRateLimit.js.map +1 -0
  287. package/build/dist/Server/Services/DatabaseService.js +56 -7
  288. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  289. package/build/dist/Server/Services/IncidentCustomFieldService.js +204 -8
  290. package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
  291. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +24 -0
  292. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  293. package/build/dist/Server/Services/IncidentEpisodeService.js +14 -0
  294. package/build/dist/Server/Services/IncidentEpisodeService.js.map +1 -1
  295. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js +26 -0
  296. package/build/dist/Server/Services/IncidentEpisodeStateTimelineService.js.map +1 -1
  297. package/build/dist/Server/Services/IncidentPublicNoteService.js +26 -1
  298. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  299. package/build/dist/Server/Services/IncidentService.js +1052 -0
  300. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  301. package/build/dist/Server/Services/IncidentStateTimelineService.js +12 -0
  302. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  303. package/build/dist/Server/Services/IncidentTemplateService.js +83 -0
  304. package/build/dist/Server/Services/IncidentTemplateService.js.map +1 -1
  305. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +26 -1
  306. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  307. package/build/dist/Server/Services/ScheduledMaintenanceService.js +47 -17
  308. package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
  309. package/build/dist/Server/Services/SmsService.js +2 -22
  310. package/build/dist/Server/Services/SmsService.js.map +1 -1
  311. package/build/dist/Server/Services/StatusPageService.js +47 -9
  312. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  313. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +237 -32
  314. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  315. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js +109 -0
  316. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateStatusPageService.js.map +1 -1
  317. package/build/dist/Server/Services/StatusPageSubscriberService.js +925 -99
  318. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  319. package/build/dist/Server/Types/Markdown.js +110 -6
  320. package/build/dist/Server/Types/Markdown.js.map +1 -1
  321. package/build/dist/Server/Types/Workflow/Components/Email.js +55 -6
  322. package/build/dist/Server/Types/Workflow/Components/Email.js.map +1 -1
  323. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js +211 -0
  324. package/build/dist/Server/Utils/CustomField/CustomFieldRename.js.map +1 -0
  325. package/build/dist/Server/Utils/DataSource/EgressGuard.js +41 -12
  326. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  327. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js +101 -0
  328. package/build/dist/Server/Utils/Mail/PinnedSmtpSocket.js.map +1 -0
  329. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +9 -0
  330. package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
  331. package/build/dist/Server/Utils/SSRFProtection.js +48 -6
  332. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  333. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js +631 -0
  334. package/build/dist/Server/Utils/StatusPage/IncidentStatusPageScope.js.map +1 -0
  335. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js +403 -0
  336. package/build/dist/Server/Utils/StatusPage/IncidentSubscriberAudienceBuilder.js.map +1 -0
  337. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js +451 -0
  338. package/build/dist/Server/Utils/StatusPage/IncidentTemplateVariableBuilder.js.map +1 -0
  339. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js +13 -0
  340. package/build/dist/Server/Utils/StatusPage/StatusPageExclusion.js.map +1 -0
  341. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js +91 -0
  342. package/build/dist/Server/Utils/StatusPage/StatusPageReadAccess.js.map +1 -0
  343. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js +101 -0
  344. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeNotice.js.map +1 -0
  345. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js +47 -0
  346. package/build/dist/Server/Utils/StatusPage/StatusPageSubscriberUnsubscribeToken.js.map +1 -0
  347. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js +273 -0
  348. package/build/dist/Server/Utils/StatusPage/SubscriberIncidentEmailBuilder.js.map +1 -0
  349. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js +73 -0
  350. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationClaim.js.map +1 -0
  351. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js +722 -0
  352. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationDeliveryRecord.js.map +1 -0
  353. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js +102 -0
  354. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationFanOut.js.map +1 -0
  355. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js +527 -0
  356. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationPreviewBuilder.js.map +1 -0
  357. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js +278 -0
  358. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationResendAccess.js.map +1 -0
  359. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js +137 -0
  360. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationRunLimit.js.map +1 -0
  361. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js +103 -0
  362. package/build/dist/Server/Utils/StatusPage/SubscriberNotificationTiming.js.map +1 -0
  363. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js +114 -0
  364. package/build/dist/Server/Utils/StatusPage/SubscriberTemplateIncidentRecordAccess.js.map +1 -0
  365. package/build/dist/Server/Utils/StatusPageResource.js +39 -7
  366. package/build/dist/Server/Utils/StatusPageResource.js.map +1 -1
  367. package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
  368. package/build/dist/Server/Utils/VM/VMRunner.js +1 -0
  369. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  370. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js +3 -0
  371. package/build/dist/Server/Utils/Workflow/OAuth2TokenClient.js.map +1 -1
  372. package/build/dist/Types/CustomField/CustomFieldOrder.js +36 -0
  373. package/build/dist/Types/CustomField/CustomFieldOrder.js.map +1 -0
  374. package/build/dist/Types/CustomField/CustomFieldSavedViews.js +135 -0
  375. package/build/dist/Types/CustomField/CustomFieldSavedViews.js.map +1 -0
  376. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js +27 -0
  377. package/build/dist/Types/CustomField/CustomFieldTemplateMerge.js.map +1 -0
  378. package/build/dist/Types/CustomField/CustomFieldType.js +12 -0
  379. package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
  380. package/build/dist/Types/CustomField/CustomFieldValueFormat.js +126 -0
  381. package/build/dist/Types/CustomField/CustomFieldValueFormat.js.map +1 -0
  382. package/build/dist/Types/CustomField/CustomFieldValueValidator.js +280 -0
  383. package/build/dist/Types/CustomField/CustomFieldValueValidator.js.map +1 -0
  384. package/build/dist/Types/CustomField/CustomFieldVariableKey.js +150 -0
  385. package/build/dist/Types/CustomField/CustomFieldVariableKey.js.map +1 -0
  386. package/build/dist/Types/SafeHtml.js +84 -0
  387. package/build/dist/Types/SafeHtml.js.map +1 -0
  388. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js +117 -0
  389. package/build/dist/Types/StatusPage/IncidentCreatedRenotify.js.map +1 -0
  390. package/build/dist/Types/StatusPage/IncidentCreatedResend.js +86 -0
  391. package/build/dist/Types/StatusPage/IncidentCreatedResend.js.map +1 -0
  392. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js +246 -0
  393. package/build/dist/Types/StatusPage/IncidentScopeAddedPagesNotification.js.map +1 -0
  394. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js +183 -0
  395. package/build/dist/Types/StatusPage/IncidentSubscriberAudience.js.map +1 -0
  396. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js +277 -0
  397. package/build/dist/Types/StatusPage/StatusPageSubscriberUnsubscribe.js.map +1 -0
  398. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js +27 -0
  399. package/build/dist/Types/StatusPage/SubscriberNotificationInterruption.js.map +1 -0
  400. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js +190 -0
  401. package/build/dist/Types/StatusPage/SubscriberNotificationPreview.js.map +1 -0
  402. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js +102 -0
  403. package/build/dist/Types/StatusPage/SubscriberNotificationResend.js.map +1 -0
  404. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js +97 -0
  405. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateCompiler.js.map +1 -0
  406. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js +175 -0
  407. package/build/dist/Types/StatusPage/SubscriberNotificationTemplatePreview.js.map +1 -0
  408. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js +144 -4
  409. package/build/dist/Types/StatusPage/SubscriberNotificationTemplateVariables.js.map +1 -1
  410. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js +29 -9
  411. package/build/dist/Types/StatusPage/SubscriberUpdateNotification.js.map +1 -1
  412. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js +169 -0
  413. package/build/dist/UI/Components/CustomFields/CustomFieldFormFields.js.map +1 -0
  414. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js +212 -0
  415. package/build/dist/UI/Components/CustomFields/CustomFieldModelFormFields.js.map +1 -0
  416. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +64 -49
  417. package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
  418. package/build/dist/UI/Components/Forms/ModelForm.js +39 -5
  419. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  420. package/build/dist/UI/Components/Forms/Validation.js +1 -1
  421. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  422. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  423. package/build/dist/UI/Components/ModelTable/AnalyticsModelTable.js.map +1 -1
  424. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +2 -2
  425. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  426. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +13 -1
  427. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  428. package/build/dist/UI/Components/ModelTable/ModelTable.js +1 -1
  429. package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
  430. package/build/dist/UI/Utils/SensitiveUrlToken.js +10 -5
  431. package/build/dist/UI/Utils/SensitiveUrlToken.js.map +1 -1
  432. package/build/dist/Utils/API.js +177 -1
  433. package/build/dist/Utils/API.js.map +1 -1
  434. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js +155 -0
  435. package/build/dist/Utils/Incident/IncidentNoteTemplateVariables.js.map +1 -0
  436. package/build/dist/Utils/Markdown/MarkdownEscape.js +48 -0
  437. package/build/dist/Utils/Markdown/MarkdownEscape.js.map +1 -1
  438. package/build/dist/Utils/ProbeNetworkFailureUtil.js +460 -0
  439. package/build/dist/Utils/ProbeNetworkFailureUtil.js.map +1 -0
  440. package/package.json +1 -1
@@ -1,6 +1,5 @@
1
1
  import dns from "dns";
2
2
  import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
3
- import { IsBillingEnabled } from "../../../../Server/EnvironmentConfig";
4
3
  import DataSourceEgressGuard, {
5
4
  AddressVerdict,
6
5
  DEFAULT_DNS_RESOLVE_ATTEMPTS,
@@ -17,12 +16,22 @@ import EgressGuardException, {
17
16
  } from "../../../../Types/Exception/EgressGuardException";
18
17
 
19
18
  const ENV_VAR_NAME: string = "DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES";
19
+ const BILLING_ENV_VAR_NAME: string = "BILLING_ENABLED";
20
20
 
21
21
  let savedEnvValue: string | undefined = undefined;
22
+ let savedBillingValue: string | undefined = undefined;
22
23
 
24
+ /*
25
+ * Every test starts on a self-hosted install. Both variables decide how much a
26
+ * refusal is allowed to say, and CI exports BILLING_ENABLED=true, so a test
27
+ * that inherited the environment would assert a different contract there than
28
+ * on a laptop. Tests about SaaS set BILLING_ENABLED themselves.
29
+ */
23
30
  beforeEach(() => {
24
31
  savedEnvValue = process.env[ENV_VAR_NAME];
32
+ savedBillingValue = process.env[BILLING_ENV_VAR_NAME];
25
33
  delete process.env[ENV_VAR_NAME];
34
+ delete process.env[BILLING_ENV_VAR_NAME];
26
35
  });
27
36
 
28
37
  afterEach(() => {
@@ -31,6 +40,11 @@ afterEach(() => {
31
40
  } else {
32
41
  process.env[ENV_VAR_NAME] = savedEnvValue;
33
42
  }
43
+ if (savedBillingValue === undefined) {
44
+ delete process.env[BILLING_ENV_VAR_NAME];
45
+ } else {
46
+ process.env[BILLING_ENV_VAR_NAME] = savedBillingValue;
47
+ }
34
48
  jest.restoreAllMocks();
35
49
  });
36
50
 
@@ -905,6 +919,33 @@ describe("DataSourceEgressGuard.assertHostnameAllowed - numeric hostname encodin
905
919
  }
906
920
  });
907
921
 
922
+ describe("DataSourceEgressGuard.shouldIncludeResolutionDetail", () => {
923
+ test("defaults to detail on a self-hosted install", () => {
924
+ expect(DataSourceEgressGuard.shouldIncludeResolutionDetail()).toBe(true);
925
+ });
926
+
927
+ test("defaults to no detail on SaaS", () => {
928
+ process.env[BILLING_ENV_VAR_NAME] = "true";
929
+ expect(DataSourceEgressGuard.shouldIncludeResolutionDetail()).toBe(false);
930
+ });
931
+
932
+ test("defaults to no detail once an operator forces SaaS policy", () => {
933
+ process.env[ENV_VAR_NAME] = "true";
934
+ expect(DataSourceEgressGuard.shouldIncludeResolutionDetail()).toBe(false);
935
+ });
936
+
937
+ test("an explicit value wins in either deployment", () => {
938
+ expect(DataSourceEgressGuard.shouldIncludeResolutionDetail(false)).toBe(
939
+ false,
940
+ );
941
+
942
+ process.env[BILLING_ENV_VAR_NAME] = "true";
943
+ expect(DataSourceEgressGuard.shouldIncludeResolutionDetail(true)).toBe(
944
+ true,
945
+ );
946
+ });
947
+ });
948
+
908
949
  describe("DataSourceEgressGuard.shouldBlockPrivateAddresses", () => {
909
950
  test("returns true when DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES=true", () => {
910
951
  process.env[ENV_VAR_NAME] = "true";
@@ -913,16 +954,18 @@ describe("DataSourceEgressGuard.shouldBlockPrivateAddresses", () => {
913
954
 
914
955
  test("falls back to the billing flag when the env var is unset", () => {
915
956
  delete process.env[ENV_VAR_NAME];
916
- expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(
917
- IsBillingEnabled,
918
- );
957
+ expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
958
+
959
+ process.env[BILLING_ENV_VAR_NAME] = "true";
960
+ expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(true);
919
961
  });
920
962
 
921
963
  test("treats any value other than 'true' as unset", () => {
922
964
  process.env[ENV_VAR_NAME] = "false";
923
- expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(
924
- IsBillingEnabled,
925
- );
965
+ expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
966
+
967
+ process.env[BILLING_ENV_VAR_NAME] = "true";
968
+ expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(true);
926
969
  });
927
970
 
928
971
  test("checkAddress derives the policy from the env var when options omit the flag", () => {
@@ -1174,7 +1217,7 @@ describe("DataSourceEgressGuard - typed failure reasons", () => {
1174
1217
  );
1175
1218
 
1176
1219
  test.each([undefined, true])(
1177
- "a resolver rejection is ResolutionFailed when includeResolvedAddressInError is %s",
1220
+ "a resolver rejection is ResolutionFailed on a self-hosted install when includeResolvedAddressInError is %s",
1178
1221
  async (includeResolvedAddressInError: boolean | undefined) => {
1179
1222
  const error: Error = await captureRejection(
1180
1223
  DataSourceEgressGuard.assertHostnameAllowed("missing.example.com", {
@@ -1195,7 +1238,7 @@ describe("DataSourceEgressGuard - typed failure reasons", () => {
1195
1238
  );
1196
1239
 
1197
1240
  test.each([undefined, true])(
1198
- "an empty DNS answer is ResolutionFailed when includeResolvedAddressInError is %s",
1241
+ "an empty DNS answer is ResolutionFailed on a self-hosted install when includeResolvedAddressInError is %s",
1199
1242
  async (includeResolvedAddressInError: boolean | undefined) => {
1200
1243
  const resolver: Resolver = makeResolver([]);
1201
1244
  const error: Error = await captureRejection(
@@ -1217,7 +1260,7 @@ describe("DataSourceEgressGuard - typed failure reasons", () => {
1217
1260
  );
1218
1261
 
1219
1262
  test.each([undefined, true])(
1220
- "a blocked resolved address is AddressBlocked when includeResolvedAddressInError is %s",
1263
+ "a blocked resolved address is AddressBlocked on a self-hosted install when includeResolvedAddressInError is %s",
1221
1264
  async (includeResolvedAddressInError: boolean | undefined) => {
1222
1265
  const resolver: Resolver = makeResolver([
1223
1266
  { address: "10.23.45.67", family: 4 },
@@ -1542,6 +1585,199 @@ describe("DataSourceEgressGuard - the sanitized failure is one indistinguishable
1542
1585
  expect(line).toContain(ORACLE_HOSTNAME);
1543
1586
  }
1544
1587
  });
1588
+
1589
+ /*
1590
+ * Most callers never set includeResolvedAddressInError: the workflow Send
1591
+ * Email step, a project's SMTP server, OAuth token URLs, LLM providers,
1592
+ * data sources, Runbook HTTP steps. Each echoes the guard's message to
1593
+ * project members, so on OneUptime Cloud the default itself has to be the
1594
+ * sanitized path — a member typing `redis` or
1595
+ * `kubernetes.default.svc.cluster.local` into any of those fields must not
1596
+ * learn whether the name exists or what it points at.
1597
+ */
1598
+ describe("when the caller leaves includeResolvedAddressInError unset", () => {
1599
+ const callerOptions: EgressGuardOptions = { targetLabel: "SMTP server" };
1600
+
1601
+ const resolverReturnsLoopback: EgressResolveFunction = makeResolver([
1602
+ { address: "127.0.0.1", family: 4 },
1603
+ ]).resolveFunction;
1604
+
1605
+ test.each([
1606
+ ["on SaaS (BILLING_ENABLED=true)", BILLING_ENV_VAR_NAME],
1607
+ [
1608
+ "once an operator forces SaaS policy (DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES=true)",
1609
+ ENV_VAR_NAME,
1610
+ ],
1611
+ ])(
1612
+ "%s a DNS failure and a blocked resolution are indistinguishable",
1613
+ async (_deployment: string, envVarName: string) => {
1614
+ process.env[envVarName] = "true";
1615
+
1616
+ const errors: Array<Error> = [
1617
+ await failWith(resolverRejects, callerOptions),
1618
+ await failWith(resolverReturnsNothing, callerOptions),
1619
+ await failWith(resolverReturnsPrivateAddress, callerOptions),
1620
+ await failWith(resolverReturnsLoopback, callerOptions),
1621
+ ];
1622
+
1623
+ const expectedFingerprint: EgressFailureFingerprint = fingerprint(
1624
+ errors[0]!,
1625
+ );
1626
+ expect(expectedFingerprint.message).toBe(
1627
+ `SMTP server host ${ORACLE_HOSTNAME} could not be reached.`,
1628
+ );
1629
+ expect(expectedFingerprint.reason).toBe(
1630
+ EgressFailureReason.Unreachable,
1631
+ );
1632
+
1633
+ for (const error of errors) {
1634
+ expect(fingerprint(error)).toEqual(expectedFingerprint);
1635
+ expect(Object.keys(error).sort()).toEqual(["_code", "reason"]);
1636
+ expect(JSON.stringify(error)).not.toContain(SECRET_ADDRESS);
1637
+ expect(error.message).not.toContain("127.0.0.1");
1638
+ expect(error.message).not.toContain("ENOTFOUND");
1639
+ }
1640
+ },
1641
+ );
1642
+
1643
+ test("on SaaS the URL entry points are sanitized too", async () => {
1644
+ /*
1645
+ * LLM providers, OAuth token URLs, Runbook HTTP steps and domain
1646
+ * verification come in through the URL forms, not the hostname one.
1647
+ */
1648
+ process.env[BILLING_ENV_VAR_NAME] = "true";
1649
+
1650
+ const missingError: Error = await captureRejection(
1651
+ DataSourceEgressGuard.assertUrlAllowedAndPin(
1652
+ `https://${ORACLE_HOSTNAME}/v1/chat/completions`,
1653
+ { targetLabel: "LLM provider", resolveFunction: resolverRejects },
1654
+ ),
1655
+ );
1656
+ const internalError: Error = await captureRejection(
1657
+ DataSourceEgressGuard.assertUrlAllowed(
1658
+ `https://${ORACLE_HOSTNAME}/token`,
1659
+ {
1660
+ targetLabel: "LLM provider",
1661
+ resolveFunction: resolverReturnsPrivateAddress,
1662
+ },
1663
+ ),
1664
+ );
1665
+
1666
+ expect(fingerprint(internalError)).toEqual(fingerprint(missingError));
1667
+ expect(missingError.message).toBe(
1668
+ `LLM provider host ${ORACLE_HOSTNAME} could not be reached.`,
1669
+ );
1670
+ });
1671
+
1672
+ test("on SaaS a caller that loosens blockPrivateAddresses still gets no detail", async () => {
1673
+ /*
1674
+ * The default follows the deployment, not the per-call address policy:
1675
+ * who typed the target is what makes the detail an oracle.
1676
+ */
1677
+ process.env[BILLING_ENV_VAR_NAME] = "true";
1678
+ const loosenedOptions: EgressGuardOptions = {
1679
+ ...callerOptions,
1680
+ blockPrivateAddresses: false,
1681
+ };
1682
+
1683
+ const missingError: Error = await failWith(
1684
+ resolverRejects,
1685
+ loosenedOptions,
1686
+ );
1687
+ const loopbackError: Error = await failWith(
1688
+ resolverReturnsLoopback,
1689
+ loosenedOptions,
1690
+ );
1691
+
1692
+ expect(fingerprint(loopbackError)).toEqual(fingerprint(missingError));
1693
+ expect(loopbackError.message).toBe(
1694
+ `SMTP server host ${ORACLE_HOSTNAME} could not be reached.`,
1695
+ );
1696
+ });
1697
+
1698
+ test("on SaaS an explicit true still gets the detail", async () => {
1699
+ process.env[BILLING_ENV_VAR_NAME] = "true";
1700
+ const detailedOptions: EgressGuardOptions = {
1701
+ ...callerOptions,
1702
+ includeResolvedAddressInError: true,
1703
+ };
1704
+
1705
+ const missingError: Error = await failWith(
1706
+ resolverRejects,
1707
+ detailedOptions,
1708
+ );
1709
+ const internalError: Error = await failWith(
1710
+ resolverReturnsPrivateAddress,
1711
+ detailedOptions,
1712
+ );
1713
+
1714
+ expectEgressFailure(missingError, EgressFailureReason.ResolutionFailed);
1715
+ expectEgressFailure(internalError, EgressFailureReason.AddressBlocked);
1716
+ expect(internalError.message).toBe(
1717
+ `SMTP server host ${ORACLE_HOSTNAME} resolves to ${SECRET_ADDRESS}, which is not allowed: private network address.`,
1718
+ );
1719
+ });
1720
+
1721
+ test("on SaaS a literal IP is still refused with its reason", async () => {
1722
+ // It restates what the member typed and reveals nothing about DNS.
1723
+ process.env[BILLING_ENV_VAR_NAME] = "true";
1724
+
1725
+ const error: Error = await captureRejection(
1726
+ DataSourceEgressGuard.assertHostnameAllowed(SECRET_ADDRESS, {
1727
+ ...callerOptions,
1728
+ }),
1729
+ );
1730
+
1731
+ expectEgressFailure(error, EgressFailureReason.AddressBlocked);
1732
+ expect(error.message).toBe(
1733
+ `SMTP server host ${SECRET_ADDRESS} is not allowed: private network address.`,
1734
+ );
1735
+ });
1736
+
1737
+ test("on SaaS the operator still gets the precise cause in the debug log", async () => {
1738
+ process.env[BILLING_ENV_VAR_NAME] = "true";
1739
+ const debugSpy: jest.SpyInstance = jest
1740
+ .spyOn(logger, "debug")
1741
+ .mockImplementation((): void => {
1742
+ // Swallow the output; only the call itself is under test.
1743
+ });
1744
+
1745
+ await failWith(resolverRejects, callerOptions);
1746
+ await failWith(resolverReturnsNothing, callerOptions);
1747
+ await failWith(resolverReturnsPrivateAddress, callerOptions);
1748
+
1749
+ const loggedLines: Array<string> = debugSpy.mock.calls.map(
1750
+ (call: Array<unknown>) => {
1751
+ return String(call[0]);
1752
+ },
1753
+ );
1754
+
1755
+ expect(loggedLines).toHaveLength(3);
1756
+ expect(loggedLines[0]).toContain("ENOTFOUND");
1757
+ expect(loggedLines[1]).toContain("no addresses");
1758
+ expect(loggedLines[2]).toContain(SECRET_ADDRESS);
1759
+ expect(loggedLines[2]).toContain("private network address");
1760
+ });
1761
+
1762
+ test("a self-hosted install keeps the operator-friendly detail", async () => {
1763
+ // Private ranges are reachable here, so loopback is the refused case.
1764
+ const missingError: Error = await failWith(
1765
+ resolverRejects,
1766
+ callerOptions,
1767
+ );
1768
+ const loopbackError: Error = await failWith(
1769
+ resolverReturnsLoopback,
1770
+ callerOptions,
1771
+ );
1772
+
1773
+ expect(missingError.message).toBe(
1774
+ `Could not resolve smtp server host ${ORACLE_HOSTNAME}: getaddrinfo ENOTFOUND ${ORACLE_HOSTNAME} ${SECRET_ADDRESS}`,
1775
+ );
1776
+ expect(loopbackError.message).toBe(
1777
+ `SMTP server host ${ORACLE_HOSTNAME} resolves to 127.0.0.1, which is not allowed: loopback address.`,
1778
+ );
1779
+ });
1780
+ });
1545
1781
  });
1546
1782
 
1547
1783
  /*
@@ -0,0 +1,226 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import yaml from "js-yaml";
5
+ import DataSourceEgressGuard, {
6
+ ResolvedAddress,
7
+ } from "../../../../Server/Utils/DataSource/EgressGuard";
8
+
9
+ /*
10
+ * DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES lets a self-hosted operator make the
11
+ * egress guard refuse private ranges for data sources, LLM providers, SMTP,
12
+ * OAuth token URLs, OIDC discovery and runbook HTTP steps. The guard has read
13
+ * it for a long time, and the docs told operators to set it, but Docker
14
+ * Compose never passed it to the app container and config.example.env had no
15
+ * line for it -- so setting it in config.env did nothing.
16
+ *
17
+ * This pins the Compose half of the wiring (the Helm half is covered by
18
+ * HelmChart/Public/oneuptime/tests/outbound-connections-egress_test.yaml) and
19
+ * ties it to the name the guard actually reads, by feeding the interpolated
20
+ * value to the real guard rather than comparing strings.
21
+ */
22
+
23
+ const ENV_VAR_NAME: string = "DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES";
24
+ // The webhook half of the egress policy, already wired end to end.
25
+ const SIBLING_ENV_VAR_NAME: string = "ALLOW_PRIVATE_NETWORK_WEBHOOKS";
26
+
27
+ const REPO_ROOT: string = path.resolve(__dirname, "../../../../../..");
28
+ const COMPOSE_PATH: string = path.join(REPO_ROOT, "docker-compose.base.yml");
29
+ const EXAMPLE_ENV_PATH: string = path.join(REPO_ROOT, "config.example.env");
30
+
31
+ type ComposeEnvironment = Record<string, string | number | boolean | null>;
32
+
33
+ interface ComposeService {
34
+ environment?: ComposeEnvironment;
35
+ }
36
+
37
+ interface ComposeConfig {
38
+ "x-common-variables": ComposeEnvironment;
39
+ services: Record<string, ComposeService>;
40
+ }
41
+
42
+ // js-yaml resolves the `<<: *anchor` merge keys, as Compose does.
43
+ const loadCompose: () => ComposeConfig = (): ComposeConfig => {
44
+ return yaml.load(fs.readFileSync(COMPOSE_PATH, "utf8")) as ComposeConfig;
45
+ };
46
+
47
+ const exampleEnvLines: (name: string) => Array<string> = (
48
+ name: string,
49
+ ): Array<string> => {
50
+ const source: string = fs.readFileSync(EXAMPLE_ENV_PATH, "utf8");
51
+ return source.split("\n").filter((line: string) => {
52
+ return line.startsWith(`${name}=`);
53
+ });
54
+ };
55
+
56
+ /*
57
+ * Resolve a Compose `${NAME:-default}` reference the way Compose does: the
58
+ * default applies when the variable is unset OR empty. config.env is exported
59
+ * into the shell before `docker compose up`, so `shellEnv` stands in for it.
60
+ */
61
+ const interpolate: (
62
+ reference: string,
63
+ shellEnv: Record<string, string | undefined>,
64
+ ) => string = (
65
+ reference: string,
66
+ shellEnv: Record<string, string | undefined>,
67
+ ): string => {
68
+ const match: RegExpMatchArray | null = reference.match(
69
+ /^\$\{([A-Z0-9_]+):-(.*)\}$/,
70
+ );
71
+ if (!match) {
72
+ throw new Error(`Not a defaulted Compose reference: ${reference}`);
73
+ }
74
+ const value: string | undefined = shellEnv[match[1]!];
75
+ return value ? value : match[2]!;
76
+ };
77
+
78
+ const appReference: () => string = (): string => {
79
+ const reference: unknown =
80
+ loadCompose().services["app"]?.environment?.[ENV_VAR_NAME];
81
+ expect(typeof reference).toBe("string");
82
+ return reference as string;
83
+ };
84
+
85
+ describe("DATA_SOURCE_BLOCK_PRIVATE_ADDRESSES deployment wiring", () => {
86
+ let savedValue: string | undefined = undefined;
87
+ let savedBilling: string | undefined = undefined;
88
+
89
+ beforeEach(() => {
90
+ savedValue = process.env[ENV_VAR_NAME];
91
+ savedBilling = process.env["BILLING_ENABLED"];
92
+ });
93
+
94
+ afterEach(() => {
95
+ for (const [name, saved] of [
96
+ [ENV_VAR_NAME, savedValue],
97
+ ["BILLING_ENABLED", savedBilling],
98
+ ] as Array<[string, string | undefined]>) {
99
+ if (saved === undefined) {
100
+ delete process.env[name];
101
+ } else {
102
+ process.env[name] = saved;
103
+ }
104
+ }
105
+ });
106
+
107
+ test("Docker Compose passes it in the shared environment, defaulted off", () => {
108
+ expect(loadCompose()["x-common-variables"][ENV_VAR_NAME]).toBe(
109
+ `\${${ENV_VAR_NAME}:-false}`,
110
+ );
111
+ });
112
+
113
+ /*
114
+ * The app container runs the API, the queue workers, workflows and the
115
+ * notification (SMTP) code, which is every server-side caller of the guard.
116
+ */
117
+ test("the app container receives it", () => {
118
+ expect(appReference()).toBe(`\${${ENV_VAR_NAME}:-false}`);
119
+ });
120
+
121
+ test("every Compose service that gets the webhook policy gets this one too", () => {
122
+ const services: Record<string, ComposeService> = loadCompose().services;
123
+ const withWebhookPolicy: Array<string> = Object.keys(services).filter(
124
+ (name: string) => {
125
+ return (
126
+ services[name]?.environment?.[SIBLING_ENV_VAR_NAME] !== undefined
127
+ );
128
+ },
129
+ );
130
+
131
+ expect(withWebhookPolicy).toContain("app");
132
+ for (const name of withWebhookPolicy) {
133
+ expect({
134
+ service: name,
135
+ value: services[name]?.environment?.[ENV_VAR_NAME],
136
+ }).toEqual({ service: name, value: `\${${ENV_VAR_NAME}:-false}` });
137
+ }
138
+ });
139
+
140
+ /*
141
+ * One line, so an operator flips it instead of appending a second one (the
142
+ * later line would win), and MergeEnvTemplate adds it to an existing
143
+ * config.env on upgrade.
144
+ */
145
+ test("config.example.env ships exactly one line, set to false", () => {
146
+ expect(exampleEnvLines(ENV_VAR_NAME)).toEqual([`${ENV_VAR_NAME}=false`]);
147
+ });
148
+
149
+ test("a self-hosted install keeps private ranges reachable with the shipped line", () => {
150
+ const shipped: string = exampleEnvLines(ENV_VAR_NAME)[0]!.split("=")[1]!;
151
+ process.env["BILLING_ENABLED"] = "false";
152
+ process.env[ENV_VAR_NAME] = interpolate(appReference(), {
153
+ [ENV_VAR_NAME]: shipped,
154
+ });
155
+
156
+ expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
157
+ });
158
+
159
+ test("a config.env written before the line existed keeps private ranges reachable", () => {
160
+ process.env["BILLING_ENABLED"] = "false";
161
+ process.env[ENV_VAR_NAME] = interpolate(appReference(), {});
162
+
163
+ expect(process.env[ENV_VAR_NAME]).toBe("false");
164
+ expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(false);
165
+ });
166
+
167
+ // The regression itself: the operator's value has to reach the guard.
168
+ test("setting it to true in config.env makes the guard block private ranges", () => {
169
+ process.env["BILLING_ENABLED"] = "false";
170
+ process.env[ENV_VAR_NAME] = interpolate(appReference(), {
171
+ [ENV_VAR_NAME]: "true",
172
+ });
173
+
174
+ expect(DataSourceEgressGuard.shouldBlockPrivateAddresses()).toBe(true);
175
+ expect(DataSourceEgressGuard.checkAddress("10.0.0.5").blocked).toBe(true);
176
+ });
177
+
178
+ /*
179
+ * The self-hosted docs quote the refusal an operator sees once the switch is
180
+ * on, so they can match it against a log. Produce it with the real guard,
181
+ * the way LLMService calls it, rather than trusting the copy.
182
+ */
183
+ test("the docs quote the refusal the guard actually reports", async () => {
184
+ const docs: string = fs.readFileSync(
185
+ path.join(
186
+ REPO_ROOT,
187
+ "packages/App/FeatureSet/Docs/Content/en/self-hosted/private-network-access.md",
188
+ ),
189
+ "utf8",
190
+ );
191
+ // The refused name first, then the same address typed as an IP.
192
+ const quotes: Array<string> = Array.from(
193
+ docs.matchAll(/_"(LLM provider host [^"]+)"_/g),
194
+ (match: RegExpMatchArray) => {
195
+ return match[1]!;
196
+ },
197
+ );
198
+ expect(quotes).toHaveLength(2);
199
+
200
+ process.env["BILLING_ENABLED"] = "false";
201
+ process.env[ENV_VAR_NAME] = "true";
202
+
203
+ const refusalFor: (url: string) => Promise<string> = async (
204
+ url: string,
205
+ ): Promise<string> => {
206
+ try {
207
+ await DataSourceEgressGuard.assertUrlAllowedAndPin(url, {
208
+ targetLabel: "LLM provider",
209
+ resolveFunction: async (): Promise<Array<ResolvedAddress>> => {
210
+ return [{ address: "10.0.4.12", family: 4 }];
211
+ },
212
+ });
213
+ } catch (err) {
214
+ return (err as Error).message;
215
+ }
216
+ return "";
217
+ };
218
+
219
+ expect(
220
+ await refusalFor("http://ollama.internal:11434/v1/chat/completions"),
221
+ ).toBe(quotes[0]);
222
+ expect(await refusalFor("http://10.0.4.12:11434/v1/chat/completions")).toBe(
223
+ quotes[1],
224
+ );
225
+ });
226
+ });
@@ -6,6 +6,9 @@ import DataSourceEgressGuard, {
6
6
  } from "../../../../Server/Utils/DataSource/EgressGuard";
7
7
  import BadDataException from "../../../../Types/Exception/BadDataException";
8
8
  import { describe, expect, test } from "@jest/globals";
9
+ import { ClientRequest } from "http";
10
+ import https from "https";
11
+ import { startEachTestOnSelfHostedEgressPolicy } from "../EgressPolicyEnvironment";
9
12
 
10
13
  /*
11
14
  * The egress guard is only half a control on its own: validating an address
@@ -61,6 +64,9 @@ const callLookup: CallLookup = (
61
64
  });
62
65
  };
63
66
 
67
+ // Detailed refusals are asserted throughout; SaaS behaviour is tested apart.
68
+ startEachTestOnSelfHostedEgressPolicy();
69
+
64
70
  describe("DataSourceEgressGuard.createPinnedLookup", () => {
65
71
  const addresses: Array<ResolvedAddress> = [
66
72
  { address: "93.184.216.34", family: 4 },
@@ -113,6 +119,131 @@ describe("DataSourceEgressGuard.createPinnedLookup", () => {
113
119
 
114
120
  expect(result.address).toBe("93.184.216.34");
115
121
  });
122
+
123
+ /*
124
+ * dns.lookup never calls back synchronously, and Node's net/tls connect
125
+ * relies on that. See the regression test below for what broke.
126
+ */
127
+ test.each([
128
+ ["every address", { all: true }],
129
+ ["the first address", { all: false }],
130
+ ["no address of the wanted family", { family: 6 as number }],
131
+ ])(
132
+ "answers asynchronously with %s",
133
+ async (
134
+ _label: string,
135
+ options: { all?: boolean | undefined; family?: number | undefined },
136
+ ) => {
137
+ const lookup: EgressLookupFunction =
138
+ DataSourceEgressGuard.createPinnedLookup([
139
+ { address: "93.184.216.34", family: 4 },
140
+ ]);
141
+ let hasAnswered: boolean = false;
142
+
143
+ const answered: Promise<LookupResult> = new Promise(
144
+ (resolve: (value: LookupResult) => void) => {
145
+ lookup(
146
+ "anything.example",
147
+ options,
148
+ (
149
+ error: NodeJS.ErrnoException | null,
150
+ address: string | Array<{ address: string; family: number }>,
151
+ family?: number,
152
+ ) => {
153
+ hasAnswered = true;
154
+ resolve({ error, address, family });
155
+ },
156
+ );
157
+ },
158
+ );
159
+
160
+ expect(hasAnswered).toBe(false);
161
+
162
+ const result: LookupResult = await answered;
163
+
164
+ expect(hasAnswered).toBe(true);
165
+ if (options.family === 6) {
166
+ expect(result.error?.code).toBe("ENOTFOUND");
167
+ } else {
168
+ expect(result.error).toBeNull();
169
+ }
170
+ },
171
+ );
172
+ });
173
+
174
+ /*
175
+ * REGRESSION: an HTTPS request through a pinned agent whose every address
176
+ * fails at connect() time. That is what a probe with no IPv6 gets for an
177
+ * IPv6-only hostname (EADDRNOTAVAIL/ENETUNREACH from connect() itself), and
178
+ * with a lookup that answered synchronously Node destroyed the TLSSocket
179
+ * before tls.connect called setServername on it: https.get threw
180
+ * "Cannot read properties of null (reading 'setServername')", which the
181
+ * monitor reported instead of the real error, and the real error escaped as
182
+ * an unhandled 'error' event on the socket.
183
+ *
184
+ * connect() to the broadcast address fails the same way, locally and with no
185
+ * packet sent (EAFNOSUPPORT on macOS, ENETUNREACH on Linux).
186
+ */
187
+ describe("DataSourceEgressGuard.createPinnedLookup through an https agent", () => {
188
+ test("a connect that fails on every pinned address rejects cleanly instead of throwing", async () => {
189
+ const uncaught: Array<unknown> = [];
190
+ const onUncaught: (error: unknown) => void = (error: unknown): void => {
191
+ uncaught.push(error);
192
+ };
193
+
194
+ process.on("uncaughtException", onUncaught);
195
+
196
+ try {
197
+ const agent: https.Agent = new https.Agent({
198
+ lookup: DataSourceEgressGuard.createPinnedLookup([
199
+ { address: "255.255.255.255", family: 4 },
200
+ ]) as never,
201
+ });
202
+
203
+ const requestError: Error = await new Promise(
204
+ (resolve: (error: Error) => void, reject: (error: Error) => void) => {
205
+ let request: ClientRequest;
206
+
207
+ try {
208
+ request = https.get("https://pinned.example/", {
209
+ agent: agent,
210
+ timeout: 5000,
211
+ });
212
+ } catch (error) {
213
+ reject(error as Error);
214
+ return;
215
+ }
216
+
217
+ request.on("error", (error: Error) => {
218
+ resolve(error);
219
+ });
220
+ request.on("timeout", () => {
221
+ request.destroy(new Error("request timed out"));
222
+ });
223
+ request.on("response", () => {
224
+ reject(new Error("broadcast address answered"));
225
+ });
226
+ },
227
+ );
228
+
229
+ // Give a stray 'error' event on the socket a chance to surface.
230
+ await new Promise<void>((resolve: () => void) => {
231
+ setTimeout(resolve, 50);
232
+ });
233
+
234
+ expect(requestError).not.toBeInstanceOf(TypeError);
235
+ expect(requestError.message).not.toContain("setServername");
236
+ expect(
237
+ typeof (requestError as NodeJS.ErrnoException).code === "string" ||
238
+ Array.isArray((requestError as { errors?: unknown }).errors),
239
+ ).toBe(true);
240
+ expect(uncaught).toEqual([]);
241
+
242
+ agent.destroy();
243
+ } finally {
244
+ process.removeListener("uncaughtException", onUncaught);
245
+ }
246
+ });
116
247
  });
117
248
 
118
249
  describe("DataSourceEgressGuard.createPinnedAgents", () => {