@oneuptime/common 12.0.6 → 12.0.7

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 (424) hide show
  1. package/Models/DatabaseModels/AIInsight.ts +1 -1
  2. package/Models/DatabaseModels/DatabaseBaseModel/FileModel.ts +2 -3
  3. package/Models/DatabaseModels/DeletedProject.ts +595 -0
  4. package/Models/DatabaseModels/Index.ts +2 -0
  5. package/Models/DatabaseModels/Monitor.ts +111 -0
  6. package/Models/DatabaseModels/Project.ts +3 -3
  7. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -1
  8. package/Server/API/FileAPI.ts +15 -2
  9. package/Server/API/MicrosoftTeamsAPI.ts +6 -1
  10. package/Server/API/ProjectAPI.ts +214 -0
  11. package/Server/API/StatusPageAPI.ts +39 -6
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.ts +50 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.ts +67 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.ts +39 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  16. package/Server/Services/AIAgentService.ts +20 -0
  17. package/Server/Services/DashboardDomainService.ts +73 -28
  18. package/Server/Services/DatabaseService.ts +1 -0
  19. package/Server/Services/DeletedProjectService.ts +138 -0
  20. package/Server/Services/FileService.ts +61 -0
  21. package/Server/Services/IncidentEpisodePublicNoteService.ts +18 -0
  22. package/Server/Services/IncidentPublicNoteService.ts +18 -0
  23. package/Server/Services/Index.ts +2 -0
  24. package/Server/Services/MonitorService.ts +311 -0
  25. package/Server/Services/ProbeService.ts +20 -0
  26. package/Server/Services/ProjectService.ts +297 -1
  27. package/Server/Services/PushNotificationService.ts +71 -0
  28. package/Server/Services/ScheduledMaintenancePublicNoteService.ts +18 -0
  29. package/Server/Services/StatusPageAnnouncementService.ts +53 -0
  30. package/Server/Services/StatusPageDomainService.ts +73 -28
  31. package/Server/Services/TeamMemberService.ts +117 -12
  32. package/Server/Services/TelemetryIngestionKeyService.ts +1 -0
  33. package/Server/Services/UserWebhookService.ts +34 -109
  34. package/Server/Services/WorkspaceNotificationRuleService.ts +69 -0
  35. package/Server/Services/WorkspaceProjectAuthTokenService.ts +37 -1
  36. package/Server/Types/Database/DeleteById.ts +2 -0
  37. package/Server/Types/Database/DeleteOneBy.ts +6 -0
  38. package/Server/Types/Domain.ts +41 -0
  39. package/Server/Types/Workflow/Components/API/Delete.ts +2 -1
  40. package/Server/Types/Workflow/Components/API/Get.ts +2 -1
  41. package/Server/Types/Workflow/Components/API/Patch.ts +2 -1
  42. package/Server/Types/Workflow/Components/API/Post.ts +2 -1
  43. package/Server/Types/Workflow/Components/API/Put.ts +2 -1
  44. package/Server/Types/Workflow/Components/API/Utils.ts +41 -2
  45. package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +25 -19
  46. package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +6 -5
  47. package/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts +32 -14
  48. package/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts +31 -13
  49. package/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts +29 -20
  50. package/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts +28 -21
  51. package/Server/Types/Workflow/Components/BaseModel/LogComponentError.ts +89 -0
  52. package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +208 -0
  53. package/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts +11 -0
  54. package/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts +41 -18
  55. package/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts +45 -19
  56. package/Server/Types/Workflow/Components/Discord/SendMessageToChannel.ts +16 -9
  57. package/Server/Types/Workflow/Components/IncomingWebhookUtils.ts +66 -0
  58. package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +15 -9
  59. package/Server/Types/Workflow/Components/Slack/SendMessageToChannel.ts +17 -0
  60. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
  61. package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +171 -0
  62. package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +3 -3
  63. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +3 -2
  64. package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +1 -1
  65. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +6 -6
  66. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +43 -5
  67. package/Server/Utils/DataSource/EgressGuard.ts +176 -16
  68. package/Server/Utils/DataSource/HttpFetch.ts +6 -45
  69. package/Server/Utils/Execute.ts +8 -0
  70. package/Server/Utils/InlineImageAccessTokenSync.ts +23 -0
  71. package/Server/Utils/LLM/LLMService.ts +57 -12
  72. package/Server/Utils/Monitor/MonitorAlert.ts +40 -0
  73. package/Server/Utils/Monitor/MonitorDependencySuppression.ts +192 -0
  74. package/Server/Utils/Monitor/MonitorIncident.ts +40 -0
  75. package/Server/Utils/Monitor/MonitorResource.ts +36 -0
  76. package/Server/Utils/Response.ts +142 -6
  77. package/Server/Utils/SSRFProtection.ts +273 -60
  78. package/Server/Utils/StartServer.ts +8 -0
  79. package/Server/Utils/VM/VMRunner.ts +88 -1
  80. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +555 -49
  81. package/Server/Utils/Workspace/Slack/Slack.ts +25 -2
  82. package/Server/Utils/Workspace/WorkspaceBase.ts +6 -0
  83. package/Tests/App/Dashboard/DangerZone.test.tsx +334 -0
  84. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +539 -0
  85. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +678 -0
  86. package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +706 -0
  87. package/Tests/App/Dashboard/DashboardVariablesModal.test.tsx +508 -0
  88. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +470 -0
  89. package/Tests/App/StatusPage/StatusPageDetailPagesNotFound.test.tsx +308 -0
  90. package/Tests/Models/DatabaseModels/DeletedProjectModel.test.ts +251 -0
  91. package/Tests/Models/DatabaseModels/SavedViewJSONColumnTuples.test.ts +115 -0
  92. package/Tests/Server/API/FileAPIAccessControl.test.ts +316 -0
  93. package/Tests/Server/API/ProjectAPI.test.ts +299 -1
  94. package/Tests/Server/API/ProjectAPIAdjustBalance.test.ts +459 -0
  95. package/Tests/Server/API/ProjectAPIAdjustBalanceBillingDisabled.test.ts +177 -0
  96. package/Tests/Server/Services/DatabaseServiceDeletionReason.test.ts +123 -0
  97. package/Tests/Server/Services/DeletedProjectService.test.ts +415 -0
  98. package/Tests/Server/Services/DeletedProjectServiceSelfHosted.test.ts +70 -0
  99. package/Tests/Server/Services/DomainSubdomainHooks.test.ts +94 -0
  100. package/Tests/Server/Services/FileIsPublicBooleanMigration.test.ts +244 -0
  101. package/Tests/Server/Services/FileServiceMimeType.test.ts +89 -0
  102. package/Tests/Server/Services/MonitorServiceDependencyValidation.test.ts +702 -0
  103. package/Tests/Server/Services/ProjectServiceAdjustBalance.test.ts +615 -0
  104. package/Tests/Server/Services/ProjectServiceDeleteAudit.test.ts +406 -0
  105. package/Tests/Server/Services/PublishTimeImageVisibility.test.ts +450 -0
  106. package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +669 -0
  107. package/Tests/Server/Services/UserWebhookSSRF.test.ts +174 -0
  108. package/Tests/Server/Services/WebPushEndpointAllowlist.test.ts +133 -0
  109. package/Tests/Server/Services/WorkspaceProjectAuthTokenRepoint.test.ts +618 -0
  110. package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +26 -1
  111. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +215 -0
  112. package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +742 -0
  113. package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +256 -0
  114. package/Tests/Server/Types/Workflow/Components/ModelArguments.test.ts +371 -0
  115. package/Tests/Server/Utils/AI/CodeAgentCommandGuard.test.ts +204 -0
  116. package/Tests/Server/Utils/AI/CodeAgentWriteLimits.test.ts +213 -0
  117. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +52 -5
  118. package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +3 -0
  119. package/Tests/Server/Utils/AI/LLMServiceEgressGuard.test.ts +315 -0
  120. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +3 -0
  121. package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +17 -5
  122. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +14 -1
  123. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +35 -0
  124. package/Tests/Server/Utils/AI/StubLLMEgressGuard.ts +25 -0
  125. package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +62 -7
  126. package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +69 -0
  127. package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +330 -0
  128. package/Tests/Server/Utils/GitHubCreatePullRequestWithToken.test.ts +393 -0
  129. package/Tests/Server/Utils/InlineImageAccessTokenSync.test.ts +202 -0
  130. package/Tests/Server/Utils/Monitor/MonitorDependencySuppression.test.ts +811 -0
  131. package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +604 -0
  132. package/Tests/Server/Utils/ResponseSendFileResponse.test.ts +192 -0
  133. package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +406 -0
  134. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +194 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +1169 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +2 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +1605 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTenantResolution.test.ts +1218 -0
  139. package/Tests/Types/AI/AIChatMessageStatus.test.ts +79 -0
  140. package/Tests/Types/AI/AIInsightStatus.test.ts +40 -0
  141. package/Tests/Types/API/HostnameAuthorityParsing.test.ts +157 -0
  142. package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +27 -0
  143. package/Tests/Types/DataSource/DataSourceLimits.test.ts +71 -0
  144. package/Tests/Types/DataSource/DataSourceType.test.ts +181 -0
  145. package/Tests/Types/DomainSubdomainValidation.test.ts +90 -0
  146. package/Tests/Types/JSONFunctions.test.ts +252 -1
  147. package/Tests/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.test.ts +61 -0
  148. package/Tests/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.test.ts +62 -0
  149. package/Tests/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.test.ts +68 -0
  150. package/Tests/Types/Monitor/SnmpMonitor/SnmpVersion.test.ts +51 -0
  151. package/Tests/UI/Components/404.test.tsx +1 -1
  152. package/Tests/UI/Components/Alert.test.tsx +1 -1
  153. package/Tests/UI/Components/Badge.test.tsx +1 -1
  154. package/Tests/UI/Components/BasicForm.test.tsx +39 -10
  155. package/Tests/UI/Components/Button.test.tsx +1 -1
  156. package/Tests/UI/Components/Card.test.tsx +1 -1
  157. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +1 -1
  158. package/Tests/UI/Components/ColorViewer.test.tsx +1 -1
  159. package/Tests/UI/Components/ComponentsModal.test.tsx +7 -1
  160. package/Tests/UI/Components/DictionaryOfStrings.test.tsx +1 -1
  161. package/Tests/UI/Components/Dropdown.test.tsx +1 -1
  162. package/Tests/UI/Components/FilePicker.test.tsx +1 -1
  163. package/Tests/UI/Components/HiddenText.test.tsx +1 -1
  164. package/Tests/UI/Components/Input.test.tsx +1 -1
  165. package/Tests/UI/Components/Loader.test.tsx +1 -1
  166. package/Tests/UI/Components/LogTimeRangePicker.test.tsx +1 -1
  167. package/Tests/UI/Components/MasterPage.test.tsx +1 -1
  168. package/Tests/UI/Components/ModalPortalDismissal.test.tsx +489 -0
  169. package/Tests/UI/Components/ModelDelete.test.tsx +214 -0
  170. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1 -1
  171. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +1 -1
  172. package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +1 -1
  173. package/Tests/UI/Components/ModelTable/BaseModelTableUrlState.test.tsx +1 -1
  174. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1 -1
  175. package/Tests/UI/Components/NavBar.test.tsx +1 -1
  176. package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +1 -1
  177. package/Tests/UI/Components/Pagination.test.tsx +1 -1
  178. package/Tests/UI/Components/Pill.test.tsx +1 -1
  179. package/Tests/UI/Components/Probe.test.tsx +1 -1
  180. package/Tests/UI/Components/ProgressBar.test.tsx +1 -1
  181. package/Tests/UI/Components/RadioButtons.test.tsx +1 -1
  182. package/Tests/UI/Components/SideMenuItem.test.tsx +1 -1
  183. package/Tests/UI/Components/SideOver.test.tsx +500 -6
  184. package/Tests/UI/Components/Tabs.test.tsx +1 -1
  185. package/Tests/UI/Components/TelemetrySearchBar.test.tsx +1 -1
  186. package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +1 -1
  187. package/Tests/UI/Components/TextArea.test.tsx +1 -1
  188. package/Tests/UI/Components/Toast.test.tsx +1 -1
  189. package/Tests/UI/Components/Toggle.test.tsx +1 -1
  190. package/Tests/UI/Components/TopSection.test.tsx +1 -1
  191. package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +1 -1
  192. package/Tests/UI/Rum/ReplayScrubber.test.tsx +1 -1
  193. package/Tests/UI/Rum/ReplayStage.test.tsx +1 -1
  194. package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +1 -1
  195. package/Tests/UI/Utils/LocalStorageArrayRoundTrip.test.ts +72 -0
  196. package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +19 -2
  197. package/Tests/Utils/Dashboard/ModelQueryVariableInterpolation.test.ts +44 -0
  198. package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +288 -5
  199. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +42 -0
  200. package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +27 -0
  201. package/Tests/Utils/LegacySerializedArray.test.ts +139 -0
  202. package/Tests/Utils/Monitor/MonitorDependencyRule.test.ts +190 -0
  203. package/Tests/Utils/Realtime.test.ts +78 -0
  204. package/Tests/Utils/Telemetry/SavedViewFilters.test.ts +246 -0
  205. package/Tests/__mocks__/i18next-browser-languagedetector.js +21 -0
  206. package/Types/AI/FixPullRequestCiStatus.ts +1 -1
  207. package/Types/AI/FixVerificationStatus.ts +2 -2
  208. package/Types/API/Hostname.ts +92 -4
  209. package/Types/API/URL.ts +43 -25
  210. package/Types/Billing/BalanceAdjustmentType.ts +14 -0
  211. package/Types/Billing/ProjectBalanceType.ts +13 -0
  212. package/Types/Domain.ts +26 -0
  213. package/Types/File/MimeType.ts +11 -0
  214. package/Types/JSONFunctions.ts +15 -0
  215. package/Types/Telemetry/TelemetrySavedViewState.ts +9 -1
  216. package/Types/Workflow/Components/BaseModel.ts +92 -28
  217. package/Typings/Index.d.ts +12 -0
  218. package/UI/Components/GanttChart/ChartContainer.tsx +4 -3
  219. package/UI/Components/Modal/ConfirmModal.tsx +10 -5
  220. package/UI/Components/Modal/Modal.tsx +27 -47
  221. package/UI/Components/ModelDelete/ModelDelete.tsx +23 -6
  222. package/UI/Components/SideOver/SideOver.tsx +195 -68
  223. package/UI/Utils/PageScrollLock.ts +66 -0
  224. package/Utils/Dashboard/HtmlWidgetDocument.ts +4 -2
  225. package/Utils/Dashboard/VariableInterpolation.ts +19 -1
  226. package/Utils/LegacySerializedArray.ts +60 -0
  227. package/Utils/Monitor/MonitorDependencyRule.ts +88 -0
  228. package/Utils/Telemetry/SavedViewFilters.ts +88 -0
  229. package/build/dist/Models/DatabaseModels/AIInsight.js +1 -1
  230. package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
  231. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js +2 -3
  232. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js.map +1 -1
  233. package/build/dist/Models/DatabaseModels/DeletedProject.js +640 -0
  234. package/build/dist/Models/DatabaseModels/DeletedProject.js.map +1 -0
  235. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  236. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  237. package/build/dist/Models/DatabaseModels/Monitor.js +106 -0
  238. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  239. package/build/dist/Models/DatabaseModels/Project.js +3 -3
  240. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  241. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  242. package/build/dist/Server/API/FileAPI.js +14 -2
  243. package/build/dist/Server/API/FileAPI.js.map +1 -1
  244. package/build/dist/Server/API/MicrosoftTeamsAPI.js +6 -1
  245. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  246. package/build/dist/Server/API/ProjectAPI.js +157 -4
  247. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  248. package/build/dist/Server/API/StatusPageAPI.js +27 -3
  249. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  250. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js +34 -0
  251. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js.map +1 -0
  252. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js +30 -0
  253. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js.map +1 -0
  254. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js +20 -0
  255. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js.map +1 -0
  256. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  257. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  258. package/build/dist/Server/Services/AIAgentService.js +17 -0
  259. package/build/dist/Server/Services/AIAgentService.js.map +1 -1
  260. package/build/dist/Server/Services/DashboardDomainService.js +55 -18
  261. package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
  262. package/build/dist/Server/Services/DatabaseService.js +1 -0
  263. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  264. package/build/dist/Server/Services/DeletedProjectService.js +124 -0
  265. package/build/dist/Server/Services/DeletedProjectService.js.map +1 -0
  266. package/build/dist/Server/Services/FileService.js +52 -0
  267. package/build/dist/Server/Services/FileService.js.map +1 -1
  268. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +10 -1
  269. package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
  270. package/build/dist/Server/Services/IncidentPublicNoteService.js +10 -1
  271. package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
  272. package/build/dist/Server/Services/Index.js +2 -0
  273. package/build/dist/Server/Services/Index.js.map +1 -1
  274. package/build/dist/Server/Services/MonitorService.js +232 -0
  275. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  276. package/build/dist/Server/Services/ProbeService.js +17 -0
  277. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  278. package/build/dist/Server/Services/ProjectService.js +228 -6
  279. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  280. package/build/dist/Server/Services/PushNotificationService.js +56 -0
  281. package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
  282. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +10 -1
  283. package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
  284. package/build/dist/Server/Services/StatusPageAnnouncementService.js +54 -0
  285. package/build/dist/Server/Services/StatusPageAnnouncementService.js.map +1 -1
  286. package/build/dist/Server/Services/StatusPageDomainService.js +55 -18
  287. package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
  288. package/build/dist/Server/Services/TeamMemberService.js +97 -6
  289. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  290. package/build/dist/Server/Services/TelemetryIngestionKeyService.js +1 -0
  291. package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
  292. package/build/dist/Server/Services/UserWebhookService.js +31 -87
  293. package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
  294. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +65 -0
  295. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  296. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +23 -0
  297. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  298. package/build/dist/Server/Types/Domain.js +37 -0
  299. package/build/dist/Server/Types/Domain.js.map +1 -1
  300. package/build/dist/Server/Types/Workflow/Components/API/Delete.js +2 -1
  301. package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
  302. package/build/dist/Server/Types/Workflow/Components/API/Get.js +2 -1
  303. package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
  304. package/build/dist/Server/Types/Workflow/Components/API/Patch.js +2 -1
  305. package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
  306. package/build/dist/Server/Types/Workflow/Components/API/Post.js +2 -1
  307. package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
  308. package/build/dist/Server/Types/Workflow/Components/API/Put.js +2 -1
  309. package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
  310. package/build/dist/Server/Types/Workflow/Components/API/Utils.js +35 -2
  311. package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
  312. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +15 -11
  313. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
  314. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +2 -3
  315. package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
  316. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js +21 -10
  317. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js.map +1 -1
  318. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js +21 -10
  319. package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js.map +1 -1
  320. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js +18 -13
  321. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js.map +1 -1
  322. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js +18 -13
  323. package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js.map +1 -1
  324. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js +43 -0
  325. package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js.map +1 -0
  326. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +116 -0
  327. package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -0
  328. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js +7 -0
  329. package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js.map +1 -1
  330. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js +24 -13
  331. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js.map +1 -1
  332. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js +27 -13
  333. package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js.map +1 -1
  334. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js +14 -6
  335. package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js.map +1 -1
  336. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js +42 -0
  337. package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js.map +1 -0
  338. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +15 -6
  339. package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
  340. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js +10 -2
  341. package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js.map +1 -1
  342. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
  343. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +141 -0
  344. package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js.map +1 -1
  345. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  346. package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +1 -1
  347. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +6 -6
  348. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -1
  349. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +39 -5
  350. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  351. package/build/dist/Server/Utils/DataSource/EgressGuard.js +94 -10
  352. package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
  353. package/build/dist/Server/Utils/DataSource/HttpFetch.js +1 -19
  354. package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
  355. package/build/dist/Server/Utils/Execute.js +2 -2
  356. package/build/dist/Server/Utils/Execute.js.map +1 -1
  357. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js +13 -0
  358. package/build/dist/Server/Utils/InlineImageAccessTokenSync.js.map +1 -1
  359. package/build/dist/Server/Utils/LLM/LLMService.js +43 -12
  360. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  361. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +44 -24
  362. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  363. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js +158 -0
  364. package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js.map +1 -0
  365. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +50 -30
  366. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  367. package/build/dist/Server/Utils/Monitor/MonitorResource.js +32 -0
  368. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  369. package/build/dist/Server/Utils/Response.js +112 -5
  370. package/build/dist/Server/Utils/Response.js.map +1 -1
  371. package/build/dist/Server/Utils/SSRFProtection.js +219 -53
  372. package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
  373. package/build/dist/Server/Utils/StartServer.js +7 -0
  374. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  375. package/build/dist/Server/Utils/VM/VMRunner.js +73 -1
  376. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  377. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +445 -43
  378. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  379. package/build/dist/Server/Utils/Workspace/Slack/Slack.js +18 -1
  380. package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
  381. package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
  382. package/build/dist/Types/AI/FixPullRequestCiStatus.js +1 -1
  383. package/build/dist/Types/AI/FixVerificationStatus.js +2 -2
  384. package/build/dist/Types/API/Hostname.js +73 -4
  385. package/build/dist/Types/API/Hostname.js.map +1 -1
  386. package/build/dist/Types/API/URL.js +42 -24
  387. package/build/dist/Types/API/URL.js.map +1 -1
  388. package/build/dist/Types/Billing/BalanceAdjustmentType.js +15 -0
  389. package/build/dist/Types/Billing/BalanceAdjustmentType.js.map +1 -0
  390. package/build/dist/Types/Billing/ProjectBalanceType.js +14 -0
  391. package/build/dist/Types/Billing/ProjectBalanceType.js.map +1 -0
  392. package/build/dist/Types/Domain.js +22 -0
  393. package/build/dist/Types/Domain.js.map +1 -1
  394. package/build/dist/Types/File/MimeType.js +11 -0
  395. package/build/dist/Types/File/MimeType.js.map +1 -1
  396. package/build/dist/Types/JSONFunctions.js +14 -0
  397. package/build/dist/Types/JSONFunctions.js.map +1 -1
  398. package/build/dist/Types/Workflow/Components/BaseModel.js +83 -28
  399. package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
  400. package/build/dist/UI/Components/GanttChart/ChartContainer.js +4 -3
  401. package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +1 -1
  402. package/build/dist/UI/Components/Modal/ConfirmModal.js +3 -1
  403. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  404. package/build/dist/UI/Components/Modal/Modal.js +24 -34
  405. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  406. package/build/dist/UI/Components/ModelDelete/ModelDelete.js +11 -6
  407. package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
  408. package/build/dist/UI/Components/SideOver/SideOver.js +105 -30
  409. package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
  410. package/build/dist/UI/Utils/PageScrollLock.js +52 -0
  411. package/build/dist/UI/Utils/PageScrollLock.js.map +1 -0
  412. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +4 -2
  413. package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -1
  414. package/build/dist/Utils/Dashboard/VariableInterpolation.js +19 -1
  415. package/build/dist/Utils/Dashboard/VariableInterpolation.js.map +1 -1
  416. package/build/dist/Utils/LegacySerializedArray.js +51 -0
  417. package/build/dist/Utils/LegacySerializedArray.js.map +1 -0
  418. package/build/dist/Utils/Monitor/MonitorDependencyRule.js +47 -0
  419. package/build/dist/Utils/Monitor/MonitorDependencyRule.js.map +1 -0
  420. package/build/dist/Utils/Telemetry/SavedViewFilters.js +52 -0
  421. package/build/dist/Utils/Telemetry/SavedViewFilters.js.map +1 -0
  422. package/jest.config.json +3 -0
  423. package/package.json +1 -1
  424. package/tsconfig.json +3 -1
