@oneuptime/common 12.0.5 → 12.0.6

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 (528) hide show
  1. package/Models/DatabaseModels/AIRun.ts +33 -0
  2. package/Models/DatabaseModels/DataSource.ts +744 -0
  3. package/Models/DatabaseModels/Index.ts +3 -0
  4. package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
  5. package/Models/DatabaseModels/User.ts +2 -0
  6. package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
  8. package/Models/DatabaseModels/UserSession.ts +2 -0
  9. package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
  10. package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
  11. package/Server/API/AIInsightAPI.ts +47 -4
  12. package/Server/API/AIInvestigationAPI.ts +25 -0
  13. package/Server/API/DashboardAPI.ts +176 -492
  14. package/Server/API/DataSourceAPI.ts +268 -0
  15. package/Server/API/StatusPageAPI.ts +11 -1
  16. package/Server/API/UserAPI.ts +119 -0
  17. package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
  19. package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  22. package/Server/Infrastructure/Queue.ts +26 -0
  23. package/Server/Infrastructure/QueueWorker.ts +17 -0
  24. package/Server/Middleware/TelemetryIngest.ts +8 -3
  25. package/Server/Services/AIInsightService.ts +132 -12
  26. package/Server/Services/AIRunService.ts +34 -0
  27. package/Server/Services/AIService.ts +17 -2
  28. package/Server/Services/AlertService.ts +38 -32
  29. package/Server/Services/AnalyticsDatabaseService.ts +8 -4
  30. package/Server/Services/DataSourceService.ts +200 -0
  31. package/Server/Services/HostService.ts +92 -0
  32. package/Server/Services/IncidentService.ts +45 -0
  33. package/Server/Services/Index.ts +2 -0
  34. package/Server/Services/MonitorService.ts +49 -0
  35. package/Server/Services/MonitorStatusService.ts +166 -0
  36. package/Server/Services/OpenTelemetryIngestService.ts +255 -184
  37. package/Server/Services/RumSessionPinService.ts +7 -21
  38. package/Server/Services/StatusPageSubscriberService.ts +13 -0
  39. package/Server/Services/UserService.ts +280 -9
  40. package/Server/Static/Vendor/README.md +62 -0
  41. package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
  42. package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
  43. package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
  44. package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
  45. package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
  46. package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
  47. package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
  48. package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
  49. package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
  50. package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
  51. package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
  52. package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
  53. package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
  54. package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
  55. package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
  56. package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
  57. package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
  58. package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
  59. package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
  60. package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
  61. package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
  62. package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
  63. package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
  64. package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
  65. package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
  66. package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
  67. package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
  68. package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
  69. package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
  70. package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
  71. package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
  72. package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
  73. package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
  74. package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
  75. package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
  76. package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
  77. package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
  78. package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
  79. package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
  80. package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
  81. package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
  82. package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
  83. package/Server/Utils/AI/CodeFix/FixRunBudget.ts +2 -2
  84. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
  85. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
  86. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
  87. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
  88. package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
  89. package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
  90. package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
  91. package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
  92. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
  93. package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
  94. package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
  95. package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
  96. package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
  97. package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
  98. package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
  99. package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
  100. package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
  101. package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
  102. package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
  103. package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
  104. package/Server/Utils/DataSource/EgressGuard.ts +446 -0
  105. package/Server/Utils/DataSource/HttpFetch.ts +221 -0
  106. package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
  107. package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
  108. package/Server/Utils/DataSource/Types.ts +93 -0
  109. package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
  110. package/Server/Utils/InProcessMemo.ts +141 -0
  111. package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
  112. package/Server/Utils/Monitor/MonitorMetricUtil.ts +58 -0
  113. package/Server/Utils/Monitor/MonitorResource.ts +14 -0
  114. package/Server/Utils/Response.ts +12 -1
  115. package/Server/Utils/SSRFProtection.ts +237 -0
  116. package/Server/Utils/StartServer.ts +8 -0
  117. package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
  118. package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
  119. package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
  120. package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
  121. package/Server/Utils/Telemetry/Telemetry.ts +112 -1
  122. package/Server/Utils/Telemetry.ts +104 -3
  123. package/Server/Utils/VendorAssets.ts +165 -0
  124. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
  125. package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
  126. package/Server/Views/Partials/Head.ejs +21 -7
  127. package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
  128. package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
  129. package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
  130. package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
  131. package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
  132. package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
  133. package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
  134. package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
  135. package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
  136. package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
  137. package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
  138. package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
  139. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
  140. package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
  141. package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
  142. package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
  143. package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
  144. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
  145. package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
  146. package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
  147. package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
  148. package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
  149. package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
  150. package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
  151. package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
  152. package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
  153. package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
  154. package/Tests/Server/Services/AIInsightService.test.ts +266 -11
  155. package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
  156. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +4 -0
  157. package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
  158. package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
  159. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
  160. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
  161. package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
  162. package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
  163. package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
  164. package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
  165. package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
  166. package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
  167. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
  168. package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
  169. package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
  170. package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
  171. package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
  172. package/Tests/Server/Utils/AI/FixRunBudget.test.ts +2 -2
  173. package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
  174. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
  175. package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
  176. package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
  177. package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
  178. package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
  179. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
  180. package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
  181. package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
  182. package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
  183. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
  184. package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
  185. package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
  186. package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
  187. package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
  188. package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
  189. package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
  190. package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
  191. package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
  192. package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
  193. package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
  194. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
  195. package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
  196. package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
  197. package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
  198. package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
  199. package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
  200. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
  201. package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
  202. package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
  203. package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
  204. package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
  205. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
  206. package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
  207. package/Tests/Server/Utils/Telemetry.test.ts +87 -0
  208. package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
  209. package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
  210. package/Tests/Server/Views/OfflineRender.test.ts +297 -0
  211. package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
  212. package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
  213. package/Tests/Types/Password.test.ts +363 -0
  214. package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
  215. package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
  216. package/Tests/UI/Components/Modal.test.tsx +281 -1
  217. package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
  218. package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
  219. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
  220. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  221. package/Tests/Utils/Analytics.test.ts +23 -0
  222. package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
  223. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
  224. package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
  225. package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
  226. package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
  227. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
  228. package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
  229. package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
  230. package/Types/AI/AIInsightEvidence.ts +6 -0
  231. package/Types/Analytics/RevenueEvent.ts +31 -0
  232. package/Types/Dashboard/DashboardComponentType.ts +31 -0
  233. package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
  234. package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
  235. package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
  236. package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
  237. package/Types/DataSource/DataSourceLimits.ts +62 -0
  238. package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
  239. package/Types/DataSource/DataSourceTableResult.ts +38 -0
  240. package/Types/DataSource/DataSourceType.ts +213 -0
  241. package/Types/Date.ts +43 -3
  242. package/Types/Password.ts +64 -0
  243. package/Types/Permission.ts +46 -0
  244. package/Types/UserAuthenticationStatus.ts +34 -0
  245. package/UI/Components/Button/Button.tsx +1 -1
  246. package/UI/Components/ErrorBoundary.tsx +459 -108
  247. package/UI/Components/Icon/Icon.tsx +10 -9
  248. package/UI/Components/Modal/ConfirmModal.tsx +6 -1
  249. package/UI/Components/Modal/Modal.tsx +267 -35
  250. package/UI/Components/Modal/ModalFooter.tsx +6 -1
  251. package/UI/Components/MoreMenu/MoreMenu.tsx +19 -4
  252. package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
  253. package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
  254. package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
  255. package/UI/Components/StatusPage/ResourceList.tsx +570 -0
  256. package/UI/Styles/Theme.css +18 -0
  257. package/UI/Utils/API/RequestOptions.ts +7 -0
  258. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
  259. package/UI/Utils/ModelAPI/ModelAPI.ts +1 -0
  260. package/UI/Utils/TestDataSource.ts +61 -0
  261. package/UI/esbuild-config.js +45 -0
  262. package/Utils/Analytics.ts +15 -0
  263. package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
  264. package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
  265. package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
  266. package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
  267. package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
  268. package/Utils/Dashboard/Components/Index.ts +28 -0
  269. package/Utils/DataSource/DataSourceQueryText.ts +94 -0
  270. package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
  271. package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
  272. package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
  273. package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
  274. package/Utils/StatusPage/ResourceExplorer.ts +853 -0
  275. package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
  276. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  277. package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
  278. package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
  279. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  280. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  281. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
  282. package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
  283. package/build/dist/Models/DatabaseModels/User.js +2 -0
  284. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  285. package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
  286. package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
  287. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
  288. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  289. package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
  290. package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
  291. package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
  292. package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
  293. package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
  294. package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
  295. package/build/dist/Server/API/AIInsightAPI.js +34 -4
  296. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  297. package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
  298. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  299. package/build/dist/Server/API/DashboardAPI.js +119 -433
  300. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  301. package/build/dist/Server/API/DataSourceAPI.js +173 -0
  302. package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
  303. package/build/dist/Server/API/StatusPageAPI.js +11 -1
  304. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  305. package/build/dist/Server/API/UserAPI.js +94 -2
  306. package/build/dist/Server/API/UserAPI.js.map +1 -1
  307. package/build/dist/Server/Infrastructure/ClickhouseConfig.js +49 -0
  308. package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
  309. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
  310. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
  311. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
  312. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
  313. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
  314. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
  315. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  316. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  317. package/build/dist/Server/Infrastructure/Queue.js +27 -0
  318. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  319. package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
  320. package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
  321. package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
  322. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  323. package/build/dist/Server/Services/AIInsightService.js +110 -10
  324. package/build/dist/Server/Services/AIInsightService.js.map +1 -1
  325. package/build/dist/Server/Services/AIRunService.js +34 -0
  326. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  327. package/build/dist/Server/Services/AIService.js +16 -2
  328. package/build/dist/Server/Services/AIService.js.map +1 -1
  329. package/build/dist/Server/Services/AlertService.js +28 -35
  330. package/build/dist/Server/Services/AlertService.js.map +1 -1
  331. package/build/dist/Server/Services/AnalyticsDatabaseService.js +8 -4
  332. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  333. package/build/dist/Server/Services/DataSourceService.js +159 -0
  334. package/build/dist/Server/Services/DataSourceService.js.map +1 -0
  335. package/build/dist/Server/Services/HostService.js +74 -0
  336. package/build/dist/Server/Services/HostService.js.map +1 -1
  337. package/build/dist/Server/Services/IncidentService.js +35 -24
  338. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  339. package/build/dist/Server/Services/Index.js +2 -0
  340. package/build/dist/Server/Services/Index.js.map +1 -1
  341. package/build/dist/Server/Services/MonitorService.js +45 -0
  342. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  343. package/build/dist/Server/Services/MonitorStatusService.js +129 -0
  344. package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
  345. package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
  346. package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
  347. package/build/dist/Server/Services/RumSessionPinService.js +7 -15
  348. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  349. package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
  350. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  351. package/build/dist/Server/Services/UserService.js +241 -10
  352. package/build/dist/Server/Services/UserService.js.map +1 -1
  353. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
  354. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  355. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
  356. package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
  357. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
  358. package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
  359. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
  360. package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
  361. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +2 -2
  362. package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
  363. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
  364. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  365. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
  366. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  367. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
  368. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  369. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
  370. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
  371. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
  372. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
  373. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
  374. package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
  375. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
  376. package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
  377. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
  378. package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
  379. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
  380. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
  381. package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
  382. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
  383. package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
  384. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
  385. package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
  386. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
  387. package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
  388. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
  389. package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
  390. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
  391. package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
  392. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
  393. package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
  394. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
  395. package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
  396. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
  397. package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
  398. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
  399. package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
  400. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
  401. package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
  402. package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
  403. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
  404. package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
  405. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
  406. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
  407. package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
  408. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
  409. package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
  410. package/build/dist/Server/Utils/DataSource/Types.js +2 -0
  411. package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
  412. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
  413. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
  414. package/build/dist/Server/Utils/InProcessMemo.js +113 -0
  415. package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
  416. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
  417. package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
  418. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +34 -0
  419. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  420. package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
  421. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  422. package/build/dist/Server/Utils/Response.js +9 -1
  423. package/build/dist/Server/Utils/Response.js.map +1 -1
  424. package/build/dist/Server/Utils/SSRFProtection.js +192 -0
  425. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
  426. package/build/dist/Server/Utils/StartServer.js +7 -0
  427. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  428. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
  429. package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
  430. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
  431. package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
  432. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
  433. package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
  434. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
  435. package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
  436. package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
  437. package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
  438. package/build/dist/Server/Utils/Telemetry.js +93 -6
  439. package/build/dist/Server/Utils/Telemetry.js.map +1 -1
  440. package/build/dist/Server/Utils/VendorAssets.js +121 -0
  441. package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
  442. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
  443. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  444. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
  445. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  446. package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
  447. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
  448. package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
  449. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  450. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
  451. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
  452. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
  453. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
  454. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
  455. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
  456. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
  457. package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
  458. package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
  459. package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
  460. package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
  461. package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
  462. package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
  463. package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
  464. package/build/dist/Types/DataSource/DataSourceType.js +183 -0
  465. package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
  466. package/build/dist/Types/Date.js +38 -3
  467. package/build/dist/Types/Date.js.map +1 -1
  468. package/build/dist/Types/Password.js +56 -0
  469. package/build/dist/Types/Password.js.map +1 -0
  470. package/build/dist/Types/Permission.js +42 -0
  471. package/build/dist/Types/Permission.js.map +1 -1
  472. package/build/dist/Types/UserAuthenticationStatus.js +2 -0
  473. package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
  474. package/build/dist/UI/Components/ErrorBoundary.js +388 -72
  475. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  476. package/build/dist/UI/Components/Icon/Icon.js +10 -4
  477. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  478. package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
  479. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  480. package/build/dist/UI/Components/Modal/Modal.js +178 -19
  481. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  482. package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
  483. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  484. package/build/dist/UI/Components/MoreMenu/MoreMenu.js +9 -6
  485. package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
  486. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
  487. package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
  488. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
  489. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
  490. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
  491. package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
  492. package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
  493. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
  494. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
  495. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  496. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +3 -8
  497. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  498. package/build/dist/UI/Utils/TestDataSource.js +39 -0
  499. package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
  500. package/build/dist/Utils/Analytics.js +4 -0
  501. package/build/dist/Utils/Analytics.js.map +1 -1
  502. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
  503. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
  504. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
  505. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
  506. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
  507. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
  508. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
  509. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
  510. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
  511. package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
  512. package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
  513. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  514. package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
  515. package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
  516. package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
  517. package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
  518. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
  519. package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
  520. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
  521. package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
  522. package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
  523. package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
  524. package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
  525. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
  526. package/jest.config.json +1 -0
  527. package/package.json +5 -1
  528. package/tsconfig.json +12 -10
