@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.
- package/Models/DatabaseModels/AIInsight.ts +1 -1
- package/Models/DatabaseModels/DatabaseBaseModel/FileModel.ts +2 -3
- package/Models/DatabaseModels/DeletedProject.ts +595 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/Monitor.ts +111 -0
- package/Models/DatabaseModels/Project.ts +3 -3
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -1
- package/Server/API/FileAPI.ts +15 -2
- package/Server/API/MicrosoftTeamsAPI.ts +6 -1
- package/Server/API/ProjectAPI.ts +214 -0
- package/Server/API/StatusPageAPI.ts +39 -6
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.ts +50 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Services/AIAgentService.ts +20 -0
- package/Server/Services/DashboardDomainService.ts +73 -28
- package/Server/Services/DatabaseService.ts +1 -0
- package/Server/Services/DeletedProjectService.ts +138 -0
- package/Server/Services/FileService.ts +61 -0
- package/Server/Services/IncidentEpisodePublicNoteService.ts +18 -0
- package/Server/Services/IncidentPublicNoteService.ts +18 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MonitorService.ts +311 -0
- package/Server/Services/ProbeService.ts +20 -0
- package/Server/Services/ProjectService.ts +297 -1
- package/Server/Services/PushNotificationService.ts +71 -0
- package/Server/Services/ScheduledMaintenancePublicNoteService.ts +18 -0
- package/Server/Services/StatusPageAnnouncementService.ts +53 -0
- package/Server/Services/StatusPageDomainService.ts +73 -28
- package/Server/Services/TeamMemberService.ts +117 -12
- package/Server/Services/TelemetryIngestionKeyService.ts +1 -0
- package/Server/Services/UserWebhookService.ts +34 -109
- package/Server/Services/WorkspaceNotificationRuleService.ts +69 -0
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +37 -1
- package/Server/Types/Database/DeleteById.ts +2 -0
- package/Server/Types/Database/DeleteOneBy.ts +6 -0
- package/Server/Types/Domain.ts +41 -0
- package/Server/Types/Workflow/Components/API/Delete.ts +2 -1
- package/Server/Types/Workflow/Components/API/Get.ts +2 -1
- package/Server/Types/Workflow/Components/API/Patch.ts +2 -1
- package/Server/Types/Workflow/Components/API/Post.ts +2 -1
- package/Server/Types/Workflow/Components/API/Put.ts +2 -1
- package/Server/Types/Workflow/Components/API/Utils.ts +41 -2
- package/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts +25 -19
- package/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts +6 -5
- package/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.ts +32 -14
- package/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts +31 -13
- package/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts +29 -20
- package/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts +28 -21
- package/Server/Types/Workflow/Components/BaseModel/LogComponentError.ts +89 -0
- package/Server/Types/Workflow/Components/BaseModel/ModelArguments.ts +208 -0
- package/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts +11 -0
- package/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts +41 -18
- package/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts +45 -19
- package/Server/Types/Workflow/Components/Discord/SendMessageToChannel.ts +16 -9
- package/Server/Types/Workflow/Components/IncomingWebhookUtils.ts +66 -0
- package/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.ts +15 -9
- package/Server/Types/Workflow/Components/Slack/SendMessageToChannel.ts +17 -0
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +1 -1
- package/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.ts +171 -0
- package/Server/Utils/AI/SRE/FixFromIncidentTaskTrigger.ts +3 -3
- package/Server/Utils/AI/SRE/Insights/FixRouting.ts +3 -2
- package/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.ts +1 -1
- package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +6 -6
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +43 -5
- package/Server/Utils/DataSource/EgressGuard.ts +176 -16
- package/Server/Utils/DataSource/HttpFetch.ts +6 -45
- package/Server/Utils/Execute.ts +8 -0
- package/Server/Utils/InlineImageAccessTokenSync.ts +23 -0
- package/Server/Utils/LLM/LLMService.ts +57 -12
- package/Server/Utils/Monitor/MonitorAlert.ts +40 -0
- package/Server/Utils/Monitor/MonitorDependencySuppression.ts +192 -0
- package/Server/Utils/Monitor/MonitorIncident.ts +40 -0
- package/Server/Utils/Monitor/MonitorResource.ts +36 -0
- package/Server/Utils/Response.ts +142 -6
- package/Server/Utils/SSRFProtection.ts +273 -60
- package/Server/Utils/StartServer.ts +8 -0
- package/Server/Utils/VM/VMRunner.ts +88 -1
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +555 -49
- package/Server/Utils/Workspace/Slack/Slack.ts +25 -2
- package/Server/Utils/Workspace/WorkspaceBase.ts +6 -0
- package/Tests/App/Dashboard/DangerZone.test.tsx +334 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +539 -0
- package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +678 -0
- package/Tests/App/Dashboard/DashboardVariablesDiscard.test.tsx +706 -0
- package/Tests/App/Dashboard/DashboardVariablesModal.test.tsx +508 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +470 -0
- package/Tests/App/StatusPage/StatusPageDetailPagesNotFound.test.tsx +308 -0
- package/Tests/Models/DatabaseModels/DeletedProjectModel.test.ts +251 -0
- package/Tests/Models/DatabaseModels/SavedViewJSONColumnTuples.test.ts +115 -0
- package/Tests/Server/API/FileAPIAccessControl.test.ts +316 -0
- package/Tests/Server/API/ProjectAPI.test.ts +299 -1
- package/Tests/Server/API/ProjectAPIAdjustBalance.test.ts +459 -0
- package/Tests/Server/API/ProjectAPIAdjustBalanceBillingDisabled.test.ts +177 -0
- package/Tests/Server/Services/DatabaseServiceDeletionReason.test.ts +123 -0
- package/Tests/Server/Services/DeletedProjectService.test.ts +415 -0
- package/Tests/Server/Services/DeletedProjectServiceSelfHosted.test.ts +70 -0
- package/Tests/Server/Services/DomainSubdomainHooks.test.ts +94 -0
- package/Tests/Server/Services/FileIsPublicBooleanMigration.test.ts +244 -0
- package/Tests/Server/Services/FileServiceMimeType.test.ts +89 -0
- package/Tests/Server/Services/MonitorServiceDependencyValidation.test.ts +702 -0
- package/Tests/Server/Services/ProjectServiceAdjustBalance.test.ts +615 -0
- package/Tests/Server/Services/ProjectServiceDeleteAudit.test.ts +406 -0
- package/Tests/Server/Services/PublishTimeImageVisibility.test.ts +450 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +669 -0
- package/Tests/Server/Services/UserWebhookSSRF.test.ts +174 -0
- package/Tests/Server/Services/WebPushEndpointAllowlist.test.ts +133 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenRepoint.test.ts +618 -0
- package/Tests/Server/Types/Workflow/Components/ApiComponentErrorPort.test.ts +26 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +215 -0
- package/Tests/Server/Types/Workflow/Components/BaseModelDatabaseComponents.test.ts +742 -0
- package/Tests/Server/Types/Workflow/Components/ChatWebhookComponents.test.ts +256 -0
- package/Tests/Server/Types/Workflow/Components/ModelArguments.test.ts +371 -0
- package/Tests/Server/Utils/AI/CodeAgentCommandGuard.test.ts +204 -0
- package/Tests/Server/Utils/AI/CodeAgentWriteLimits.test.ts +213 -0
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +52 -5
- package/Tests/Server/Utils/AI/LLMServiceBaseUrl.test.ts +3 -0
- package/Tests/Server/Utils/AI/LLMServiceEgressGuard.test.ts +315 -0
- package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +3 -0
- package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +17 -5
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +14 -1
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +35 -0
- package/Tests/Server/Utils/AI/StubLLMEgressGuard.ts +25 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +62 -7
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +69 -0
- package/Tests/Server/Utils/DataSource/EgressGuardPinning.test.ts +330 -0
- package/Tests/Server/Utils/GitHubCreatePullRequestWithToken.test.ts +393 -0
- package/Tests/Server/Utils/InlineImageAccessTokenSync.test.ts +202 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppression.test.ts +811 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +604 -0
- package/Tests/Server/Utils/ResponseSendFileResponse.test.ts +192 -0
- package/Tests/Server/Utils/SSRFProtectionBypasses.test.ts +406 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +194 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +1169 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +2 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +1605 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTenantResolution.test.ts +1218 -0
- package/Tests/Types/AI/AIChatMessageStatus.test.ts +79 -0
- package/Tests/Types/AI/AIInsightStatus.test.ts +40 -0
- package/Tests/Types/API/HostnameAuthorityParsing.test.ts +157 -0
- package/Tests/Types/Dashboard/DashboardTemplateInvariants.test.ts +27 -0
- package/Tests/Types/DataSource/DataSourceLimits.test.ts +71 -0
- package/Tests/Types/DataSource/DataSourceType.test.ts +181 -0
- package/Tests/Types/DomainSubdomainValidation.test.ts +90 -0
- package/Tests/Types/JSONFunctions.test.ts +252 -1
- package/Tests/Types/Monitor/SnmpMonitor/SnmpAuthProtocol.test.ts +61 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpPrivProtocol.test.ts +62 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpSecurityLevel.test.ts +68 -0
- package/Tests/Types/Monitor/SnmpMonitor/SnmpVersion.test.ts +51 -0
- package/Tests/UI/Components/404.test.tsx +1 -1
- package/Tests/UI/Components/Alert.test.tsx +1 -1
- package/Tests/UI/Components/Badge.test.tsx +1 -1
- package/Tests/UI/Components/BasicForm.test.tsx +39 -10
- package/Tests/UI/Components/Button.test.tsx +1 -1
- package/Tests/UI/Components/Card.test.tsx +1 -1
- package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +1 -1
- package/Tests/UI/Components/ColorViewer.test.tsx +1 -1
- package/Tests/UI/Components/ComponentsModal.test.tsx +7 -1
- package/Tests/UI/Components/DictionaryOfStrings.test.tsx +1 -1
- package/Tests/UI/Components/Dropdown.test.tsx +1 -1
- package/Tests/UI/Components/FilePicker.test.tsx +1 -1
- package/Tests/UI/Components/HiddenText.test.tsx +1 -1
- package/Tests/UI/Components/Input.test.tsx +1 -1
- package/Tests/UI/Components/Loader.test.tsx +1 -1
- package/Tests/UI/Components/LogTimeRangePicker.test.tsx +1 -1
- package/Tests/UI/Components/MasterPage.test.tsx +1 -1
- package/Tests/UI/Components/ModalPortalDismissal.test.tsx +489 -0
- package/Tests/UI/Components/ModelDelete.test.tsx +214 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableSearchLabels.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/BaseModelTableUrlState.test.tsx +1 -1
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1 -1
- package/Tests/UI/Components/NavBar.test.tsx +1 -1
- package/Tests/UI/Components/OperatorSelectorKeyboard.test.tsx +1 -1
- package/Tests/UI/Components/Pagination.test.tsx +1 -1
- package/Tests/UI/Components/Pill.test.tsx +1 -1
- package/Tests/UI/Components/Probe.test.tsx +1 -1
- package/Tests/UI/Components/ProgressBar.test.tsx +1 -1
- package/Tests/UI/Components/RadioButtons.test.tsx +1 -1
- package/Tests/UI/Components/SideMenuItem.test.tsx +1 -1
- package/Tests/UI/Components/SideOver.test.tsx +500 -6
- package/Tests/UI/Components/Tabs.test.tsx +1 -1
- package/Tests/UI/Components/TelemetrySearchBar.test.tsx +1 -1
- package/Tests/UI/Components/TelemetryTimeRangePicker.test.tsx +1 -1
- package/Tests/UI/Components/TextArea.test.tsx +1 -1
- package/Tests/UI/Components/Toast.test.tsx +1 -1
- package/Tests/UI/Components/Toggle.test.tsx +1 -1
- package/Tests/UI/Components/TopSection.test.tsx +1 -1
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +1 -1
- package/Tests/UI/Rum/ReplayScrubber.test.tsx +1 -1
- package/Tests/UI/Rum/ReplayStage.test.tsx +1 -1
- package/Tests/UI/Telemetry/TelemetrySnapshotWindowAlert.test.tsx +1 -1
- package/Tests/UI/Utils/LocalStorageArrayRoundTrip.test.ts +72 -0
- package/Tests/Utils/Dashboard/HtmlWidgetDocument.test.ts +19 -2
- package/Tests/Utils/Dashboard/ModelQueryVariableInterpolation.test.ts +44 -0
- package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +288 -5
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +42 -0
- package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +27 -0
- package/Tests/Utils/LegacySerializedArray.test.ts +139 -0
- package/Tests/Utils/Monitor/MonitorDependencyRule.test.ts +190 -0
- package/Tests/Utils/Realtime.test.ts +78 -0
- package/Tests/Utils/Telemetry/SavedViewFilters.test.ts +246 -0
- package/Tests/__mocks__/i18next-browser-languagedetector.js +21 -0
- package/Types/AI/FixPullRequestCiStatus.ts +1 -1
- package/Types/AI/FixVerificationStatus.ts +2 -2
- package/Types/API/Hostname.ts +92 -4
- package/Types/API/URL.ts +43 -25
- package/Types/Billing/BalanceAdjustmentType.ts +14 -0
- package/Types/Billing/ProjectBalanceType.ts +13 -0
- package/Types/Domain.ts +26 -0
- package/Types/File/MimeType.ts +11 -0
- package/Types/JSONFunctions.ts +15 -0
- package/Types/Telemetry/TelemetrySavedViewState.ts +9 -1
- package/Types/Workflow/Components/BaseModel.ts +92 -28
- package/Typings/Index.d.ts +12 -0
- package/UI/Components/GanttChart/ChartContainer.tsx +4 -3
- package/UI/Components/Modal/ConfirmModal.tsx +10 -5
- package/UI/Components/Modal/Modal.tsx +27 -47
- package/UI/Components/ModelDelete/ModelDelete.tsx +23 -6
- package/UI/Components/SideOver/SideOver.tsx +195 -68
- package/UI/Utils/PageScrollLock.ts +66 -0
- package/Utils/Dashboard/HtmlWidgetDocument.ts +4 -2
- package/Utils/Dashboard/VariableInterpolation.ts +19 -1
- package/Utils/LegacySerializedArray.ts +60 -0
- package/Utils/Monitor/MonitorDependencyRule.ts +88 -0
- package/Utils/Telemetry/SavedViewFilters.ts +88 -0
- package/build/dist/Models/DatabaseModels/AIInsight.js +1 -1
- package/build/dist/Models/DatabaseModels/AIInsight.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js +2 -3
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/FileModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DeletedProject.js +640 -0
- package/build/dist/Models/DatabaseModels/DeletedProject.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +106 -0
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +3 -3
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/FileAPI.js +14 -2
- package/build/dist/Server/API/FileAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +6 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectAPI.js +157 -4
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +27 -3
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786446545142-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js +30 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786449497966-AddMonitorDependency.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786461136170-AddDeletedProjectTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIAgentService.js +17 -0
- package/build/dist/Server/Services/AIAgentService.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +55 -18
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +1 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DeletedProjectService.js +124 -0
- package/build/dist/Server/Services/DeletedProjectService.js.map +1 -0
- package/build/dist/Server/Services/FileService.js +52 -0
- package/build/dist/Server/Services/FileService.js.map +1 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js +10 -1
- package/build/dist/Server/Services/IncidentEpisodePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js +10 -1
- package/build/dist/Server/Services/IncidentPublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +232 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +17 -0
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +228 -6
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/PushNotificationService.js +56 -0
- package/build/dist/Server/Services/PushNotificationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js +10 -1
- package/build/dist/Server/Services/ScheduledMaintenancePublicNoteService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageAnnouncementService.js +54 -0
- package/build/dist/Server/Services/StatusPageAnnouncementService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +55 -18
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +97 -6
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js +1 -0
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
- package/build/dist/Server/Services/UserWebhookService.js +31 -87
- package/build/dist/Server/Services/UserWebhookService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +65 -0
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +23 -0
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +37 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js +2 -1
- package/build/dist/Server/Types/Workflow/Components/API/Delete.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js +2 -1
- package/build/dist/Server/Types/Workflow/Components/API/Get.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js +2 -1
- package/build/dist/Server/Types/Workflow/Components/API/Patch.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js +2 -1
- package/build/dist/Server/Types/Workflow/Components/API/Post.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js +2 -1
- package/build/dist/Server/Types/Workflow/Components/API/Put.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js +35 -2
- package/build/dist/Server/Types/Workflow/Components/API/Utils.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js +15 -11
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js +2 -3
- package/build/dist/Server/Types/Workflow/Components/BaseModel/CreateOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js +21 -10
- package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js +21 -10
- package/build/dist/Server/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js +18 -13
- package/build/dist/Server/Types/Workflow/Components/BaseModel/FindManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js +18 -13
- package/build/dist/Server/Types/Workflow/Components/BaseModel/FindOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js +43 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/LogComponentError.js.map +1 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js +116 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/ModelArguments.js.map +1 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js +7 -0
- package/build/dist/Server/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js +24 -13
- package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js +27 -13
- package/build/dist/Server/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js +14 -6
- package/build/dist/Server/Types/Workflow/Components/Discord/SendMessageToChannel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js +42 -0
- package/build/dist/Server/Types/Workflow/Components/IncomingWebhookUtils.js.map +1 -0
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js +15 -6
- package/build/dist/Server/Types/Workflow/Components/MicrosoftTeams/SendMessageToChannel.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js +10 -2
- package/build/dist/Server/Types/Workflow/Components/Slack/SendMessageToChannel.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js +141 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeAgentWorkspaceGuard.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/TelemetryImprovementTaskTrigger.js +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +6 -6
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +39 -5
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +94 -10
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +1 -19
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -1
- package/build/dist/Server/Utils/Execute.js +2 -2
- package/build/dist/Server/Utils/Execute.js.map +1 -1
- package/build/dist/Server/Utils/InlineImageAccessTokenSync.js +13 -0
- package/build/dist/Server/Utils/InlineImageAccessTokenSync.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +43 -12
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +44 -24
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js +158 -0
- package/build/dist/Server/Utils/Monitor/MonitorDependencySuppression.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +50 -30
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +32 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +112 -5
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +219 -53
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +7 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +73 -1
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +445 -43
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +18 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/WorkspaceBase.js.map +1 -1
- package/build/dist/Types/AI/FixPullRequestCiStatus.js +1 -1
- package/build/dist/Types/AI/FixVerificationStatus.js +2 -2
- package/build/dist/Types/API/Hostname.js +73 -4
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/API/URL.js +42 -24
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Billing/BalanceAdjustmentType.js +15 -0
- package/build/dist/Types/Billing/BalanceAdjustmentType.js.map +1 -0
- package/build/dist/Types/Billing/ProjectBalanceType.js +14 -0
- package/build/dist/Types/Billing/ProjectBalanceType.js.map +1 -0
- package/build/dist/Types/Domain.js +22 -0
- package/build/dist/Types/Domain.js.map +1 -1
- package/build/dist/Types/File/MimeType.js +11 -0
- package/build/dist/Types/File/MimeType.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +14 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Workflow/Components/BaseModel.js +83 -28
- package/build/dist/Types/Workflow/Components/BaseModel.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/ChartContainer.js +4 -3
- package/build/dist/UI/Components/GanttChart/ChartContainer.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +3 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +24 -34
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/ModelDelete/ModelDelete.js +11 -6
- package/build/dist/UI/Components/ModelDelete/ModelDelete.js.map +1 -1
- package/build/dist/UI/Components/SideOver/SideOver.js +105 -30
- package/build/dist/UI/Components/SideOver/SideOver.js.map +1 -1
- package/build/dist/UI/Utils/PageScrollLock.js +52 -0
- package/build/dist/UI/Utils/PageScrollLock.js.map +1 -0
- package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js +4 -2
- package/build/dist/Utils/Dashboard/HtmlWidgetDocument.js.map +1 -1
- package/build/dist/Utils/Dashboard/VariableInterpolation.js +19 -1
- package/build/dist/Utils/Dashboard/VariableInterpolation.js.map +1 -1
- package/build/dist/Utils/LegacySerializedArray.js +51 -0
- package/build/dist/Utils/LegacySerializedArray.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorDependencyRule.js +47 -0
- package/build/dist/Utils/Monitor/MonitorDependencyRule.js.map +1 -0
- package/build/dist/Utils/Telemetry/SavedViewFilters.js +52 -0
- package/build/dist/Utils/Telemetry/SavedViewFilters.js.map +1 -0
- package/jest.config.json +3 -0
- package/package.json +1 -1
- package/tsconfig.json +3 -1
package/Server/Utils/Response.ts
CHANGED
|
@@ -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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
|
40
|
+
parsed = new globalThis.URL(rawUrl.toString());
|
|
34
41
|
} catch {
|
|
35
42
|
return "";
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
return SSRFProtection.extractHost(parsed.hostname.
|
|
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
|
|
59
|
+
parsed = new globalThis.URL(rawUrl.toString());
|
|
53
60
|
} catch {
|
|
54
61
|
return false;
|
|
55
62
|
}
|
|
56
63
|
|
|
57
|
-
|
|
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
|
|
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
|
|
181
|
-
|
|
436
|
+
const address: string = SSRFProtection.stripZoneId(
|
|
437
|
+
hostname.replace(/^\[|\]$/g, ""),
|
|
182
438
|
);
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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 (
|
|
223
|
-
|
|
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
|
-
|
|
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
|
|