@@ -21,11 +21,63 @@ import Dictionary from "../../Types/Dictionary";
21
21
  import Exception from "../../Types/Exception/Exception";
22
22
  import { JSONArray, JSONObject } from "../../Types/JSON";
23
23
  import ListData from "../../Types/ListData";
24
+ import MimeType from "../../Types/File/MimeType";
24
25
  import PositiveNumber from "../../Types/PositiveNumber";
25
26
  import Route from "../../Types/API/Route";
26
27
  import CaptureSpan from "./Telemetry/CaptureSpan";
27
28
  import { IsBillingEnabled } from "../EnvironmentConfig";
28
29
 
30
+ /*
31
+ * Raster image types, which is to say the types that cannot carry script and
32
+ * are therefore safe both to echo back verbatim and to render in our own
33
+ * origin. An SVG is not here on purpose: it is a document, not a picture, and
34
+ * a PDF is not here either because it gets its own scripting engine.
35
+ *
36
+ * The list is deliberately wider than the MimeType enum. Until November 2025
37
+ * (62d74c1d84) the file picker stored the browser's own MIME string verbatim,
38
+ * so installs upgrading through this change hold rows with values that never
39
+ * appeared in the enum - "image/vnd.microsoft.icon" from a favicon upload,
40
+ * "image/jpg" from assorted tools. Collapsing those to octet-stream would stop
41
+ * every one of those avatars and logos from rendering, because nosniff means
42
+ * an <img> cannot recover from a type it was given wrongly.
43
+ */
44
+ const SAFE_IMAGE_MIME_TYPES: Set<string> = new Set<string>([
45
+ MimeType.png,
46
+ // MimeType.jpg is the same string as MimeType.jpeg, so it is covered.
47
+ MimeType.jpeg,
48
+ MimeType.gif,
49
+ MimeType.webp,
50
+ MimeType.ico,
51
+ MimeType.bmp,
52
+ MimeType.tiff,
53
+ MimeType.avif,
54
+ MimeType.heic,
55
+ "image/jpg",
56
+ "image/pjpeg",
57
+ "image/vnd.microsoft.icon",
58
+ "image/x-ms-bmp",
59
+ ]);
60
+
61
+ /*
62
+ * `fileType` is a plain varchar filled in from the upload request body, so by
63
+ * the time a file is served back it holds whatever string the uploader chose -
64
+ * the MimeType enum is a compile-time type and is erased at runtime. Echoing
65
+ * that string back as Content-Type is what turns "upload an image" into "run
66
+ * script on the dashboard origin", because /api, /file and /dashboard are the
67
+ * same origin. Only types we actually recognise get echoed back.
68
+ *
69
+ * Note this read-side list is wider than the one FileService enforces on
70
+ * upload: new rows are held to the canonical enum, old rows only have to be
71
+ * servable.
72
+ */
73
+ const ALLOWED_MIME_TYPES: Set<string> = new Set<string>([
74
+ ...Object.values(MimeType),
75
+ ...SAFE_IMAGE_MIME_TYPES,
76
+ ]);
77
+
78
+ // What an unrecognised fileType degrades to: opaque bytes, never a document.
79
+ const FALLBACK_MIME_TYPE: string = "application/octet-stream";
80
+
29
81
  export default class Response {
30
82
  @CaptureSpan()
31
83
  public static sendEmptySuccessResponse(
@@ -67,21 +119,105 @@ export default class Response {
67
119
  oneUptimeResponse.status(statusCode).send(body);
68
120
  }
69
121
 
122
+ /*
123
+ * Reduce a stored fileType to something safe to hand back as Content-Type.
124
+ * Anything we do not recognise - a made-up string, "text/html", a row
125
+ * written before uploads were validated - becomes opaque bytes.
126
+ */
127
+ public static getSafeFileType(fileType: string | undefined | null): string {
128
+ const normalizedFileType: string = (fileType || "").trim().toLowerCase();
129
+
130
+ if (ALLOWED_MIME_TYPES.has(normalizedFileType)) {
131
+ return normalizedFileType;
132
+ }
133
+
134
+ return FALLBACK_MIME_TYPE;
135
+ }
136
+
137
+ /*
138
+ * The filename goes into a response header, so it gets the same treatment as
139
+ * the MIME string: no quotes, no CR/LF, nothing that could split the header.
140
+ * This is the ASCII fallback only - see getContentDisposition, which pairs it
141
+ * with an encoded copy so a non-Latin name is not reduced to underscores.
142
+ */
143
+ public static getSafeFileName(name: string | undefined | null): string {
144
+ const safeFileName: string = (name || "")
145
+ .replace(/[^a-zA-Z0-9._-]/g, "_")
146
+ .slice(0, 100);
147
+
148
+ return safeFileName || "download";
149
+ }
150
+
151
+ /*
152
+ * RFC 6266: `filename` carries an ASCII-safe fallback for old clients and
153
+ * `filename*` carries the real name percent-encoded, so "月次レポート.pdf"
154
+ * downloads under its own name instead of as a row of underscores. Both are
155
+ * escaped, so neither can close the quoted string or inject a header.
156
+ */
157
+ public static getContentDisposition(
158
+ disposition: "inline" | "attachment",
159
+ name: string | undefined | null,
160
+ ): string {
161
+ const fileName: string = (name || "").trim().slice(0, 100) || "download";
162
+
163
+ /*
164
+ * encodeURIComponent leaves !'()* alone and RFC 5987's attr-char does not
165
+ * allow them - single quote especially, since it delimits the value.
166
+ */
167
+ const encodedFileName: string = encodeURIComponent(fileName).replace(
168
+ /['()*!]/g,
169
+ (character: string): string => {
170
+ return `%${character.charCodeAt(0).toString(16).toUpperCase()}`;
171
+ },
172
+ );
173
+
174
+ return `${disposition}; filename="${Response.getSafeFileName(fileName)}"; filename*=UTF-8''${encodedFileName}`;
175
+ }
176
+
70
177
  @CaptureSpan()
71
178
  public static async sendFileResponse(
72
179
  _req: ExpressRequest | ExpressRequest,
73
180
  res: ExpressResponse,
74
181
  file: FileModel,
75
182
  ): Promise<void> {
76
- /** Create read stream */
77
-
78
183
  const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
79
184
 
80
- /** Set the proper content type */
81
- oneUptimeResponse.set("Content-Type", file.fileType);
185
+ const fileType: string = Response.getSafeFileType(file.fileType);
186
+
187
+ oneUptimeResponse.set("Content-Type", fileType);
188
+
189
+ /*
190
+ * Belt and braces for the types we do echo back. nosniff stops the browser
191
+ * from guessing its way to a different type; the CSP gives the response an
192
+ * opaque origin with no scripting even if it is opened as a top-level
193
+ * document, so a crafted file cannot reach dashboard cookies; and nothing
194
+ * we serve here is ever meant to be framed.
195
+ */
196
+ oneUptimeResponse.set("X-Content-Type-Options", "nosniff");
197
+ oneUptimeResponse.set(
198
+ "Content-Security-Policy",
199
+ "sandbox; script-src 'none'; object-src 'none'; frame-ancestors 'none'",
200
+ );
201
+ oneUptimeResponse.set("X-Frame-Options", "DENY");
202
+
203
+ /*
204
+ * Inline is reserved for raster images, which cannot execute anything.
205
+ * Everything else - SVG, PDF, office docs, whatever an older row happens
206
+ * to hold - downloads instead of rendering. This is deliberately not an
207
+ * SVG ban: <img> and <link rel="icon"> ignore Content-Disposition, so
208
+ * status page logos, favicons and avatars still render. The only
209
+ * behaviour that changes is a top-level navigation to the file URL, and
210
+ * that is exactly the navigation an XSS payload needs.
211
+ */
212
+ oneUptimeResponse.set(
213
+ "Content-Disposition",
214
+ Response.getContentDisposition(
215
+ SAFE_IMAGE_MIME_TYPES.has(fileType) ? "inline" : "attachment",
216
+ file.name,
217
+ ),
218
+ );
219
+
82
220
  oneUptimeResponse.status(200);
83
- /** Return response */
84
- // readstream.pipe(res);
85
221
 
86
222
  oneUptimeResponse.send(file.file);
87
223
  }
@@ -1,9 +1,7 @@
1
1
  import URL from "../../Types/API/URL";
2
2
  import BadDataException from "../../Types/Exception/BadDataException";
3
3
  import dns from "dns";
4
-
5
- const IPV4_LITERAL_REGEX: RegExp = /^(\d{1,3}\.){3}\d{1,3}$/;
6
- const IPV6_UNIQUE_LOCAL_REGEX: RegExp = /^f[cd][0-9a-f]{2}:/;
4
+ import net from "net";
7
5
 
8
6
  /*
9
7
  * Guards outbound HTTP(S) requests whose target is (fully or partly)
@@ -28,14 +26,23 @@ export default class SSRFProtection {
28
26
  * (`http://169.254.169.254/?x=office.com`) and pins nothing.
29
27
  */
30
28
  public static getBareHostname(rawUrl: string | URL): string {
31
- let parsed: URL;
29
+ /*
30
+ * WHATWG, not URL.fromString. OneUptime's parser takes everything before
31
+ * the first "/" as the authority and never terminates at "?" or "#", so it
32
+ * reads "https://169.254.169.254#.office.com" as the host
33
+ * "169.254.169.254#.office.com" - which ends with ".office.com" and
34
+ * satisfied the allowlist, while axios re-parsed the same string per WHATWG
35
+ * and dialled 169.254.169.254. A pin is only worth anything if it parses
36
+ * the host the same way the HTTP client will.
37
+ */
38
+ let parsed: globalThis.URL;
32
39
  try {
33
- parsed = URL.fromString(rawUrl.toString());
40
+ parsed = new globalThis.URL(rawUrl.toString());
34
41
  } catch {
35
42
  return "";
36
43
  }
37
44
 
38
- return SSRFProtection.extractHost(parsed.hostname.hostname.toLowerCase());
45
+ return SSRFProtection.extractHost(parsed.hostname.toLowerCase());
39
46
  }
40
47
 
41
48
  /*
@@ -47,14 +54,15 @@ export default class SSRFProtection {
47
54
  rawUrl: string | URL,
48
55
  allowedDomains: Array<string>,
49
56
  ): boolean {
50
- let parsed: URL;
57
+ let parsed: globalThis.URL;
51
58
  try {
52
- parsed = URL.fromString(rawUrl.toString());
59
+ parsed = new globalThis.URL(rawUrl.toString());
53
60
  } catch {
54
61
  return false;
55
62
  }
56
63
 
57
- if (parsed.protocol.toString().toLowerCase() !== "https://") {
64
+ // Scheme and host must come from the SAME parser, or they can disagree.
65
+ if (parsed.protocol.toLowerCase() !== "https:") {
58
66
  return false;
59
67
  }
60
68
 
@@ -73,6 +81,30 @@ export default class SSRFProtection {
73
81
  public static async validateWebhookTargetIsSafe(
74
82
  rawUrl: string | URL,
75
83
  ): Promise<void> {
84
+ /*
85
+ * URL.fromString only knows http/https/ws/wss/mongodb/mailto and silently
86
+ * DEFAULTS anything else to https - "file:///etc/passwd" comes back as
87
+ * host "file", not as a file: URL - so the protocol check below never sees
88
+ * the scheme the caller actually wrote. Read it off the raw string first.
89
+ *
90
+ * The ":" must be followed by "/" or this would treat the host in a
91
+ * scheme-less "example.com:8080/hook" as a scheme and reject it.
92
+ */
93
+ const schemeMatch: RegExpMatchArray | null = rawUrl
94
+ .toString()
95
+ .trim()
96
+ .match(/^([a-z][a-z0-9+.-]*):\//i);
97
+
98
+ if (
99
+ schemeMatch &&
100
+ schemeMatch[1] &&
101
+ !["http", "https"].includes(schemeMatch[1].toLowerCase())
102
+ ) {
103
+ throw new BadDataException(
104
+ "Webhook URL must use http or https protocol.",
105
+ );
106
+ }
107
+
76
108
  let parsed: URL;
77
109
  try {
78
110
  parsed = URL.fromString(rawUrl.toString());
@@ -164,72 +196,253 @@ export default class SSRFProtection {
164
196
  return host;
165
197
  }
166
198
 
199
+ /*
200
+ * An IP literal needs no DNS lookup - but "looks like it has a colon" is not
201
+ * the same question, and getting it wrong is a bypass in both directions.
202
+ * net.isIP is the authority: it rejects "2852039166" and "0177.0.0.1"
203
+ * (which then go to DNS, where getaddrinfo decodes them and the resolved
204
+ * address gets checked) and accepts every real IPv6 spelling.
205
+ */
167
206
  private static isIpLiteral(hostname: string): boolean {
168
- return IPV4_LITERAL_REGEX.test(hostname) || hostname.includes(":");
207
+ return net.isIP(SSRFProtection.stripZoneId(hostname)) !== 0;
208
+ }
209
+
210
+ private static stripZoneId(address: string): string {
211
+ const zoneIndex: number = address.indexOf("%");
212
+ return zoneIndex === -1 ? address : address.substring(0, zoneIndex);
213
+ }
214
+
215
+ /*
216
+ * Expands an IPv6 address into its eight 16-bit groups, folding an embedded
217
+ * IPv4 tail ("::ffff:127.0.0.1") into two hex groups on the way, so that
218
+ * every spelling of one address produces one canonical answer. Returns null
219
+ * if the address is not parseable as IPv6.
220
+ *
221
+ * This exists because the checks below CANNOT be string prefix matches:
222
+ * "::1", "0:0:0:0:0:0:0:1" and "0000:...:0001" are the same host, and
223
+ * "::ffff:169.254.169.254" is the cloud metadata endpoint wearing a hat.
224
+ */
225
+ private static expandIpv6(address: string): Array<number> | null {
226
+ let ip: string = SSRFProtection.stripZoneId(address.toLowerCase());
227
+
228
+ if (net.isIPv6(ip) === false) {
229
+ return null;
230
+ }
231
+
232
+ // Fold a dotted-quad tail into the two hex groups it stands for.
233
+ const lastColonIndex: number = ip.lastIndexOf(":");
234
+ const tail: string = ip.substring(lastColonIndex + 1);
235
+ if (tail.includes(".")) {
236
+ if (net.isIPv4(tail) === false) {
237
+ return null;
238
+ }
239
+ const octets: Array<number> = tail.split(".").map((part: string) => {
240
+ return parseInt(part, 10);
241
+ });
242
+ const high: string = (
243
+ ((octets[0] as number) << 8) |
244
+ (octets[1] as number)
245
+ ).toString(16);
246
+ const low: string = (
247
+ ((octets[2] as number) << 8) |
248
+ (octets[3] as number)
249
+ ).toString(16);
250
+ ip = `${ip.substring(0, lastColonIndex)}:${high}:${low}`;
251
+ }
252
+
253
+ const halves: Array<string> = ip.split("::");
254
+ if (halves.length > 2) {
255
+ return null;
256
+ }
257
+
258
+ const head: Array<string> =
259
+ halves[0] === undefined || halves[0] === "" ? [] : halves[0].split(":");
260
+ const rear: Array<string> =
261
+ halves.length === 2 && halves[1] !== undefined && halves[1] !== ""
262
+ ? halves[1].split(":")
263
+ : [];
264
+
265
+ let groups: Array<string>;
266
+ if (halves.length === 2) {
267
+ const zeroFill: number = 8 - head.length - rear.length;
268
+ if (zeroFill < 0) {
269
+ return null;
270
+ }
271
+ groups = [...head, ...new Array(zeroFill).fill("0"), ...rear];
272
+ } else {
273
+ groups = head;
274
+ }
275
+
276
+ if (groups.length !== 8) {
277
+ return null;
278
+ }
279
+
280
+ const parsed: Array<number> = groups.map((group: string) => {
281
+ return parseInt(group || "0", 16);
282
+ });
283
+
284
+ if (
285
+ parsed.some((group: number) => {
286
+ return Number.isNaN(group) || group < 0 || group > 0xffff;
287
+ })
288
+ ) {
289
+ return null;
290
+ }
291
+
292
+ return parsed;
293
+ }
294
+
295
+ /*
296
+ * True when an IPv6 literal points somewhere the server must never be made
297
+ * to reach. Ranges, not spellings: loopback, unspecified, link-local
298
+ * (fe80::/10), unique-local (fc00::/7) and multicast (ff00::/8), plus the
299
+ * three ways IPv6 can carry an IPv4 destination - IPv4-mapped
300
+ * (::ffff:0:0/96), IPv4-compatible (::/96) and the NAT64 well-known prefix
301
+ * (64:ff9b::/96) - which are handed to the IPv4 blocklist.
302
+ */
303
+ private static isBlockedIpv6(address: string): boolean {
304
+ const groups: Array<number> | null = SSRFProtection.expandIpv6(address);
305
+
306
+ if (!groups) {
307
+ // Not parseable as IPv6; the caller's other checks decide.
308
+ return false;
309
+ }
310
+
311
+ const isZeroThrough: (endExclusive: number) => boolean = (
312
+ endExclusive: number,
313
+ ): boolean => {
314
+ return groups.slice(0, endExclusive).every((group: number) => {
315
+ return group === 0;
316
+ });
317
+ };
318
+
319
+ const embeddedIpv4: () => string = (): string => {
320
+ const high: number = groups[6] as number;
321
+ const low: number = groups[7] as number;
322
+ return `${high >> 8}.${high & 0xff}.${low >> 8}.${low & 0xff}`;
323
+ };
324
+
325
+ // :: — unspecified. Connecting here lands on the local host.
326
+ if (isZeroThrough(8)) {
327
+ return true;
328
+ }
329
+
330
+ // ::1 — loopback.
331
+ if (isZeroThrough(7) && groups[7] === 1) {
332
+ return true;
333
+ }
334
+
335
+ // ::ffff:0:0/96 — IPv4-mapped.
336
+ if (isZeroThrough(5) && groups[5] === 0xffff) {
337
+ return SSRFProtection.isBlockedIpv4(embeddedIpv4());
338
+ }
339
+
340
+ // 64:ff9b::/96 — NAT64 well-known prefix.
341
+ if (
342
+ groups[0] === 0x0064 &&
343
+ groups[1] === 0xff9b &&
344
+ groups[2] === 0 &&
345
+ groups[3] === 0 &&
346
+ groups[4] === 0 &&
347
+ groups[5] === 0
348
+ ) {
349
+ return SSRFProtection.isBlockedIpv4(embeddedIpv4());
350
+ }
351
+
352
+ // ::/96 — IPv4-compatible (deprecated, still routable by some stacks).
353
+ if (isZeroThrough(6)) {
354
+ return SSRFProtection.isBlockedIpv4(embeddedIpv4());
355
+ }
356
+
357
+ const first: number = groups[0] as number;
358
+
359
+ // fe80::/10 — link-local.
360
+ if ((first & 0xffc0) === 0xfe80) {
361
+ return true;
362
+ }
363
+
364
+ // fc00::/7 — unique-local.
365
+ if ((first & 0xfe00) === 0xfc00) {
366
+ return true;
367
+ }
368
+
369
+ // fec0::/10 — site-local. Deprecated by RFC 3879, still routed on some networks.
370
+ if ((first & 0xffc0) === 0xfec0) {
371
+ return true;
372
+ }
373
+
374
+ // ff00::/8 — multicast.
375
+ if ((first & 0xff00) === 0xff00) {
376
+ return true;
377
+ }
378
+
379
+ return false;
380
+ }
381
+
382
+ private static isBlockedIpv4(address: string): boolean {
383
+ const octets: Array<number> = address.split(".").map((part: string) => {
384
+ return Number(part);
385
+ });
386
+
387
+ if (
388
+ octets.length !== 4 ||
389
+ octets.some((octet: number) => {
390
+ return Number.isNaN(octet) || octet < 0 || octet > 255;
391
+ })
392
+ ) {
393
+ return true;
394
+ }
395
+
396
+ const [first, second] = octets as [number, number, number, number];
397
+
398
+ if (first === 0) {
399
+ return true; // 0.0.0.0/8 — "this host".
400
+ }
401
+ if (first === 127) {
402
+ return true; // loopback
403
+ }
404
+ if (first === 10) {
405
+ return true; // RFC-1918
406
+ }
407
+ if (first === 172 && (second & 0xf0) === 16) {
408
+ return true; // RFC-1918 172.16/12
409
+ }
410
+ if (first === 192 && second === 168) {
411
+ return true; // RFC-1918
412
+ }
413
+ if (first === 169 && second === 254) {
414
+ return true; // link-local, incl. the 169.254.169.254 metadata endpoint
415
+ }
416
+ if (first === 100 && (second & 0xc0) === 64) {
417
+ return true; // CGNAT 100.64/10
418
+ }
419
+ if (first >= 224) {
420
+ return true; // multicast, reserved, and 255.255.255.255
421
+ }
422
+
423
+ return false;
169
424
  }
170
425
 
171
426
  private static isBlockedHostnameLiteral(hostname: string): boolean {
172
427
  if (
173
428
  hostname === "localhost" ||
429
+ hostname === "localhost." ||
174
430
  hostname.endsWith(".localhost") ||
175
431
  hostname === "metadata.google.internal"
176
432
  ) {
177
433
  return true;
178
434
  }
179
435
 
180
- const ipv4Match: RegExpMatchArray | null = hostname.match(
181
- /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,
436
+ const address: string = SSRFProtection.stripZoneId(
437
+ hostname.replace(/^\[|\]$/g, ""),
182
438
  );
183
- if (ipv4Match) {
184
- const octets: Array<number> = [
185
- Number(ipv4Match[1]),
186
- Number(ipv4Match[2]),
187
- Number(ipv4Match[3]),
188
- Number(ipv4Match[4]),
189
- ];
190
-
191
- if (
192
- octets.some((o: number) => {
193
- return o < 0 || o > 255;
194
- })
195
- ) {
196
- return true;
197
- }
198
- if (octets[0] === 0) {
199
- return true;
200
- }
201
- if (octets[0] === 127) {
202
- return true;
203
- }
204
- if (octets[0] === 10) {
205
- return true;
206
- }
207
- if (octets[0] === 172 && (octets[1]! & 0xf0) === 16) {
208
- return true;
209
- }
210
- if (octets[0] === 192 && octets[1] === 168) {
211
- return true;
212
- }
213
- if (octets[0] === 169 && octets[1] === 254) {
214
- return true;
215
- }
216
- if (octets[0] === 100 && (octets[1]! & 0xc0) === 64) {
217
- return true;
218
- }
219
- return false;
439
+
440
+ if (net.isIPv4(address)) {
441
+ return SSRFProtection.isBlockedIpv4(address);
220
442
  }
221
443
 
222
- if (hostname.includes(":")) {
223
- const stripped: string = hostname.replace(/^\[|\]$/g, "");
224
- if (stripped === "::1" || stripped === "::") {
225
- return true;
226
- }
227
- if (stripped.startsWith("fe80:") || stripped.startsWith("fe80::")) {
228
- return true;
229
- }
230
- if (IPV6_UNIQUE_LOCAL_REGEX.test(stripped)) {
231
- return true;
232
- }
444
+ if (net.isIPv6(address)) {
445
+ return SSRFProtection.isBlockedIpv6(address);
233
446
  }
234
447
 
235
448
  return false;
@@ -127,6 +127,14 @@ const setDefaultHeaders: RequestHandler = (
127
127
 
128
128
  res.header("Access-Control-Expose-Headers", CORS_EXPOSED_HEADERS.join(", "));
129
129
 
130
+ /*
131
+ * Content sniffing turns "the server declared a boring type" into "the
132
+ * browser guessed an interesting one", which is how an upload becomes a
133
+ * document. nginx sets this on the static app locations but not on /api or
134
+ * /file, so set it here for everything the app itself serves.
135
+ */
136
+ res.header("X-Content-Type-Options", "nosniff");
137
+
130
138
  next();
131
139
  };
132
140
 
@@ -10,6 +10,7 @@ import CaptureSpan from "../Telemetry/CaptureSpan";
10
10
  import Dictionary from "../../../Types/Dictionary";
11
11
  import GenericObject from "../../../Types/GenericObject";
12
12
  import vm, { Context } from "vm";
13
+ import SSRFProtection from "../SSRFProtection";
13
14
 
14
15
  /**
15
16
  * Symbol used to retrieve the real (unwrapped) target from a sandbox proxy.
@@ -356,6 +357,51 @@ function unwrapProxy<T>(value: T): T {
356
357
  }
357
358
 
358
359
  export default class VMRunner {
360
+ /*
361
+ * Works out which URL the sandbox's axios call will actually dial, so the
362
+ * SSRF check and the request agree on the destination. axios accepts the
363
+ * target three different ways - a positional url, config.url on a
364
+ * `request()` call, and a relative url resolved against config.baseURL - and
365
+ * a check that only looked at the positional argument would miss two of them.
366
+ *
367
+ * Anything that does not end up an absolute http(s) URL is rejected rather
368
+ * than guessed at.
369
+ */
370
+ private static resolveEffectiveRequestUrl(data: {
371
+ method: string;
372
+ url: string;
373
+ config?: JSONObject | undefined;
374
+ }): string {
375
+ const baseUrl: string =
376
+ typeof data.config?.["baseURL"] === "string"
377
+ ? (data.config["baseURL"] as string)
378
+ : "";
379
+
380
+ let target: string = data.url || "";
381
+
382
+ if (data.method === "request" || !target) {
383
+ const configUrl: unknown = data.config?.["url"];
384
+ target = typeof configUrl === "string" ? configUrl : target;
385
+ }
386
+
387
+ let absolute: string = target;
388
+
389
+ const absoluteUrlRegex: RegExp = /^https?:\/\//i;
390
+
391
+ if (!absoluteUrlRegex.test(absolute)) {
392
+ if (!baseUrl) {
393
+ throw new Error("Request URL must be an absolute http or https URL.");
394
+ }
395
+ absolute = `${baseUrl.replace(/\/+$/, "")}/${absolute.replace(/^\/+/, "")}`;
396
+ }
397
+
398
+ if (!absoluteUrlRegex.test(absolute)) {
399
+ throw new Error("Request URL must be an absolute http or https URL.");
400
+ }
401
+
402
+ return absolute;
403
+ }
404
+
359
405
  @CaptureSpan()
360
406
  public static async runCodeInNodeVM(data: {
361
407
  code: string;
@@ -730,7 +776,7 @@ export default class VMRunner {
730
776
  hasBody && arg1 ? (JSON.parse(arg1) as JSONObject) : undefined;
731
777
 
732
778
  const configStr: string | undefined = hasBody ? arg2 : arg1;
733
- const config: JSONObject | undefined = configStr
779
+ let config: JSONObject | undefined = configStr
734
780
  ? (JSON.parse(configStr) as JSONObject)
735
781
  : undefined;
736
782
 
@@ -783,6 +829,47 @@ export default class VMRunner {
783
829
  return plain;
784
830
  };
785
831
 
832
+ /*
833
+ * SSRF guard (GHSA-v5xh-rw9h-77fv).
834
+ *
835
+ * This bridge hands the host process's real axios to code the user
836
+ * wrote - the Custom JavaScript workflow component documents "you can
837
+ * use axios module" - so without a check here it is a strictly more
838
+ * capable version of the hole that was reported against the API
839
+ * components: arbitrary method, headers and body against the internal
840
+ * network, with the full response marshalled back into the sandbox
841
+ * and on into the workflow log.
842
+ *
843
+ * It has to live on THIS side of the isolate boundary. The
844
+ * sandbox-side axios shim is attacker-editable - user code can simply
845
+ * redefine it - so a check there guards nothing.
846
+ */
847
+ const effectiveUrl: string = VMRunner.resolveEffectiveRequestUrl({
848
+ method,
849
+ url,
850
+ config,
851
+ });
852
+
853
+ await SSRFProtection.validateWebhookTargetIsSafe(effectiveUrl);
854
+
855
+ /*
856
+ * The URL that was just validated has to be the URL that gets
857
+ * dialled. Each of these would otherwise steer the connection
858
+ * somewhere else entirely, past the check above:
859
+ * proxy - sends the request to an arbitrary host:port
860
+ * socketPath - connects to a unix socket (/var/run/docker.sock)
861
+ * transport / adapter - replaces the transport wholesale
862
+ * and a redirect would let a validated public host nominate an
863
+ * internal one on the second hop.
864
+ */
865
+ const safeConfig: JSONObject = config || {};
866
+ delete safeConfig["proxy"];
867
+ delete safeConfig["socketPath"];
868
+ delete safeConfig["transport"];
869
+ delete safeConfig["adapter"];
870
+ safeConfig["maxRedirects"] = 0;
871
+ config = safeConfig;
872
+
786
873
  try {
787
874
  let response: AxiosResponse;
788
875