@@ -3,6 +3,7 @@ import QueryDeepPartialEntity from "../../../Types/Database/PartialEntity";
3
3
  import ObjectID from "../../../Types/ObjectID";
4
4
  import DatabaseService from "../../Services/DatabaseService";
5
5
  import GlobalCache from "../../Infrastructure/GlobalCache";
6
+ import InProcessMemo from "../InProcessMemo";
6
7
  import SingleFlight from "../SingleFlight";
7
8
  import logger from "../Logger";
8
9
 
@@ -116,10 +117,55 @@ function writeWindowNamespace(cacheNamespace: string): string {
116
117
  }
117
118
 
118
119
  export default class ResourceHeartbeat {
120
+ /*
121
+ * Time-based L1 over the whole gate cascade. SingleFlight collapses
122
+ * CONCURRENT duplicates, but sequential batches — the next job for the
123
+ * same host arriving a second later — each still paid 1-3 Redis round
124
+ * trips (liveness SET NX, fingerprint compare-and-claim, rate-limit
125
+ * SET NX) to be told "nothing to do". Once THIS process has settled a
126
+ * heartbeat for a (resource, fingerprint), that answer holds for the
127
+ * rest of the window, so re-asking Redis per batch buys nothing.
128
+ *
129
+ * The memo key includes the FINGERPRINT, so a genuinely changed payload
130
+ * bypasses the memo and reaches the compare-and-claim gate as promptly
131
+ * as it did before — the L1 only ever suppresses re-asking a question
132
+ * whose inputs are identical.
133
+ *
134
+ * Entries live for min(60s, the caller's liveness window, pre-jitter).
135
+ * Within that span the Redis liveness key this process (or another pod)
136
+ * claimed cannot have expired yet (its jittered TTL is >= the window),
137
+ * so a skipped probe would have been refused anyway. The one real trade
138
+ * is cross-pod lastSeen PRECISION: when a DIFFERENT pod's claim expires
139
+ * mid-window, this pod would previously have won the reopened gate on
140
+ * its next batch, and now reacts up to one memo TTL later (any other pod
141
+ * still reacts immediately). That delay is bounded by the existing
142
+ * window — the same staleness the jittered TTL already permits — and
143
+ * sits far inside the 15-minute markDisconnected* thresholds.
144
+ *
145
+ * Outcomes that must retry are NEVER memoized: a failed or lock-skipped
146
+ * metadata write (those release their Redis gates precisely so the next
147
+ * batch retries) and any outcome where a gate probe threw (a Redis
148
+ * outage must keep failing open per batch, not once per memo TTL).
149
+ */
150
+ private static readonly recentHeartbeatMemo: InProcessMemo<boolean> =
151
+ new InProcessMemo<boolean>({
152
+ ttlInMs: 60 * 1000,
153
+ maxEntries: 10_000,
154
+ });
155
+
119
156
  public static async write<TBaseModel extends BaseModel>(
120
157
  input: ResourceHeartbeatWrite<TBaseModel>,
121
158
  ): Promise<void> {
122
159
  const cacheKey: string = input.id.toString();
160
+ const memoKey: string = `${input.cacheNamespace}:${cacheKey}:${input.fingerprint}`;
161
+
162
+ /*
163
+ * L1: this process already settled this exact heartbeat within the
164
+ * window. Zero network cost, no gates touched.
165
+ */
166
+ if (this.recentHeartbeatMemo.get(memoKey)) {
167
+ return;
168
+ }
123
169
 
124
170
  /*
125
171
  * Collapse duplicate concurrent callers inside THIS process first. One
@@ -128,18 +174,41 @@ export default class ResourceHeartbeat {
128
174
  * and without this every one of them independently asks Redis the same
129
175
  * question and throws the answer away.
130
176
  */
131
- await SingleFlight.run(
132
- `${input.cacheNamespace}:${cacheKey}:${input.fingerprint}`,
133
- async () => {
134
- await this.writeOnce(input, cacheKey);
135
- },
136
- );
177
+ await SingleFlight.run(memoKey, async () => {
178
+ await this.writeOnce(input, cacheKey, memoKey);
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Drop the recent-heartbeat memo. For tests only — suites that reuse one
184
+ * resource id across cases simulate the memo TTL expiring with this, the
185
+ * same way they already clear their fake Redis maps and SingleFlight.
186
+ */
187
+ public static clearRecentHeartbeatMemo(): void {
188
+ this.recentHeartbeatMemo.clear();
137
189
  }
138
190
 
139
191
  private static async writeOnce<TBaseModel extends BaseModel>(
140
192
  input: ResourceHeartbeatWrite<TBaseModel>,
141
193
  cacheKey: string,
194
+ memoKey: string,
142
195
  ): Promise<void> {
196
+ /*
197
+ * Whether every Redis gate we consulted actually answered. A probe that
198
+ * THREW fell back to a hardcoded outcome (open for liveness, closed for
199
+ * enrichment) — that outcome is a per-batch policy, not a fact about the
200
+ * window, so it must never be memoized into the L1.
201
+ */
202
+ let gatesAnswered: boolean = true;
203
+
204
+ /*
205
+ * The memo must expire no later than the PRE-JITTER window, because that
206
+ * is the earliest instant the Redis liveness key claimed alongside it
207
+ * could expire — and never later than the 60-second norm.
208
+ */
209
+ const memoTtlInMs: number =
210
+ Math.min(60, Math.max(input.throttleInSeconds, 0)) * 1000;
211
+
143
212
  /*
144
213
  * Liveness gate. Presence-only and jittered: a fleet-wide restart would
145
214
  * otherwise re-synchronise every resource's window and rebuild the herd on
@@ -163,6 +232,7 @@ export default class ResourceHeartbeat {
163
232
  * the write below cannot queue.
164
233
  */
165
234
  writeLiveness = true;
235
+ gatesAnswered = false;
166
236
  }
167
237
 
168
238
  const hasMetadata: boolean =
@@ -194,6 +264,7 @@ export default class ResourceHeartbeat {
194
264
  * write storm this exists to prevent.
195
265
  */
196
266
  metadataChanged = false;
267
+ gatesAnswered = false;
197
268
  }
198
269
  }
199
270
 
@@ -218,11 +289,19 @@ export default class ResourceHeartbeat {
218
289
  );
219
290
  } catch {
220
291
  writeMetadata = false;
292
+ gatesAnswered = false;
221
293
  }
222
294
  }
223
295
 
224
296
  if (!writeLiveness && !writeMetadata) {
225
- // Nothing to say. Issue no statement at all.
297
+ /*
298
+ * Nothing to say. Issue no statement at all — and remember that for
299
+ * the memo TTL, since every gate answered and the same inputs can
300
+ * only produce the same silence for the rest of the window.
301
+ */
302
+ if (gatesAnswered) {
303
+ this.recentHeartbeatMemo.set(memoKey, true, memoTtlInMs);
304
+ }
226
305
  return;
227
306
  }
228
307
 
@@ -283,6 +362,19 @@ export default class ResourceHeartbeat {
283
362
  * statement that returns immediately when contended.
284
363
  */
285
364
  await this.releaseGates(input.cacheNamespace, cacheKey, true);
365
+ return;
366
+ }
367
+
368
+ /*
369
+ * The write LANDED: nothing is left for this exact input to do this
370
+ * window, so remember that. Memoized only when every gate genuinely
371
+ * answered (an outage outcome is per-batch policy, never a fact) and
372
+ * NOT for the liveness-only lock-skip above `wrote === false` — that
373
+ * case is rare, already free of gate churn next batch, and memoizing
374
+ * a write we did not perform is a subtlety not worth carrying.
375
+ */
376
+ if (wrote && gatesAnswered) {
377
+ this.recentHeartbeatMemo.set(memoKey, true, memoTtlInMs);
286
378
  }
287
379
  }
288
380
 
@@ -451,7 +451,6 @@ export default class TelemetryUtil {
451
451
  };
452
452
  }
453
453
 
454
- @CaptureSpan()
455
454
  public static getAttributes(data: {
456
455
  prefixKeysWithString: string;
457
456
  items: JSONArray;
@@ -655,4 +654,116 @@ export default class TelemetryUtil {
655
654
 
656
655
  return Object.keys(attributes).sort();
657
656
  }
657
+
658
+ /*
659
+ * OTLP scope attributes, prefixed for row storage. The three OTel ingest
660
+ * services stamp every key of the scope envelope onto the row's attribute
661
+ * map under a `scope.` prefix. The scope object is invariant for every
662
+ * record/span/datapoint inside its scopeLogs/scopeSpans/scopeMetrics
663
+ * envelope, so callers build this map ONCE per scope and merge it per row
664
+ * — instead of re-deriving the prefixed key strings for every row, which
665
+ * was a visible per-record cost on large batches.
666
+ *
667
+ * A missing/empty scope returns an empty map, which merges to a no-op —
668
+ * exactly what the previous inline `if (scope && keys.length > 0)` guard
669
+ * produced.
670
+ */
671
+ public static getPrefixedScopeAttributes(
672
+ scope: JSONObject | undefined | null,
673
+ ): Dictionary<AttributeType | Array<AttributeType>> {
674
+ const prefixedScopeAttributes: Dictionary<
675
+ AttributeType | Array<AttributeType>
676
+ > = {};
677
+
678
+ if (!scope || typeof scope !== "object") {
679
+ return prefixedScopeAttributes;
680
+ }
681
+
682
+ for (const key of Object.keys(scope)) {
683
+ prefixedScopeAttributes[`scope.${key}`] = scope[key] as AttributeType;
684
+ }
685
+
686
+ return prefixedScopeAttributes;
687
+ }
688
+
689
+ /*
690
+ * Sorted attribute-key union for rows whose attribute map is "large
691
+ * invariant base + few per-row keys" (metric datapoints). Produces exactly
692
+ * `Object.keys(mergedAttributes).sort()` — same elements, same order —
693
+ * without re-sorting the full key set per row:
694
+ *
695
+ * - `sortedBaseAttributeKeys` is the base map's key list, sorted once by
696
+ * the caller (per metric, not per datapoint);
697
+ * - `additionalAttributeKeys` is the per-row key list (typically 0-5
698
+ * keys), sorted here at its own tiny cost;
699
+ * - a linear two-pointer merge then yields the sorted, de-duplicated
700
+ * union.
701
+ *
702
+ * Order equivalence holds because Array.prototype.sort's default
703
+ * comparator orders strings by UTF-16 code units, and the `<` / `>`
704
+ * relational operators used in the merge apply the same code-unit
705
+ * comparison — so merging two lists sorted by that comparator gives the
706
+ * same sequence a full sort of the union would.
707
+ *
708
+ * Precondition: each input list is duplicate-free (both come from
709
+ * Object.keys, whose output cannot repeat a key). Duplicates ACROSS the
710
+ * two lists (a per-row key overriding a base key) collapse to one entry,
711
+ * matching Object.keys on the merged map.
712
+ */
713
+ public static mergeAttributeKeysWithSortedBase(data: {
714
+ sortedBaseAttributeKeys: Array<string>;
715
+ additionalAttributeKeys: Array<string>;
716
+ }): Array<string> {
717
+ const { sortedBaseAttributeKeys, additionalAttributeKeys } = data;
718
+
719
+ /*
720
+ * Always return a fresh array: rows must never share an attributeKeys
721
+ * reference, because downstream pipeline rules replace a row's key list
722
+ * independently of its siblings.
723
+ */
724
+ if (additionalAttributeKeys.length === 0) {
725
+ return sortedBaseAttributeKeys.slice();
726
+ }
727
+
728
+ const sortedAdditionalKeys: Array<string> = additionalAttributeKeys
729
+ .slice()
730
+ .sort();
731
+
732
+ const mergedKeys: Array<string> = [];
733
+ let baseIndex: number = 0;
734
+ let additionalIndex: number = 0;
735
+
736
+ while (
737
+ baseIndex < sortedBaseAttributeKeys.length &&
738
+ additionalIndex < sortedAdditionalKeys.length
739
+ ) {
740
+ const baseKey: string = sortedBaseAttributeKeys[baseIndex]!;
741
+ const additionalKey: string = sortedAdditionalKeys[additionalIndex]!;
742
+
743
+ if (baseKey < additionalKey) {
744
+ mergedKeys.push(baseKey);
745
+ baseIndex++;
746
+ } else if (baseKey > additionalKey) {
747
+ mergedKeys.push(additionalKey);
748
+ additionalIndex++;
749
+ } else {
750
+ // Same key in base and per-row attributes — the map holds it once.
751
+ mergedKeys.push(baseKey);
752
+ baseIndex++;
753
+ additionalIndex++;
754
+ }
755
+ }
756
+
757
+ while (baseIndex < sortedBaseAttributeKeys.length) {
758
+ mergedKeys.push(sortedBaseAttributeKeys[baseIndex]!);
759
+ baseIndex++;
760
+ }
761
+
762
+ while (additionalIndex < sortedAdditionalKeys.length) {
763
+ mergedKeys.push(sortedAdditionalKeys[additionalIndex]!);
764
+ additionalIndex++;
765
+ }
766
+
767
+ return mergedKeys;
768
+ }
658
769
  }
@@ -87,6 +87,13 @@ export enum SpanStatusCode {
87
87
  ERROR = 2,
88
88
  }
89
89
 
90
+ /*
91
+ * Tells a descriptive error `code` ("ECONNREFUSED") from one that is really a
92
+ * status number in disguise (an HTTP status, a Postgres SQLSTATE). See
93
+ * Telemetry.getExceptionTypeName.
94
+ */
95
+ const NUMERIC_ONLY_REGEX: RegExp = /^\d+$/;
96
+
90
97
  export default class Telemetry {
91
98
  public static sdk: opentelemetry.NodeSDK | null = null;
92
99
 
@@ -102,6 +109,20 @@ export default class Telemetry {
102
109
 
103
110
  public static serviceName: string | null = null;
104
111
 
112
+ /*
113
+ * True only when init() installed a real OTLP span exporter. @CaptureSpan
114
+ * consults this to skip span creation entirely on deployments that never
115
+ * export traces — without an exporter the spans go nowhere, but the wrapper
116
+ * would still pay attribute flattening, span allocation, and an
117
+ * AsyncLocalStorage context switch on every decorated call, which includes
118
+ * hot ingest paths that run millions of times per minute.
119
+ */
120
+ private static spanExportEnabled: boolean = false;
121
+
122
+ public static isSpanExportEnabled(): boolean {
123
+ return this.spanExportEnabled;
124
+ }
125
+
105
126
  public static getHeaders(): Dictionary<string> {
106
127
  if (!process.env["OPENTELEMETRY_EXPORTER_OTLP_HEADERS"]) {
107
128
  return {};
@@ -192,6 +213,7 @@ export default class Telemetry {
192
213
  headers: headers,
193
214
  compression: CompressionAlgorithm.GZIP,
194
215
  }) as unknown as SpanExporter;
216
+ this.spanExportEnabled = true;
195
217
  }
196
218
 
197
219
  if (this.getOltpMetricsEndpoint() && hasHeaders) {
@@ -546,7 +568,36 @@ export default class Telemetry {
546
568
  * empty "Error" status.
547
569
  */
548
570
  span.setAttributes(exceptionAttributes);
549
- span.recordException(exception as SpanException);
571
+
572
+ /*
573
+ * A NORMALIZED {name, message, stack} is handed to recordException
574
+ * rather than the raw thrown value, because the OTel SDK reads
575
+ * `exception.code` BEFORE `exception.name` when deciding
576
+ * `exception.type` (sdk-trace-base Span.recordException). OneUptime's
577
+ * Exception base class exposes an HTTP STATUS as `code`
578
+ * (NotAuthenticatedException = 401, BadDataException = 400,
579
+ * ServerException = 500 — see Types/Exception/ExceptionCode), so every
580
+ * exception this platform raises was being typed "401"/"400"/"500".
581
+ *
582
+ * That type is the exception event's identity downstream: it is hashed
583
+ * into the TelemetryException group fingerprint and it is what the
584
+ * Issues list and the AI insight titles show. A whole service's worth of
585
+ * unrelated failures collapsed into "401", telling nobody anything.
586
+ *
587
+ * The payload is built from the attributes getExceptionAttributes has
588
+ * already extracted, which means it inherits their truncation (4k
589
+ * message, 8k stack) and their crash-safety — a hostile thrown value is
590
+ * read exactly once, defensively, rather than twice.
591
+ */
592
+ span.recordException({
593
+ name: (exceptionAttributes["exception.type"] as string) || "Error",
594
+ message: (exceptionAttributes["exception.message"] as string) || "",
595
+ ...(exceptionAttributes["exception.stacktrace"]
596
+ ? {
597
+ stack: exceptionAttributes["exception.stacktrace"] as string,
598
+ }
599
+ : {}),
600
+ } as SpanException);
550
601
  span.setStatus({
551
602
  code: SpanStatusCode.ERROR,
552
603
  message:
@@ -588,8 +639,7 @@ export default class Telemetry {
588
639
  }
589
640
 
590
641
  if (exception instanceof Error) {
591
- attributes["exception.type"] =
592
- exception.name || exception.constructor?.name || "Error";
642
+ attributes["exception.type"] = this.getExceptionTypeName(exception);
593
643
  attributes["exception.message"] = this.truncate(
594
644
  exception.message || "",
595
645
  4000,
@@ -661,6 +711,57 @@ export default class Telemetry {
661
711
  return attributes;
662
712
  }
663
713
 
714
+ /*
715
+ * The most specific type name available for a thrown Error, in the order a
716
+ * human would want to read it in the Issues list.
717
+ *
718
+ * `name` is checked first but "Error" is rejected: an Error subclass that
719
+ * never assigns `this.name` inherits the generic "Error" from
720
+ * Error.prototype, and OneUptime's own Exception base does exactly that —
721
+ * so `name` alone reports "Error" for NotAuthenticatedException,
722
+ * BadDataException and every other one of them.
723
+ *
724
+ * A `code` is only used when it is a NON-NUMERIC string. That keeps the
725
+ * genuinely descriptive Node system codes (ECONNREFUSED, ENOTFOUND) while
726
+ * rejecting numeric ones — HTTP statuses on OneUptime's Exception and
727
+ * Postgres SQLSTATEs ("23505"), for which the constructor name
728
+ * (QueryFailedError) says far more. The numeric code is not lost — it keeps
729
+ * its own `exception.code` attribute.
730
+ *
731
+ * Never throws: it runs on the universal error path and the thrown value
732
+ * can be a Proxy or carry throwing getters.
733
+ */
734
+ private static getExceptionTypeName(exception: Error): string {
735
+ try {
736
+ const name: unknown = exception.name;
737
+ if (typeof name === "string" && name.trim() !== "" && name !== "Error") {
738
+ return name;
739
+ }
740
+
741
+ const code: unknown = (exception as { code?: unknown }).code;
742
+ if (
743
+ typeof code === "string" &&
744
+ code.trim() !== "" &&
745
+ !NUMERIC_ONLY_REGEX.test(code.trim())
746
+ ) {
747
+ return code.trim();
748
+ }
749
+
750
+ const constructorName: unknown = exception.constructor?.name;
751
+ if (
752
+ typeof constructorName === "string" &&
753
+ constructorName.trim() !== "" &&
754
+ constructorName !== "Object"
755
+ ) {
756
+ return constructorName;
757
+ }
758
+ } catch {
759
+ // Fall through to the generic name below.
760
+ }
761
+
762
+ return "Error";
763
+ }
764
+
664
765
  private static truncate(value: string, maxLength: number): string {
665
766
  return value.length > maxLength ? value.substring(0, maxLength) : value;
666
767
  }
@@ -0,0 +1,165 @@
1
+ import {
2
+ ExpressApplication,
3
+ ExpressRequest,
4
+ ExpressResponse,
5
+ ExpressStatic,
6
+ NextFunction,
7
+ RequestHandler,
8
+ } from "./Express";
9
+ import logger from "./Logger";
10
+ import fs from "fs";
11
+ import path from "path";
12
+
13
+ /**
14
+ * Serves the browser libraries our server-rendered pages need, from this
15
+ * install rather than from a public CDN.
16
+ *
17
+ * An air-gapped deployment has no route to cdn.tailwindcss.com or
18
+ * cdnjs.cloudflare.com, and a `<script src>` pointed at one does not fail
19
+ * fast - the browser waits out the connect timeout with the parser blocked,
20
+ * so the page renders unstyled or not at all. Same class of bug as the Monaco
21
+ * loader (see CodeEditor/MonacoLoader.ts): the fix is to stop reaching off-box
22
+ * for anything the page cannot render without.
23
+ *
24
+ * Mounted by every service, because there is no single service that serves
25
+ * every one of these pages: Docs, the API reference and the SSO/on-call
26
+ * message views all come out of the App container, the marketing site out of
27
+ * Home, and nginx sends `/` to whichever of the two the install has (App when
28
+ * billing is off, which is every self-hosted install). A path only one of them
29
+ * answers would 404 for the other.
30
+ */
31
+
32
+ /**
33
+ * Absolute, host-relative. Views hard-code this rather than take it as a
34
+ * render variable - the mount point is fixed, and threading a variable through
35
+ * every Response.render call site would be a lot of ceremony for a constant.
36
+ */
37
+ export const VendorAssetsRoute: string = "/oneuptime-assets";
38
+
39
+ /** Where the committed assets live on disk. */
40
+ export const VendorAssetsPath: string = path.resolve(
41
+ __dirname,
42
+ "..",
43
+ "Static",
44
+ "Vendor",
45
+ );
46
+
47
+ /**
48
+ * Mermaid is a dependency of Common already (Common/UI renders diagrams in
49
+ * markdown with it), so it is on disk in every image and there is no reason to
50
+ * commit a second copy. Resolved rather than hard-coded because npm may hoist
51
+ * it above Common's own node_modules.
52
+ */
53
+ export function getMermaidDistPath(): string | null {
54
+ try {
55
+ return path.join(
56
+ path.dirname(require.resolve("mermaid/package.json")),
57
+ "dist",
58
+ );
59
+ } catch (error) {
60
+ logger.error(
61
+ "mermaid could not be resolved. Diagrams in docs and blog posts will not render.",
62
+ );
63
+ logger.error(error);
64
+ return null;
65
+ }
66
+ }
67
+
68
+ /*
69
+ * Mermaid's dist directory also carries type definitions, sourcemaps and its
70
+ * own docs. Only the code the browser actually imports gets served - the
71
+ * entrypoint plus the diagram chunks it lazily pulls in.
72
+ */
73
+ const MERMAID_SERVABLE_EXTENSIONS: Array<string> = [".js", ".mjs"];
74
+
75
+ /*
76
+ * A year, for paths that cannot change meaning: tailwind and highlight name
77
+ * their version in the filename, and mermaid's chunks are content-hashed.
78
+ */
79
+ const IMMUTABLE_CACHE_MAX_AGE_MILLISECONDS: number = 365 * 24 * 60 * 60 * 1000;
80
+
81
+ /*
82
+ * An hour, for mermaid's two entrypoints. Those names are stable while
83
+ * everything they import is content-hashed, so a year-old cached copy of
84
+ * mermaid.esm.min.mjs would go on asking for chunk filenames that a mermaid
85
+ * upgrade has already deleted - and the diagrams would stay broken, for that
86
+ * one visitor, until the cache entry aged out.
87
+ */
88
+ const REVALIDATE_CACHE_MAX_AGE_MILLISECONDS: number = 60 * 60 * 1000;
89
+
90
+ /* Where mermaid puts the hashed chunks, relative to its own mount. */
91
+ const MERMAID_CHUNK_PREFIX: string = "/chunks/";
92
+
93
+ export type MountVendorAssetsFunction = (app: ExpressApplication) => void;
94
+
95
+ const mountVendorAssets: MountVendorAssetsFunction = (
96
+ app: ExpressApplication,
97
+ ): void => {
98
+ if (fs.existsSync(VendorAssetsPath)) {
99
+ app.use(
100
+ VendorAssetsRoute,
101
+ ExpressStatic(VendorAssetsPath, {
102
+ maxAge: IMMUTABLE_CACHE_MAX_AGE_MILLISECONDS,
103
+ index: false,
104
+ redirect: false,
105
+ }) as RequestHandler,
106
+ );
107
+ } else {
108
+ /*
109
+ * Nothing to serve is worth shouting about: every page that references
110
+ * these paths is about to render without its stylesheet.
111
+ */
112
+ logger.error(
113
+ `Vendored browser assets are missing at ${VendorAssetsPath}. Server-rendered pages will render unstyled.`,
114
+ );
115
+ }
116
+
117
+ const mermaidDistPath: string | null = getMermaidDistPath();
118
+
119
+ if (mermaidDistPath) {
120
+ const serveImmutable: RequestHandler = ExpressStatic(mermaidDistPath, {
121
+ maxAge: IMMUTABLE_CACHE_MAX_AGE_MILLISECONDS,
122
+ index: false,
123
+ redirect: false,
124
+ }) as RequestHandler;
125
+
126
+ const serveRevalidating: RequestHandler = ExpressStatic(mermaidDistPath, {
127
+ maxAge: REVALIDATE_CACHE_MAX_AGE_MILLISECONDS,
128
+ index: false,
129
+ redirect: false,
130
+ }) as RequestHandler;
131
+
132
+ app.use(
133
+ `${VendorAssetsRoute}/mermaid`,
134
+ (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
135
+ const extension: string = path.extname(req.path).toLowerCase();
136
+
137
+ if (!MERMAID_SERVABLE_EXTENSIONS.includes(extension)) {
138
+ return next();
139
+ }
140
+
141
+ /* Express has already stripped the mount path, so this is "/chunks/...". */
142
+ if (req.path.startsWith(MERMAID_CHUNK_PREFIX)) {
143
+ return serveImmutable(req, res, next);
144
+ }
145
+
146
+ return serveRevalidating(req, res, next);
147
+ },
148
+ );
149
+ }
150
+
151
+ /*
152
+ * Terminates the prefix. Registered unconditionally, and last, because the
153
+ * alternative is worse than a 404: express static falls through on a miss,
154
+ * and the next thing to see the request is the frontend catch-all, which
155
+ * answers with the dashboard's index page - HTTP 200, Content-Type
156
+ * text/html. A <script> handed HTML fires onload, not onerror, so the docs'
157
+ * lazy grammar loader would count it as loaded and highlight nothing, with
158
+ * no error anywhere. Better to say plainly that the asset is not here.
159
+ */
160
+ app.use(VendorAssetsRoute, (_req: ExpressRequest, res: ExpressResponse) => {
161
+ res.status(404).send("Not found");
162
+ });
163
+ };
164
+
165
+ export default mountVendorAssets;
@@ -30,6 +30,7 @@ import CaptureSpan from "../../Telemetry/CaptureSpan";
30
30
  import BadDataException from "../../../../Types/Exception/BadDataException";
31
31
  import ObjectID from "../../../../Types/ObjectID";
32
32
  import WorkspaceProjectAuthTokenService from "../../../Services/WorkspaceProjectAuthTokenService";
33
+ import SSRFProtection from "../../SSRFProtection";
33
34
  import WorkspaceProjectAuthToken, {
34
35
  MicrosoftTeamsChat,
35
36
  MicrosoftTeamsChatType,
@@ -113,6 +114,16 @@ const MICROSOFT_TEAMS_APP_TYPE: string = "SingleTenant";
113
114
  // Maximum number of pages to fetch when paginating teams
114
115
  const MICROSOFT_TEAMS_MAX_PAGES: number = 500;
115
116
 
117
+ /*
118
+ * Hosts that may receive a Teams incoming webhook. Connector webhooks are
119
+ * issued on outlook.office.com / outlook.office365.com, and per-tenant ones on
120
+ * <tenant>.webhook.office.com — all covered by these two registrable domains.
121
+ */
122
+ const MICROSOFT_TEAMS_WEBHOOK_DOMAINS: Array<string> = [
123
+ "office.com",
124
+ "office365.com",
125
+ ];
126
+
116
127
  export default class MicrosoftTeamsUtil extends WorkspaceBase {
117
128
  private static cachedAdapter: CloudAdapter | null = null;
118
129
  private static readonly WELCOME_CARD_STATE_KEY: string =
@@ -700,6 +711,13 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
700
711
  await API.post({
701
712
  url: data.url,
702
713
  data: payload,
714
+ options: {
715
+ /*
716
+ * The host is pinned to Microsoft, but do not let a redirect from it
717
+ * bounce this request to an internal address.
718
+ */
719
+ doNotFollowRedirects: true,
720
+ },
703
721
  });
704
722
 
705
723
  if (!apiResult) {
@@ -730,11 +748,16 @@ export default class MicrosoftTeamsUtil extends WorkspaceBase {
730
748
  public static isValidMicrosoftTeamsIncomingWebhookUrl(
731
749
  incomingWebhookUrl: URL,
732
750
  ): boolean {
733
- // Check if the URL contains outlook.office.com or office.com webhook pattern
734
- const urlString: string = incomingWebhookUrl.toString();
735
- return (
736
- urlString.includes("outlook.office.com") ||
737
- urlString.includes("office.com")
751
+ /*
752
+ * Pin on the URL's HOST, not on a substring of the whole URL. Subscribers
753
+ * (including unauthenticated ones on a public status page) supply this
754
+ * value and the server POSTs to it, so a substring check was satisfied by
755
+ * an attacker-controlled path or query — `http://169.254.169.254/?x=office.com`
756
+ * passed and turned this into an SSRF into the cloud metadata endpoint.
757
+ */
758
+ return SSRFProtection.isUrlOnAllowedDomain(
759
+ incomingWebhookUrl,
760
+ MICROSOFT_TEAMS_WEBHOOK_DOMAINS,
738
761
  );
739
762
  }
740
763
 
@@ -2282,6 +2282,11 @@ export default class SlackUtil extends WorkspaceBase {
2282
2282
  options: {
2283
2283
  retries: 3,
2284
2284
  exponentialBackoff: true,
2285
+ /*
2286
+ * The host is pinned to hooks.slack.com, but do not let a redirect
2287
+ * from it bounce this request to an internal address.
2288
+ */
2289
+ doNotFollowRedirects: true,
2285
2290
  },
2286
2291
  });
2287
2292