@oneuptime/common 12.0.33 → 12.0.34
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/AnalyticsModels/RumSession.ts +117 -1
- package/Models/AnalyticsModels/RumSessionChunk.ts +13 -0
- package/Models/DatabaseModels/AlertCustomField.ts +77 -3
- package/Models/DatabaseModels/ApiKeyPermission.ts +3 -27
- package/Models/DatabaseModels/IncidentCustomField.ts +77 -3
- package/Models/DatabaseModels/InventoryItemCustomField.ts +77 -3
- package/Models/DatabaseModels/MonitorCustomField.ts +77 -3
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +77 -3
- package/Models/DatabaseModels/RumApplication.ts +6 -6
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +77 -3
- package/Models/DatabaseModels/StatusPageCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamMember.ts +0 -9
- package/Models/DatabaseModels/TeamMemberCustomField.ts +77 -3
- package/Models/DatabaseModels/TeamPermission.ts +3 -24
- package/Models/DatabaseModels/TelemetryIngestionKey.ts +260 -0
- package/Models/DatabaseModels/UserTelegram.ts +0 -4
- package/Scripts/benchmark-fanin-capacity.js +181 -0
- package/Server/API/TelemetryAPI.ts +1284 -164
- package/Server/API/UserNotificationSettingAPI.ts +55 -0
- package/Server/API/UserTelegramAPI.ts +48 -4
- package/Server/EnvironmentConfig.ts +35 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.ts +91 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.ts +139 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.ts +121 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/QueueWorker.ts +20 -14
- package/Server/Infrastructure/Semaphore.ts +2 -0
- package/Server/Middleware/TelemetryIngest.ts +453 -7
- package/Server/Services/AccessTokenService.ts +1 -0
- package/Server/Services/AlertCustomFieldService.ts +98 -0
- package/Server/Services/AlertService.ts +37 -0
- package/Server/Services/ApiKeyPermissionService.ts +461 -29
- package/Server/Services/CustomFieldMappingService.ts +879 -0
- package/Server/Services/GlobalConfigService.ts +194 -0
- package/Server/Services/IncidentCustomFieldService.ts +98 -0
- package/Server/Services/IncidentService.ts +35 -0
- package/Server/Services/MetricService.ts +194 -0
- package/Server/Services/MonitorService.ts +32 -0
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +78 -25
- package/Server/Services/ProjectService.ts +22 -0
- package/Server/Services/RoutineEmailSettingsService.ts +73 -0
- package/Server/Services/RumSessionReplayViewService.ts +104 -24
- package/Server/Services/ScheduledMaintenanceCustomFieldService.ts +98 -0
- package/Server/Services/ScheduledMaintenanceService.ts +32 -0
- package/Server/Services/TeamMemberService.ts +78 -0
- package/Server/Services/TeamPermissionService.ts +235 -3
- package/Server/Services/TelemetryIngestionKeyService.ts +722 -19
- package/Server/Services/UserNotificationSettingService.ts +34 -6
- package/Server/Services/UserTelegramService.ts +326 -5
- package/Server/Types/Database/QueryHelper.ts +2 -0
- package/Server/Utils/APIKey/AccessPermission.ts +5 -2
- package/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.ts +54 -0
- package/Server/Utils/CustomField/CustomFieldMappingRegistry.ts +415 -0
- package/Server/Utils/CustomField/CustomFieldMappingValidator.ts +335 -0
- package/Server/Utils/DataSource/EgressGuard.ts +177 -8
- package/Server/Utils/EmailRollup/EmailRollupConstants.ts +38 -5
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +57 -4
- package/Server/Utils/FrontendEnvironment.ts +40 -0
- package/Server/Utils/LogRedaction.ts +20 -0
- package/Server/Utils/Logger.ts +11 -0
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +338 -77
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +30 -4
- package/Server/Utils/Monitor/MonitorAlert.ts +29 -6
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +110 -15
- package/Server/Utils/Monitor/MonitorIncident.ts +22 -6
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +29 -0
- package/Server/Utils/Monitor/SeriesContextEnricher.ts +304 -0
- package/Server/Utils/SSRFProtection.ts +217 -10
- package/Server/Utils/SessionReplay/SessionReplayGateCache.ts +239 -138
- package/Server/Utils/SessionReplay/SessionReplayHealthCounters.ts +305 -0
- package/Server/Utils/SessionReplay/SessionReplayReadService.ts +1430 -289
- package/Server/Utils/StartServer.ts +2 -17
- package/Server/Utils/TelegramVerificationToken.ts +185 -0
- package/Server/Utils/Telemetry/PinServiceName.ts +197 -0
- package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +38 -16
- package/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.ts +92 -0
- package/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.ts +217 -0
- package/Server/Utils/VM/VMAPI.ts +1 -0
- package/Server/Utils/VM/VMRunner.ts +975 -95
- package/Tests/App/AdminDashboard/AdminHeaderSmallScreens.test.tsx +202 -0
- package/Tests/App/Dashboard/DashboardHeaderSmallScreens.test.tsx +404 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +468 -0
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +399 -0
- package/Tests/App/StatusPage/StatusPageLastUpdated.test.tsx +263 -0
- package/Tests/App/StatusPage/StatusPageOverviewLiveAndSearch.test.tsx +753 -0
- package/Tests/App/StatusPage/StatusPageResourceSearchBox.test.tsx +224 -0
- package/Tests/Models/AnalyticsModels/RumSessionReplayColumns.test.ts +262 -0
- package/Tests/Models/CustomFieldMappingColumns.test.ts +166 -0
- package/Tests/Models/DatabaseModels/SessionReplayModels.test.ts +20 -6
- package/Tests/ResponsiveVisibility.test.ts +115 -0
- package/Tests/ResponsiveVisibility.ts +183 -0
- package/Tests/Server/API/SessionReplayAPI.test.ts +1940 -216
- package/Tests/Server/API/UserNotificationSettingAPI.test.ts +186 -0
- package/Tests/Server/API/UserTelegramAPISecurity.test.ts +231 -0
- package/Tests/Server/EnvironmentConfigFrontendSecurity.test.ts +234 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +202 -0
- package/Tests/Server/Infrastructure/Postgres/InventoryItemArchiveMigration.test.ts +41 -0
- package/Tests/Server/Infrastructure/Postgres/SessionReplayRecordEverySessionByDefaultMigration.test.ts +169 -0
- package/Tests/Server/Infrastructure/QueueWorkerTimeout.test.ts +210 -0
- package/Tests/Server/Infrastructure/TelemetryExporterDeploymentConfig.test.ts +130 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -1
- package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +1163 -0
- package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +5 -3
- package/Tests/Server/Services/AddTelemetryIngestionKeyTypeMigration.test.ts +555 -0
- package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +678 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +276 -28
- package/Tests/Server/Services/CustomFieldDropdownOptionsColumnWidth.test.ts +431 -0
- package/Tests/Server/Services/CustomFieldMappingService.test.ts +850 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +32 -0
- package/Tests/Server/Services/GlobalConfigService.test.ts +414 -1
- package/Tests/Server/Services/MetricRawEntityKeyPrune.test.ts +592 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +226 -1
- package/Tests/Server/Services/RoutineEmailSettingsPostgres.test.ts +329 -0
- package/Tests/Server/Services/RumSessionReplayViewService.test.ts +267 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +9 -0
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +9 -0
- package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +834 -0
- package/Tests/Server/Services/TelemetryIngestionKeyPolicyResolution.test.ts +814 -0
- package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +998 -0
- package/Tests/Server/Services/UserNotificationSettingRollupRouting.test.ts +141 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +18 -10
- package/Tests/Server/Services/UserTelegramVerificationSecurity.test.ts +688 -0
- package/Tests/Server/Utils/AI/SRE/Insights/FixRouting.test.ts +497 -0
- package/Tests/Server/Utils/AI/Toolbox/Serializer.test.ts +383 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +7 -3
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterConfig.test.ts +315 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingValidator.test.ts +437 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +300 -11
- package/Tests/Server/Utils/EmailRollup/EmailRollupBurstWindow.test.ts +83 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerPreferences.test.ts +362 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +77 -1
- package/Tests/Server/Utils/FrontendEnvironment.test.ts +190 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +26 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +23 -9
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +584 -0
- package/Tests/Server/Utils/Monitor/Criteria/IncomingRequestHeaderCriteria.test.ts +236 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +437 -0
- package/Tests/Server/Utils/Monitor/MonitorTemplateUtilSeriesContext.test.ts +176 -0
- package/Tests/Server/Utils/Monitor/SeriesContextEnricher.test.ts +356 -0
- package/Tests/Server/Utils/SSRFProtectionCloudServiceAddresses.test.ts +595 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCachePolicy.test.ts +198 -1
- package/Tests/Server/Utils/SessionReplay/SessionReplayHealthCounters.test.ts +453 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayReadServiceQueries.test.ts +1378 -0
- package/Tests/Server/Utils/SessionReplayOriginAllowListRefactor.test.ts +420 -0
- package/Tests/Server/Utils/TelegramVerificationToken.test.ts +253 -0
- package/Tests/Server/Utils/Telemetry/PinServiceName.test.ts +666 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriterCapacity.test.ts +458 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +424 -0
- package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.test.ts +528 -0
- package/Tests/Server/Utils/TelemetryExporterEnvironment.test.ts +56 -0
- package/Tests/Server/Utils/VM/VMRunnerHostBridgeLatency.test.ts +74 -0
- package/Tests/Server/Utils/VM/VMRunnerPrivateNetworkWiring.test.ts +100 -7
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +1176 -6
- package/Tests/Types/CustomField/CustomFieldMappingCatalog.test.ts +220 -0
- package/Tests/Types/CustomField/CustomFieldValueMapping.test.ts +348 -0
- package/Tests/Types/JSONFunctions.test.ts +260 -0
- package/Tests/Types/Monitor/CephAlertTemplates.test.ts +422 -53
- package/Tests/Types/Monitor/DockerAlertTemplates.test.ts +495 -15
- package/Tests/Types/Monitor/DockerSwarmAlertTemplates.test.ts +191 -45
- package/Tests/Types/Monitor/HostAlertTemplates.test.ts +370 -42
- package/Tests/Types/Monitor/IotAlertTemplates.test.ts +375 -20
- package/Tests/Types/Monitor/KubernetesAlertTemplates.test.ts +636 -42
- package/Tests/Types/Monitor/KubernetesMetricCatalog.test.ts +189 -0
- package/Tests/Types/Monitor/KubernetesTemplateGroupByKeys.test.ts +95 -14
- package/Tests/Types/Monitor/PodmanAlertTemplates.test.ts +190 -21
- package/Tests/Types/Monitor/ProxmoxAlertTemplates.test.ts +352 -22
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationAlertDebuggability.test.ts +363 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +93 -18
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +259 -0
- package/Tests/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.test.ts +476 -0
- package/Tests/Types/Monitor/RumAlertTemplates.test.ts +136 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesDebugHints.test.ts +661 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +507 -0
- package/Tests/Types/Monitor/ServiceAlertTemplates.test.ts +270 -0
- package/Tests/Types/Monitor/TemplateGroupByKeys.test.ts +38 -10
- package/Tests/Types/Monitor/Utils/RecommendationCriteriaAssertions.ts +102 -0
- package/Tests/Types/NotificationSetting/RoutineEmailEvents.test.ts +47 -0
- package/Tests/Types/Rum/SessionReplayApiContracts.test.ts +608 -0
- package/Tests/Types/Rum/SessionReplayCustomEvents.test.ts +434 -0
- package/Tests/Types/WebsiteRequest.test.ts +285 -3
- package/Tests/UI/Components/Charts/ChartBucketIdentity.test.ts +16 -3
- package/Tests/UI/Components/Charts/ChartTrailingBucketGap.test.ts +691 -0
- package/Tests/UI/Components/ComponentsModal.test.tsx +12 -4
- package/Tests/UI/Components/ComponentsModalUsability.test.tsx +518 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetailMapping.test.tsx +324 -0
- package/Tests/UI/Components/CustomFields/MapFromCustomFieldInput.test.tsx +176 -0
- package/Tests/UI/Components/Graphs/DayUptimeGraph.test.tsx +484 -0
- package/Tests/UI/Components/HeaderRightRail.test.tsx +157 -0
- package/Tests/UI/Components/IconDropdownItem.test.tsx +102 -0
- package/Tests/UI/Components/JSONTablePrototypePollution.test.tsx +117 -0
- package/Tests/UI/Components/KeyboardShortcutsModal.test.tsx +214 -0
- package/Tests/UI/Components/ModelTable/ModelTableWrapContent.test.tsx +561 -0
- package/Tests/UI/Components/MonitorGraphs/UptimeBarDayModal.test.tsx +302 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +28 -12
- package/Tests/UI/Components/ShortcutDialogGuard.test.tsx +74 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupSectionAutoExpand.test.tsx +248 -0
- package/Tests/UI/Components/TableCellWrapping.test.tsx +625 -0
- package/Tests/UI/Components/TableLoadingStates.test.tsx +205 -0
- package/Tests/UI/Components/Workflow/NodePlacement.test.ts +148 -0
- package/Tests/UI/Components/Workflow/Workflow.test.tsx +981 -0
- package/Tests/UI/ConfigBrowserTelemetry.test.ts +70 -0
- package/Tests/UI/Rum/ChunkLoader.test.ts +1036 -2
- package/Tests/UI/Rum/FidelityNotices.test.ts +187 -0
- package/Tests/UI/Rum/InactivityMap.test.ts +341 -0
- package/Tests/UI/Rum/PrivacySummaryCard.test.tsx +259 -0
- package/Tests/UI/Rum/RecordingHealthCard.test.tsx +855 -0
- package/Tests/UI/Rum/RecordingHealthStrip.test.tsx +594 -0
- package/Tests/UI/Rum/ReplayCard.test.tsx +451 -0
- package/Tests/UI/Rum/ReplayCorrelationPanel.test.tsx +536 -0
- package/Tests/UI/Rum/ReplayEngine.test.ts +2503 -0
- package/Tests/UI/Rum/ReplayEngineTypes.test.ts +171 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +711 -0
- package/Tests/UI/Rum/ReplayLink.test.tsx +119 -0
- package/Tests/UI/Rum/ReplayPinControl.test.tsx +408 -0
- package/Tests/UI/Rum/ReplayPlaybackIntent.test.ts +152 -0
- package/Tests/UI/Rum/ReplayRail.test.tsx +1296 -0
- package/Tests/UI/Rum/ReplayRailDetail.test.tsx +900 -0
- package/Tests/UI/Rum/ReplayScrubber.test.tsx +763 -147
- package/Tests/UI/Rum/ReplaySignalTypes.test.ts +189 -0
- package/Tests/UI/Rum/ReplaySignals.test.ts +1579 -0
- package/Tests/UI/Rum/ReplayStage.test.tsx +396 -473
- package/Tests/UI/Rum/ReplayStageOverlays.test.tsx +918 -0
- package/Tests/UI/Rum/ReplayTimeline.test.tsx +696 -0
- package/Tests/UI/Rum/SessionReplayEmptyState.test.tsx +499 -0
- package/Tests/UI/Rum/SessionReplaySearchBar.test.tsx +346 -0
- package/Tests/UI/Rum/SessionReplaySetupGuide.test.tsx +545 -0
- package/Tests/UI/Rum/SessionReplayTable.test.tsx +991 -0
- package/Tests/UI/Rum/TargetedCapturePanel.test.tsx +254 -0
- package/Tests/UI/Telemetry/BrowserExporterIsolation.test.ts +71 -0
- package/Tests/UI/Utils/GlobalKeyboardShortcut.test.ts +344 -0
- package/Tests/Utils/API.test.ts +308 -0
- package/Tests/Utils/HTTPResponseBodyReader.test.ts +323 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveryScanStatus.test.ts +117 -0
- package/Tests/Utils/Rum/ChunkMath.test.ts +83 -0
- package/Tests/Utils/Rum/SessionReplayHealthDiagnosis.test.ts +993 -0
- package/Tests/Utils/Rum/SessionReplayStringMap.test.ts +245 -0
- package/Tests/Utils/StatusPage/ResourceSearch.test.ts +631 -0
- package/Tests/Utils/Telemetry/OriginAllowList.test.ts +633 -0
- package/Tests/Utils/Uptime/DayUptimeGraphUtil.test.ts +461 -0
- package/Tests/Utils/ValueFormatter.test.ts +148 -0
- package/Types/CustomField/CustomFieldMappingCatalog.ts +161 -0
- package/Types/CustomField/CustomFieldMappingSourceResource.ts +19 -0
- package/Types/CustomField/CustomFieldValueMapping.ts +288 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/JSONFunctions.ts +189 -43
- package/Types/Monitor/CephAlertTemplates.ts +157 -195
- package/Types/Monitor/DockerAlertTemplates.ts +338 -128
- package/Types/Monitor/DockerSwarmAlertTemplates.ts +95 -112
- package/Types/Monitor/HostAlertTemplates.ts +302 -130
- package/Types/Monitor/IotAlertTemplates.ts +112 -104
- package/Types/Monitor/KubernetesAlertTemplates.ts +415 -224
- package/Types/Monitor/KubernetesMetricCatalog.ts +23 -19
- package/Types/Monitor/PodmanAlertTemplates.ts +116 -163
- package/Types/Monitor/ProxmoxAlertTemplates.ts +217 -99
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +98 -6
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +44 -7
- package/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.ts +383 -0
- package/Types/Monitor/RumAlertTemplates.ts +46 -7
- package/Types/Monitor/SeriesContext/SeriesDebugHints.ts +596 -0
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +554 -0
- package/Types/Monitor/ServiceAlertTemplates.ts +98 -11
- package/Types/Monitor/UptimeHistoryLabels.ts +56 -0
- package/Types/NotificationSetting/RoutineEmailEvents.ts +30 -0
- package/Types/Rum/SessionReplay.ts +209 -0
- package/Types/Rum/SessionReplayApi.ts +722 -0
- package/Types/Rum/SessionReplayCaptureTrigger.ts +21 -12
- package/Types/Rum/SessionReplayConsentMode.ts +11 -7
- package/Types/Rum/SessionReplayCustomEvents.ts +549 -0
- package/Types/Rum/SessionReplayHealth.ts +238 -0
- package/Types/Telemetry/TelemetryIngestSurface.ts +95 -0
- package/Types/Telemetry/TelemetryIngestionKeyPolicy.ts +75 -0
- package/Types/Telemetry/TelemetryIngestionKeyType.ts +29 -0
- package/Types/WebsiteRequest.ts +85 -3
- package/UI/Components/Charts/Area/AreaChart.tsx +13 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +6 -2
- package/UI/Components/Charts/Line/LineChart.tsx +13 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +29 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +22 -3
- package/UI/Components/Charts/Utils/TimeAnnotation.ts +76 -9
- package/UI/Components/Charts/Utils/XAxis.ts +217 -0
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +143 -25
- package/UI/Components/CustomFields/MapFromCustomFieldInput.tsx +216 -0
- package/UI/Components/Graphs/DayUptimeGraph.tsx +173 -8
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +22 -278
- package/UI/Components/Graphs/UptimeDaySummary.tsx +327 -0
- package/UI/Components/Header/Header.tsx +25 -9
- package/UI/Components/Header/IconDropdown/IconDropdownItem.tsx +10 -1
- package/UI/Components/Header/IconDropdown/IconDropdownMenu.tsx +6 -1
- package/UI/Components/Header/ProjectPicker/ProjectPicker.tsx +6 -1
- package/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.tsx +6 -1
- package/UI/Components/Icon/Icon.tsx +11 -0
- package/UI/Components/JSONTable/JSONTable.tsx +2 -2
- package/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.tsx +125 -0
- package/UI/Components/KeyboardShortcut/Screenshots/README.md +19 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-command-palette.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog-dark.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-dialog.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-help-menu.png +0 -0
- package/UI/Components/KeyboardShortcut/Screenshots/keyboard-shortcuts-narrow.png +0 -0
- package/UI/Components/ModelTable/Column.ts +15 -0
- package/UI/Components/Monitor/SeriesDebugCommandsViewer.tsx +64 -0
- package/UI/Components/Monitor/SeriesLabelsViewer.tsx +93 -0
- package/UI/Components/MonitorGraphs/Uptime.tsx +14 -2
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +63 -10
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +44 -0
- package/UI/Components/StatusPage/ResourceGroupSection.tsx +40 -0
- package/UI/Components/Table/CellClassName.ts +81 -0
- package/UI/Components/Table/TableRow.tsx +20 -14
- package/UI/Components/Table/TableSkeletonRows.tsx +9 -9
- package/UI/Components/Table/Types/Column.ts +36 -0
- package/UI/Components/Tooltip/Tooltip.tsx +11 -1
- package/UI/Components/Workflow/ComponentsModal.tsx +60 -39
- package/UI/Components/Workflow/NodePlacement.ts +57 -0
- package/UI/Components/Workflow/Workflow.tsx +47 -18
- package/UI/Config.ts +15 -33
- package/UI/Utils/GlobalKeyboardShortcut.ts +208 -0
- package/UI/Utils/Telemetry/BrowserTelemetryConfig.ts +28 -0
- package/UI/Utils/Telemetry/Telemetry.ts +10 -8
- package/Utils/API.ts +129 -3
- package/Utils/HTTPResponseBodyReader.ts +221 -0
- package/Utils/NetworkDiscovery/DiscoveryScanStatus.ts +77 -0
- package/Utils/Rum/ChunkMath.ts +106 -0
- package/Utils/Rum/SessionReplayHealth.ts +732 -0
- package/Utils/Rum/SessionReplayStringMap.ts +226 -0
- package/Utils/Schema/ModelSchema.ts +1 -0
- package/Utils/StatusPage/ResourceSearch.ts +290 -0
- package/Utils/Telemetry/OriginAllowList.ts +355 -0
- package/Utils/Uptime/DayUptimeGraphUtil.ts +205 -0
- package/Utils/ValueFormatter.ts +35 -1
- package/build/dist/Models/AnalyticsModels/RumSession.js +102 -19
- package/build/dist/Models/AnalyticsModels/RumSession.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js +11 -9
- package/build/dist/Models/AnalyticsModels/RumSessionChunk.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ApiKeyPermission.js +3 -27
- package/build/dist/Models/DatabaseModels/ApiKeyPermission.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/InventoryItemCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +6 -6
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/TeamCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMember.js +0 -9
- package/build/dist/Models/DatabaseModels/TeamMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +79 -3
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamPermission.js +3 -24
- package/build/dist/Models/DatabaseModels/TeamPermission.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js +267 -0
- package/build/dist/Models/DatabaseModels/TelemetryIngestionKey.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTelegram.js +0 -4
- package/build/dist/Models/DatabaseModels/UserTelegram.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +769 -109
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/API/UserNotificationSettingAPI.js +35 -0
- package/build/dist/Server/API/UserNotificationSettingAPI.js.map +1 -0
- package/build/dist/Server/API/UserTelegramAPI.js +28 -6
- package/build/dist/Server/API/UserTelegramAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +29 -2
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js +60 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791300000000-AddTelemetryIngestionKeyType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js +53 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791400000000-SessionReplayRecordEverySessionByDefault.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js +82 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791500000000-WidenCustomFieldDropdownOptions.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js +46 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791600000000-AddCustomFieldValueMapping.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +19 -7
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Infrastructure/Semaphore.js +1 -0
- package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +315 -8
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +1 -0
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AlertCustomFieldService.js +93 -0
- package/build/dist/Server/Services/AlertCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +34 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +331 -28
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/CustomFieldMappingService.js +614 -0
- package/build/dist/Server/Services/CustomFieldMappingService.js.map +1 -0
- package/build/dist/Server/Services/GlobalConfigService.js +133 -0
- package/build/dist/Server/Services/GlobalConfigService.js.map +1 -1
- package/build/dist/Server/Services/IncidentCustomFieldService.js +93 -0
- package/build/dist/Server/Services/IncidentCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +32 -0
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +163 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +29 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +79 -27
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +22 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/RoutineEmailSettingsService.js +69 -0
- package/build/dist/Server/Services/RoutineEmailSettingsService.js.map +1 -0
- package/build/dist/Server/Services/RumSessionReplayViewService.js +86 -24
- package/build/dist/Server/Services/RumSessionReplayViewService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js +93 -0
- package/build/dist/Server/Services/ScheduledMaintenanceCustomFieldService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceService.js +29 -0
- package/build/dist/Server/Services/ScheduledMaintenanceService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +62 -2
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TeamPermissionService.js +166 -3
- package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js +562 -18
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +26 -6
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserTelegramService.js +257 -4
- package/build/dist/Server/Services/UserTelegramService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +2 -2
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js +27 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldDefinitionMappingHooks.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js +226 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingRegistry.js.map +1 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js +189 -0
- package/build/dist/Server/Utils/CustomField/CustomFieldMappingValidator.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +121 -7
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js +38 -5
- package/build/dist/Server/Utils/EmailRollup/EmailRollupConstants.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +44 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/FrontendEnvironment.js +33 -0
- package/build/dist/Server/Utils/FrontendEnvironment.js.map +1 -0
- package/build/dist/Server/Utils/LogRedaction.js +20 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +10 -0
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +287 -68
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +20 -4
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +29 -6
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +88 -13
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +22 -6
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +22 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js +237 -0
- package/build/dist/Server/Utils/Monitor/SeriesContextEnricher.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +136 -9
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js +174 -89
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCache.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js +223 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayHealthCounters.js.map +1 -0
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js +945 -164
- package/build/dist/Server/Utils/SessionReplay/SessionReplayReadService.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +3 -14
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/TelegramVerificationToken.js +129 -0
- package/build/dist/Server/Utils/TelegramVerificationToken.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/PinServiceName.js +163 -0
- package/build/dist/Server/Utils/Telemetry/PinServiceName.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +28 -15
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js +64 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js +171 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryIngestionKeyRateLimiter.js.map +1 -0
- package/build/dist/Server/Utils/VM/VMAPI.js.map +1 -1
- package/build/dist/Server/Utils/VM/VMRunner.js +780 -69
- package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js +60 -0
- package/build/dist/Types/CustomField/CustomFieldMappingCatalog.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js +20 -0
- package/build/dist/Types/CustomField/CustomFieldMappingSourceResource.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldValueMapping.js +141 -0
- package/build/dist/Types/CustomField/CustomFieldValueMapping.js.map +1 -0
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +118 -37
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/Monitor/CephAlertTemplates.js +130 -171
- package/build/dist/Types/Monitor/CephAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DockerAlertTemplates.js +286 -121
- package/build/dist/Types/Monitor/DockerAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js +84 -105
- package/build/dist/Types/Monitor/DockerSwarmAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/HostAlertTemplates.js +250 -122
- package/build/dist/Types/Monitor/HostAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/IotAlertTemplates.js +81 -81
- package/build/dist/Types/Monitor/IotAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js +375 -215
- package/build/dist/Types/Monitor/KubernetesAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js +19 -19
- package/build/dist/Types/Monitor/KubernetesMetricCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js +104 -153
- package/build/dist/Types/Monitor/PodmanAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js +156 -91
- package/build/dist/Types/Monitor/ProxmoxAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +67 -6
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +38 -7
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js +261 -0
- package/build/dist/Types/Monitor/Recommendation/RecommendationCriteriaBuilder.js.map +1 -0
- package/build/dist/Types/Monitor/RumAlertTemplates.js +46 -7
- package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js +422 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js.map +1 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +441 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -0
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js +79 -15
- package/build/dist/Types/Monitor/ServiceAlertTemplates.js.map +1 -1
- package/build/dist/Types/Monitor/UptimeHistoryLabels.js +17 -0
- package/build/dist/Types/Monitor/UptimeHistoryLabels.js.map +1 -0
- package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js +29 -0
- package/build/dist/Types/NotificationSetting/RoutineEmailEvents.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplay.js +117 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayApi.js +181 -0
- package/build/dist/Types/Rum/SessionReplayApi.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js +21 -12
- package/build/dist/Types/Rum/SessionReplayCaptureTrigger.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayConsentMode.js +11 -7
- package/build/dist/Types/Rum/SessionReplayConsentMode.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js +172 -0
- package/build/dist/Types/Rum/SessionReplayCustomEvents.js.map +1 -0
- package/build/dist/Types/Rum/SessionReplayHealth.js +37 -0
- package/build/dist/Types/Rum/SessionReplayHealth.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +85 -0
- package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js +15 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyPolicy.js.map +1 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js +30 -0
- package/build/dist/Types/Telemetry/TelemetryIngestionKeyType.js.map +1 -0
- package/build/dist/Types/WebsiteRequest.js +55 -3
- package/build/dist/Types/WebsiteRequest.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +13 -2
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +6 -2
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +13 -2
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Types/XAxis/XAxis.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +15 -3
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js +40 -9
- package/build/dist/UI/Components/Charts/Utils/TimeAnnotation.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/XAxis.js +186 -0
- package/build/dist/UI/Components/Charts/Utils/XAxis.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +80 -6
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js +118 -0
- package/build/dist/UI/Components/CustomFields/MapFromCustomFieldInput.js.map +1 -0
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +112 -7
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +5 -162
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js +181 -0
- package/build/dist/UI/Components/Graphs/UptimeDaySummary.js.map +1 -0
- package/build/dist/UI/Components/Header/Header.js +2 -3
- package/build/dist/UI/Components/Header/Header.js.map +1 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js +2 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownItem.js.map +1 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js +7 -1
- package/build/dist/UI/Components/Header/IconDropdown/IconDropdownMenu.js.map +1 -1
- package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js +7 -1
- package/build/dist/UI/Components/Header/ProjectPicker/ProjectPicker.js.map +1 -1
- package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js +7 -1
- package/build/dist/UI/Components/HeaderAlert/NotificationBell/NotificationBellDropdown.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +5 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/JSONTable/JSONTable.js +2 -2
- package/build/dist/UI/Components/JSONTable/JSONTable.js.map +1 -1
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js +35 -0
- package/build/dist/UI/Components/KeyboardShortcut/KeyboardShortcutsModal.js.map +1 -0
- package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js +34 -0
- package/build/dist/UI/Components/Monitor/SeriesDebugCommandsViewer.js.map +1 -0
- package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js +49 -0
- package/build/dist/UI/Components/Monitor/SeriesLabelsViewer.js.map +1 -0
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +32 -6
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +38 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js +22 -1
- package/build/dist/UI/Components/StatusPage/ResourceGroupSection.js.map +1 -1
- package/build/dist/UI/Components/Table/CellClassName.js +55 -0
- package/build/dist/UI/Components/Table/CellClassName.js.map +1 -0
- package/build/dist/UI/Components/Table/TableRow.js +16 -13
- package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
- package/build/dist/UI/Components/Table/TableSkeletonRows.js +9 -8
- package/build/dist/UI/Components/Table/TableSkeletonRows.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +25 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +47 -30
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/NodePlacement.js +37 -0
- package/build/dist/UI/Components/Workflow/NodePlacement.js.map +1 -0
- package/build/dist/UI/Components/Workflow/Workflow.js +35 -18
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Config.js +10 -20
- package/build/dist/UI/Config.js.map +1 -1
- package/build/dist/UI/Utils/GlobalKeyboardShortcut.js +138 -0
- package/build/dist/UI/Utils/GlobalKeyboardShortcut.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js +14 -0
- package/build/dist/UI/Utils/Telemetry/BrowserTelemetryConfig.js.map +1 -0
- package/build/dist/UI/Utils/Telemetry/Telemetry.js +7 -5
- package/build/dist/UI/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Utils/API.js +67 -5
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/HTTPResponseBodyReader.js +157 -0
- package/build/dist/Utils/HTTPResponseBodyReader.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js +69 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js.map +1 -0
- package/build/dist/Utils/Rum/ChunkMath.js +76 -0
- package/build/dist/Utils/Rum/ChunkMath.js.map +1 -1
- package/build/dist/Utils/Rum/SessionReplayHealth.js +534 -0
- package/build/dist/Utils/Rum/SessionReplayHealth.js.map +1 -0
- package/build/dist/Utils/Rum/SessionReplayStringMap.js +165 -0
- package/build/dist/Utils/Rum/SessionReplayStringMap.js.map +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js +1 -0
- package/build/dist/Utils/Schema/ModelSchema.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceSearch.js +200 -0
- package/build/dist/Utils/StatusPage/ResourceSearch.js.map +1 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js +299 -0
- package/build/dist/Utils/Telemetry/OriginAllowList.js.map +1 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js +147 -0
- package/build/dist/Utils/Uptime/DayUptimeGraphUtil.js.map +1 -0
- package/build/dist/Utils/ValueFormatter.js +32 -1
- package/build/dist/Utils/ValueFormatter.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import VMRunner from "../../../../Server/Utils/VM/VMRunner";
|
|
2
2
|
import ReturnResult from "../../../../Types/IsolatedVM/ReturnResult";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
afterAll,
|
|
5
|
+
afterEach,
|
|
6
|
+
beforeAll,
|
|
7
|
+
beforeEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
jest,
|
|
11
|
+
test,
|
|
12
|
+
} from "@jest/globals";
|
|
13
|
+
import axios from "axios";
|
|
14
|
+
import dns from "dns";
|
|
15
|
+
import http from "http";
|
|
16
|
+
import https from "https";
|
|
17
|
+
import { AddressInfo } from "net";
|
|
18
|
+
import { gzipSync } from "zlib";
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Common's jsdom resolver selects Axios's browser bundle. VMRunner is a
|
|
22
|
+
* server-only utility, so use Axios's Node bundle here; otherwise forcing the
|
|
23
|
+
* `http` adapter below only finds a browser-build placeholder and the stream
|
|
24
|
+
* and socket tests never reach production transport code.
|
|
25
|
+
*/
|
|
26
|
+
jest.mock("axios", () => {
|
|
27
|
+
return jest.requireActual("axios/dist/node/axios.cjs");
|
|
28
|
+
});
|
|
4
29
|
|
|
5
30
|
/*
|
|
6
31
|
* GHSA-v5xh-rw9h-77fv fixed the API workflow components, but the Custom
|
|
@@ -18,10 +43,95 @@ import { describe, expect, jest, test } from "@jest/globals";
|
|
|
18
43
|
|
|
19
44
|
jest.setTimeout(60000);
|
|
20
45
|
|
|
21
|
-
|
|
46
|
+
interface SandboxPrivateNetworkOptions {
|
|
47
|
+
allowPrivateNetworkRequests?: boolean | undefined;
|
|
48
|
+
privateNetworkAccessIsAllowed?: boolean | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface RunningLoopbackServer {
|
|
52
|
+
port: number;
|
|
53
|
+
requestCount: () => number;
|
|
54
|
+
close: () => Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type LoopbackRequestHandler = (
|
|
58
|
+
request: http.IncomingMessage,
|
|
59
|
+
response: http.ServerResponse,
|
|
60
|
+
) => void;
|
|
61
|
+
|
|
62
|
+
type ValidationLookupSpy = jest.SpiedFunction<
|
|
63
|
+
(
|
|
64
|
+
hostname: string,
|
|
65
|
+
options: { all: true },
|
|
66
|
+
) => Promise<Array<{ address: string; family: number }>>
|
|
67
|
+
>;
|
|
68
|
+
|
|
69
|
+
interface SocketLookupSpy {
|
|
70
|
+
mockImplementation: (
|
|
71
|
+
implementation: (...args: Array<unknown>) => void,
|
|
72
|
+
) => unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function startLoopbackServer(
|
|
76
|
+
handler: LoopbackRequestHandler = (
|
|
77
|
+
_request: http.IncomingMessage,
|
|
78
|
+
response: http.ServerResponse,
|
|
79
|
+
): void => {
|
|
80
|
+
response.end("loopback sentinel");
|
|
81
|
+
},
|
|
82
|
+
): Promise<RunningLoopbackServer> {
|
|
83
|
+
let receivedRequests: number = 0;
|
|
84
|
+
const server: http.Server = http.createServer(
|
|
85
|
+
(request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
86
|
+
receivedRequests += 1;
|
|
87
|
+
handler(request, response);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
await new Promise<void>(
|
|
92
|
+
(resolve: () => void, reject: (error: Error) => void): void => {
|
|
93
|
+
const onError: (error: Error) => void = (error: Error): void => {
|
|
94
|
+
reject(error);
|
|
95
|
+
};
|
|
96
|
+
server.once("error", onError);
|
|
97
|
+
server.listen(0, "127.0.0.1", (): void => {
|
|
98
|
+
server.off("error", onError);
|
|
99
|
+
resolve();
|
|
100
|
+
});
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const address: AddressInfo = server.address() as AddressInfo;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
port: address.port,
|
|
108
|
+
requestCount: (): number => {
|
|
109
|
+
return receivedRequests;
|
|
110
|
+
},
|
|
111
|
+
close: async (): Promise<void> => {
|
|
112
|
+
await new Promise<void>(
|
|
113
|
+
(resolve: () => void, reject: (error: Error) => void): void => {
|
|
114
|
+
server.close((error?: Error): void => {
|
|
115
|
+
if (error) {
|
|
116
|
+
reject(error);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
resolve();
|
|
120
|
+
});
|
|
121
|
+
server.closeAllConnections();
|
|
122
|
+
},
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function runInSandbox(
|
|
129
|
+
code: string,
|
|
130
|
+
privateNetworkOptions: SandboxPrivateNetworkOptions = {},
|
|
131
|
+
): Promise<ReturnResult> {
|
|
22
132
|
return VMRunner.runCodeInSandbox({
|
|
23
133
|
code,
|
|
24
|
-
options: { timeout: 15000 },
|
|
134
|
+
options: { timeout: 15000, ...privateNetworkOptions },
|
|
25
135
|
});
|
|
26
136
|
}
|
|
27
137
|
|
|
@@ -30,9 +140,15 @@ async function runInSandbox(code: string): Promise<ReturnResult> {
|
|
|
30
140
|
* rejects or the returned result carries the message - accept both, and assert
|
|
31
141
|
* on the SSRF wording so a mere "connection refused" cannot pass for a block.
|
|
32
142
|
*/
|
|
33
|
-
async function errorFromSandbox(
|
|
143
|
+
async function errorFromSandbox(
|
|
144
|
+
code: string,
|
|
145
|
+
privateNetworkOptions: SandboxPrivateNetworkOptions = {},
|
|
146
|
+
): Promise<string> {
|
|
34
147
|
try {
|
|
35
|
-
const result: ReturnResult = await runInSandbox(
|
|
148
|
+
const result: ReturnResult = await runInSandbox(
|
|
149
|
+
code,
|
|
150
|
+
privateNetworkOptions,
|
|
151
|
+
);
|
|
36
152
|
if (result.scriptError) {
|
|
37
153
|
return result.scriptError.message;
|
|
38
154
|
}
|
|
@@ -45,6 +161,64 @@ async function errorFromSandbox(code: string): Promise<string> {
|
|
|
45
161
|
const REFUSAL: RegExp =
|
|
46
162
|
/not allowed|private, loopback, or link-local|absolute http|could not be resolved/i;
|
|
47
163
|
|
|
164
|
+
const ALLOW_PRIVATE_NETWORK_WEBHOOKS_ENV: string =
|
|
165
|
+
"ALLOW_PRIVATE_NETWORK_WEBHOOKS";
|
|
166
|
+
const PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV: string =
|
|
167
|
+
"PRIVATE_NETWORK_WEBHOOK_ALLOWLIST";
|
|
168
|
+
const originalAllowPrivateNetworkWebhooks: string | undefined =
|
|
169
|
+
process.env[ALLOW_PRIVATE_NETWORK_WEBHOOKS_ENV];
|
|
170
|
+
const originalPrivateNetworkWebhookAllowlist: string | undefined =
|
|
171
|
+
process.env[PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV];
|
|
172
|
+
const originalAxiosAdapter: typeof axios.defaults.adapter =
|
|
173
|
+
axios.defaults.adapter;
|
|
174
|
+
|
|
175
|
+
beforeAll(() => {
|
|
176
|
+
/*
|
|
177
|
+
* Common's default Jest environment is jsdom, where Axios selects XHR and
|
|
178
|
+
* CORS blocks the loopback fixtures before VMRunner's Node transport runs.
|
|
179
|
+
* Production VMRunner runs in Node, so force Axios's Node adapter for this
|
|
180
|
+
* server-side integration suite.
|
|
181
|
+
*/
|
|
182
|
+
axios.defaults.adapter = "http";
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
beforeEach(() => {
|
|
186
|
+
delete process.env[ALLOW_PRIVATE_NETWORK_WEBHOOKS_ENV];
|
|
187
|
+
delete process.env[PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV];
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
afterEach(() => {
|
|
191
|
+
jest.restoreAllMocks();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
afterAll(() => {
|
|
195
|
+
if (originalAxiosAdapter === undefined) {
|
|
196
|
+
delete axios.defaults.adapter;
|
|
197
|
+
} else {
|
|
198
|
+
axios.defaults.adapter = originalAxiosAdapter;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const restore: (key: string, value: string | undefined) => void = (
|
|
202
|
+
key: string,
|
|
203
|
+
value: string | undefined,
|
|
204
|
+
): void => {
|
|
205
|
+
if (value === undefined) {
|
|
206
|
+
delete process.env[key];
|
|
207
|
+
} else {
|
|
208
|
+
process.env[key] = value;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
restore(
|
|
213
|
+
ALLOW_PRIVATE_NETWORK_WEBHOOKS_ENV,
|
|
214
|
+
originalAllowPrivateNetworkWebhooks,
|
|
215
|
+
);
|
|
216
|
+
restore(
|
|
217
|
+
PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV,
|
|
218
|
+
originalPrivateNetworkWebhookAllowlist,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
48
222
|
describe("VMRunner sandbox axios bridge — SSRF guard", () => {
|
|
49
223
|
const internalTargets: Array<[string, string]> = [
|
|
50
224
|
["AWS metadata endpoint", "http://169.254.169.254/latest/meta-data/"],
|
|
@@ -131,7 +305,7 @@ describe("VMRunner sandbox axios bridge — SSRF guard", () => {
|
|
|
131
305
|
/*
|
|
132
306
|
* A proxy or a unix socket steers the connection somewhere the validated URL
|
|
133
307
|
* never named — /var/run/docker.sock being the memorable one — so the guard
|
|
134
|
-
* strips them rather than trusting the URL alone.
|
|
308
|
+
* disables or strips them rather than trusting the URL alone.
|
|
135
309
|
*/
|
|
136
310
|
test("a proxy in the config cannot steer a public URL at an internal host", async () => {
|
|
137
311
|
/*
|
|
@@ -145,6 +319,33 @@ describe("VMRunner sandbox axios bridge — SSRF guard", () => {
|
|
|
145
319
|
expect(message).not.toMatch(/169\.254\.169\.254/);
|
|
146
320
|
});
|
|
147
321
|
|
|
322
|
+
test("an environment proxy cannot become an unvalidated connection path", async () => {
|
|
323
|
+
const originalHttpProxy: string | undefined = process.env["HTTP_PROXY"];
|
|
324
|
+
const originalHttpsProxy: string | undefined = process.env["HTTPS_PROXY"];
|
|
325
|
+
|
|
326
|
+
process.env["HTTP_PROXY"] = "http://169.254.169.254:80";
|
|
327
|
+
process.env["HTTPS_PROXY"] = "http://169.254.169.254:80";
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const message: string = await errorFromSandbox(
|
|
331
|
+
`try { return await axios.get('http://8.8.8.8/', { timeout: 1 }); } catch (e) { return 'threw: ' + e.message; }`,
|
|
332
|
+
);
|
|
333
|
+
expect(message).not.toMatch(/169\.254\.169\.254/);
|
|
334
|
+
} finally {
|
|
335
|
+
if (originalHttpProxy === undefined) {
|
|
336
|
+
delete process.env["HTTP_PROXY"];
|
|
337
|
+
} else {
|
|
338
|
+
process.env["HTTP_PROXY"] = originalHttpProxy;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (originalHttpsProxy === undefined) {
|
|
342
|
+
delete process.env["HTTPS_PROXY"];
|
|
343
|
+
} else {
|
|
344
|
+
process.env["HTTPS_PROXY"] = originalHttpsProxy;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
148
349
|
test("still lets an ordinary public URL past the guard", async () => {
|
|
149
350
|
/*
|
|
150
351
|
* 8.8.8.8 is never actually reached - the 1ms timeout sees to that. The
|
|
@@ -158,6 +359,948 @@ describe("VMRunner sandbox axios bridge — SSRF guard", () => {
|
|
|
158
359
|
});
|
|
159
360
|
});
|
|
160
361
|
|
|
362
|
+
describe("VMRunner sandbox axios bridge — pinned canonical dispatch", () => {
|
|
363
|
+
async function expectNoLoopbackRequest(
|
|
364
|
+
buildCode: (port: number) => string,
|
|
365
|
+
): Promise<void> {
|
|
366
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer();
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const result: ReturnResult = await runInSandbox(
|
|
370
|
+
buildCode(loopbackServer.port),
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
expect(result.returnValue).not.toBe("loopback sentinel");
|
|
374
|
+
expect(loopbackServer.requestCount()).toBe(0);
|
|
375
|
+
} finally {
|
|
376
|
+
await loopbackServer.close();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
test("pins the address from validation instead of performing a second DNS lookup", async () => {
|
|
381
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer();
|
|
382
|
+
const validationLookupSpy: ValidationLookupSpy = jest.spyOn(
|
|
383
|
+
dns.promises,
|
|
384
|
+
"lookup",
|
|
385
|
+
) as unknown as ValidationLookupSpy;
|
|
386
|
+
validationLookupSpy.mockResolvedValue([{ address: "8.8.8.8", family: 4 }]);
|
|
387
|
+
|
|
388
|
+
const socketLookupSpy: SocketLookupSpy = jest.spyOn(
|
|
389
|
+
dns,
|
|
390
|
+
"lookup",
|
|
391
|
+
) as unknown as SocketLookupSpy;
|
|
392
|
+
socketLookupSpy.mockImplementation((...args: Array<unknown>): void => {
|
|
393
|
+
const lookupOptions: { all?: boolean } | undefined =
|
|
394
|
+
args.length > 2 && typeof args[1] === "object"
|
|
395
|
+
? (args[1] as { all?: boolean })
|
|
396
|
+
: undefined;
|
|
397
|
+
const callback: (
|
|
398
|
+
error: NodeJS.ErrnoException | null,
|
|
399
|
+
address: string | Array<{ address: string; family: number }>,
|
|
400
|
+
family?: number,
|
|
401
|
+
) => void = args[args.length - 1] as (
|
|
402
|
+
error: NodeJS.ErrnoException | null,
|
|
403
|
+
address: string | Array<{ address: string; family: number }>,
|
|
404
|
+
family?: number,
|
|
405
|
+
) => void;
|
|
406
|
+
|
|
407
|
+
queueMicrotask((): void => {
|
|
408
|
+
if (lookupOptions?.all) {
|
|
409
|
+
callback(null, [{ address: "127.0.0.1", family: 4 }]);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
callback(null, "127.0.0.1", 4);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
const result: ReturnResult = await runInSandbox(`
|
|
418
|
+
try {
|
|
419
|
+
const response = await axios.get(
|
|
420
|
+
'http://rebind.invalid:${loopbackServer.port}/secret',
|
|
421
|
+
{
|
|
422
|
+
timeout: 100,
|
|
423
|
+
httpVersion: 2,
|
|
424
|
+
http2Options: { sessionTimeout: 10000 }
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
return response.data;
|
|
428
|
+
} catch (error) {
|
|
429
|
+
return 'transport failed: ' + error.message;
|
|
430
|
+
}
|
|
431
|
+
`);
|
|
432
|
+
|
|
433
|
+
expect(result.returnValue).not.toBe("loopback sentinel");
|
|
434
|
+
expect(validationLookupSpy).toHaveBeenCalledTimes(1);
|
|
435
|
+
expect(socketLookupSpy).not.toHaveBeenCalled();
|
|
436
|
+
expect(loopbackServer.requestCount()).toBe(0);
|
|
437
|
+
} finally {
|
|
438
|
+
await loopbackServer.close();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test("drops destination-steering fields from a serialized HTTP Agent", async () => {
|
|
443
|
+
await expectNoLoopbackRequest((port: number): string => {
|
|
444
|
+
return `
|
|
445
|
+
try {
|
|
446
|
+
const response = await axios.get(
|
|
447
|
+
'http://8.8.8.8:${port}/approved',
|
|
448
|
+
{
|
|
449
|
+
timeout: 100,
|
|
450
|
+
httpAgent: new http.Agent({
|
|
451
|
+
host: '127.0.0.1',
|
|
452
|
+
port: ${port},
|
|
453
|
+
keepAlive: true
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
);
|
|
457
|
+
return response.data;
|
|
458
|
+
} catch (error) {
|
|
459
|
+
return 'transport failed: ' + error.message;
|
|
460
|
+
}
|
|
461
|
+
`;
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test("preserves TLS-only servername while replacing the HTTPS agent transport", async () => {
|
|
466
|
+
process.env[PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV] = "127.0.0.1";
|
|
467
|
+
let observedServername: string | undefined;
|
|
468
|
+
jest
|
|
469
|
+
.spyOn(https.Agent.prototype, "createConnection")
|
|
470
|
+
.mockImplementation(((options: {
|
|
471
|
+
servername?: string | undefined;
|
|
472
|
+
}): never => {
|
|
473
|
+
observedServername = options.servername;
|
|
474
|
+
throw new Error("TLS connection sentinel");
|
|
475
|
+
}) as never);
|
|
476
|
+
|
|
477
|
+
const result: ReturnResult = await runInSandbox(
|
|
478
|
+
`
|
|
479
|
+
try {
|
|
480
|
+
await axios.get('https://127.0.0.1/sni', {
|
|
481
|
+
httpsAgent: new https.Agent({
|
|
482
|
+
rejectUnauthorized: false,
|
|
483
|
+
servername: 'tenant.internal.example'
|
|
484
|
+
})
|
|
485
|
+
});
|
|
486
|
+
return 'unexpected success';
|
|
487
|
+
} catch (error) {
|
|
488
|
+
return error.message;
|
|
489
|
+
}
|
|
490
|
+
`,
|
|
491
|
+
{
|
|
492
|
+
allowPrivateNetworkRequests: true,
|
|
493
|
+
},
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
expect(result.scriptError).toBeUndefined();
|
|
497
|
+
expect(result.returnValue).toMatch(/TLS connection sentinel/i);
|
|
498
|
+
expect(observedServername).toBe("tenant.internal.example");
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("an empty positional URL wins over config.url and resolves to baseURL", async () => {
|
|
502
|
+
process.env[PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV] = "127.0.0.1";
|
|
503
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
504
|
+
(request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
505
|
+
response.end(request.url);
|
|
506
|
+
},
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
const result: ReturnResult = await runInSandbox(
|
|
511
|
+
`
|
|
512
|
+
const response = await axios.get('', {
|
|
513
|
+
url: 'http://169.254.169.254/ignored',
|
|
514
|
+
baseURL: 'http://127.0.0.1:${loopbackServer.port}/from-base-url'
|
|
515
|
+
});
|
|
516
|
+
return response.data;
|
|
517
|
+
`,
|
|
518
|
+
{
|
|
519
|
+
allowPrivateNetworkRequests: true,
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
expect(result.scriptError).toBeUndefined();
|
|
524
|
+
expect(result.returnValue).toBe("/from-base-url");
|
|
525
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
526
|
+
} finally {
|
|
527
|
+
await loopbackServer.close();
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
test("allowAbsoluteUrls false combines baseURL with a direct-method absolute URL", async () => {
|
|
532
|
+
process.env[PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV] = "127.0.0.1";
|
|
533
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
534
|
+
(request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
535
|
+
response.end(request.url);
|
|
536
|
+
},
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const result: ReturnResult = await runInSandbox(
|
|
541
|
+
`
|
|
542
|
+
const response = await axios.get(
|
|
543
|
+
'http://169.254.169.254/metadata',
|
|
544
|
+
{
|
|
545
|
+
baseURL: 'http://127.0.0.1:${loopbackServer.port}/base',
|
|
546
|
+
allowAbsoluteUrls: false
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
return response.data;
|
|
550
|
+
`,
|
|
551
|
+
{
|
|
552
|
+
allowPrivateNetworkRequests: true,
|
|
553
|
+
},
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
expect(result.scriptError).toBeUndefined();
|
|
557
|
+
expect(result.returnValue).toBe("/base/http://169.254.169.254/metadata");
|
|
558
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
559
|
+
} finally {
|
|
560
|
+
await loopbackServer.close();
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("allowAbsoluteUrls false combines baseURL with request().url", async () => {
|
|
565
|
+
process.env[PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV] = "127.0.0.1";
|
|
566
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
567
|
+
(request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
568
|
+
response.end(request.url);
|
|
569
|
+
},
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
const result: ReturnResult = await runInSandbox(
|
|
574
|
+
`
|
|
575
|
+
const response = await axios.request({
|
|
576
|
+
method: 'get',
|
|
577
|
+
url: 'http://169.254.169.254/metadata',
|
|
578
|
+
baseURL: 'http://127.0.0.1:${loopbackServer.port}/base',
|
|
579
|
+
allowAbsoluteUrls: false
|
|
580
|
+
});
|
|
581
|
+
return response.data;
|
|
582
|
+
`,
|
|
583
|
+
{
|
|
584
|
+
allowPrivateNetworkRequests: true,
|
|
585
|
+
},
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
expect(result.scriptError).toBeUndefined();
|
|
589
|
+
expect(result.returnValue).toBe("/base/http://169.254.169.254/metadata");
|
|
590
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
591
|
+
} finally {
|
|
592
|
+
await loopbackServer.close();
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
describe("VMRunner sandbox axios bridge — private network policy", () => {
|
|
598
|
+
const rfc1918Request: string = `
|
|
599
|
+
try {
|
|
600
|
+
await axios.get('http://10.0.0.5/internal', { timeout: 1 });
|
|
601
|
+
return 'request reached transport';
|
|
602
|
+
} catch (error) {
|
|
603
|
+
return 'request failed: ' + error.message;
|
|
604
|
+
}
|
|
605
|
+
`;
|
|
606
|
+
|
|
607
|
+
const incompletePolicies: Array<[string, SandboxPrivateNetworkOptions]> = [
|
|
608
|
+
["neither flag", {}],
|
|
609
|
+
["eligibility only", { allowPrivateNetworkRequests: true }],
|
|
610
|
+
["trusted policy only", { privateNetworkAccessIsAllowed: true }],
|
|
611
|
+
[
|
|
612
|
+
"a false trusted policy",
|
|
613
|
+
{
|
|
614
|
+
allowPrivateNetworkRequests: true,
|
|
615
|
+
privateNetworkAccessIsAllowed: false,
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
];
|
|
619
|
+
|
|
620
|
+
test.each(incompletePolicies)(
|
|
621
|
+
"refuses RFC1918 with %s",
|
|
622
|
+
async (
|
|
623
|
+
_label: string,
|
|
624
|
+
policy: SandboxPrivateNetworkOptions,
|
|
625
|
+
): Promise<void> => {
|
|
626
|
+
const message: string = await errorFromSandbox(rfc1918Request, policy);
|
|
627
|
+
|
|
628
|
+
expect(message).toMatch(REFUSAL);
|
|
629
|
+
},
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
test("allows RFC1918 past the guard only when both flags are true", async () => {
|
|
633
|
+
const message: string = await errorFromSandbox(rfc1918Request, {
|
|
634
|
+
allowPrivateNetworkRequests: true,
|
|
635
|
+
privateNetworkAccessIsAllowed: true,
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
expect(message).not.toMatch(REFUSAL);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("an unaware workflow caller retains webhook-config behavior", async () => {
|
|
642
|
+
process.env[ALLOW_PRIVATE_NETWORK_WEBHOOKS_ENV] = "true";
|
|
643
|
+
|
|
644
|
+
const message: string = await errorFromSandbox(rfc1918Request, {
|
|
645
|
+
allowPrivateNetworkRequests: true,
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
expect(message).not.toMatch(REFUSAL);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const alwaysForbiddenTargets: Array<[string, string]> = [
|
|
652
|
+
["link-local metadata", "http://169.254.169.254/latest/meta-data/"],
|
|
653
|
+
["Alibaba metadata", "http://100.100.100.200/latest/meta-data/"],
|
|
654
|
+
["Azure WireServer", "http://168.63.129.16/machine/?comp=goalstate"],
|
|
655
|
+
["Oracle Cloud metadata", "http://192.0.0.192/opc/v2/instance/"],
|
|
656
|
+
["AWS IPv6 services", "http://[fd00:ec2::254]/latest/meta-data/"],
|
|
657
|
+
["Google IPv6 metadata", "http://[fd20:ce::254]/computeMetadata/v1/"],
|
|
658
|
+
["RFC 8215 translation", "http://[64:ff9b:1::1]/"],
|
|
659
|
+
["legacy SIIT translation", "http://[0:0:0:0:ffff:0:7f00:1]/"],
|
|
660
|
+
];
|
|
661
|
+
|
|
662
|
+
test.each(alwaysForbiddenTargets)(
|
|
663
|
+
"still refuses %s when both private-network flags are true",
|
|
664
|
+
async (_label: string, url: string): Promise<void> => {
|
|
665
|
+
const message: string = await errorFromSandbox(
|
|
666
|
+
`return await axios.get(${JSON.stringify(url)});`,
|
|
667
|
+
{
|
|
668
|
+
allowPrivateNetworkRequests: true,
|
|
669
|
+
privateNetworkAccessIsAllowed: true,
|
|
670
|
+
},
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
expect(message).toMatch(REFUSAL);
|
|
674
|
+
},
|
|
675
|
+
);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
describe("VMRunner sandbox axios bridge — bounded host I/O", () => {
|
|
679
|
+
const allowLoopbackForWorkflow: SandboxPrivateNetworkOptions = {
|
|
680
|
+
allowPrivateNetworkRequests: true,
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
beforeEach(() => {
|
|
684
|
+
process.env[PRIVATE_NETWORK_WEBHOOK_ALLOWLIST_ENV] = "127.0.0.1";
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
test("counts decompressed response bytes and destroys an oversized gzip stream", async () => {
|
|
688
|
+
const expandedBody: Buffer = Buffer.alloc(10 * 1024 * 1024 + 1, "a");
|
|
689
|
+
const compressedBody: Buffer = gzipSync(expandedBody);
|
|
690
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
691
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
692
|
+
response.writeHead(200, {
|
|
693
|
+
"content-encoding": "gzip",
|
|
694
|
+
"content-length": compressedBody.length,
|
|
695
|
+
"content-type": "text/plain",
|
|
696
|
+
});
|
|
697
|
+
response.end(compressedBody);
|
|
698
|
+
},
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
try {
|
|
702
|
+
const message: string = await errorFromSandbox(
|
|
703
|
+
`return await axios.get('http://127.0.0.1:${loopbackServer.port}/gzip');`,
|
|
704
|
+
allowLoopbackForWorkflow,
|
|
705
|
+
);
|
|
706
|
+
|
|
707
|
+
expect(message).toMatch(/exceeded the allowed size/i);
|
|
708
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
709
|
+
} finally {
|
|
710
|
+
await loopbackServer.close();
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
test("shares one cumulative response-byte budget across sandbox requests", async () => {
|
|
715
|
+
const responseBody: Buffer = Buffer.alloc(6 * 1024 * 1024, "a");
|
|
716
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
717
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
718
|
+
response.writeHead(200, {
|
|
719
|
+
"content-length": responseBody.length,
|
|
720
|
+
"content-type": "text/plain",
|
|
721
|
+
});
|
|
722
|
+
response.end(responseBody);
|
|
723
|
+
},
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
try {
|
|
727
|
+
const message: string = await errorFromSandbox(
|
|
728
|
+
`
|
|
729
|
+
await axios.get('http://127.0.0.1:${loopbackServer.port}/first');
|
|
730
|
+
return await axios.get('http://127.0.0.1:${loopbackServer.port}/second');
|
|
731
|
+
`,
|
|
732
|
+
allowLoopbackForWorkflow,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
expect(message).toMatch(/exceeded the allowed size/i);
|
|
736
|
+
expect(loopbackServer.requestCount()).toBe(2);
|
|
737
|
+
} finally {
|
|
738
|
+
await loopbackServer.close();
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
test("rejects an oversized serialized request before host parsing or socket I/O", async () => {
|
|
743
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer();
|
|
744
|
+
|
|
745
|
+
try {
|
|
746
|
+
const result: ReturnResult = await runInSandbox(
|
|
747
|
+
`
|
|
748
|
+
try {
|
|
749
|
+
await axios.post(
|
|
750
|
+
'http://127.0.0.1:${loopbackServer.port}/upload',
|
|
751
|
+
{ payload: 'x'.repeat(10 * 1024 * 1024 + 1) }
|
|
752
|
+
);
|
|
753
|
+
return 'request sent';
|
|
754
|
+
} catch (error) {
|
|
755
|
+
return 'request refused: ' + error.message;
|
|
756
|
+
}
|
|
757
|
+
`,
|
|
758
|
+
allowLoopbackForWorkflow,
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
expect(result.returnValue).toMatch(/request refused:/i);
|
|
762
|
+
expect(result.returnValue).toMatch(
|
|
763
|
+
/request body exceeded|maxbodylength|larger than|max body/i,
|
|
764
|
+
);
|
|
765
|
+
expect(loopbackServer.requestCount()).toBe(0);
|
|
766
|
+
} finally {
|
|
767
|
+
await loopbackServer.close();
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("caps cumulative serialized request bytes across one sandbox execution", async () => {
|
|
772
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
773
|
+
(request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
774
|
+
request.resume();
|
|
775
|
+
request.on("end", (): void => {
|
|
776
|
+
response.setHeader("content-type", "application/json");
|
|
777
|
+
response.end('{"ok":true}');
|
|
778
|
+
});
|
|
779
|
+
},
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
try {
|
|
783
|
+
const result: ReturnResult = await runInSandbox(
|
|
784
|
+
`
|
|
785
|
+
const body = { payload: 'x'.repeat(6 * 1024 * 1024) };
|
|
786
|
+
await axios.post(
|
|
787
|
+
'http://127.0.0.1:${loopbackServer.port}/first',
|
|
788
|
+
body
|
|
789
|
+
);
|
|
790
|
+
try {
|
|
791
|
+
await axios.post(
|
|
792
|
+
'http://127.0.0.1:${loopbackServer.port}/second',
|
|
793
|
+
body
|
|
794
|
+
);
|
|
795
|
+
return 'second request sent';
|
|
796
|
+
} catch (error) {
|
|
797
|
+
return 'second request refused: ' + error.message;
|
|
798
|
+
}
|
|
799
|
+
`,
|
|
800
|
+
allowLoopbackForWorkflow,
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
expect(result.scriptError).toBeUndefined();
|
|
804
|
+
expect(result.returnValue).toMatch(/cumulative request size/i);
|
|
805
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
806
|
+
} finally {
|
|
807
|
+
await loopbackServer.close();
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
test("returns arraybuffer data without decimal-array serialization amplification", async () => {
|
|
812
|
+
const binaryBody: Buffer = Buffer.from([0, 255, 1, 254]);
|
|
813
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
814
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
815
|
+
response.writeHead(200, {
|
|
816
|
+
"content-length": binaryBody.length,
|
|
817
|
+
"content-type": "application/octet-stream",
|
|
818
|
+
});
|
|
819
|
+
response.end(binaryBody);
|
|
820
|
+
},
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
try {
|
|
824
|
+
const result: ReturnResult = await runInSandbox(
|
|
825
|
+
`
|
|
826
|
+
const response = await axios.get(
|
|
827
|
+
'http://127.0.0.1:${loopbackServer.port}/binary',
|
|
828
|
+
{ responseType: 'arraybuffer' }
|
|
829
|
+
);
|
|
830
|
+
return {
|
|
831
|
+
type: response.data.constructor.name,
|
|
832
|
+
length: response.data.length,
|
|
833
|
+
bytes: Array.from(response.data)
|
|
834
|
+
};
|
|
835
|
+
`,
|
|
836
|
+
allowLoopbackForWorkflow,
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
expect(result.scriptError).toBeUndefined();
|
|
840
|
+
expect(result.returnValue).toEqual({
|
|
841
|
+
type: "Uint8Array",
|
|
842
|
+
length: 4,
|
|
843
|
+
bytes: [0, 255, 1, 254],
|
|
844
|
+
});
|
|
845
|
+
} finally {
|
|
846
|
+
await loopbackServer.close();
|
|
847
|
+
}
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
test("caps base64 arraybuffer serialization and keeps its raw bytes charged", async () => {
|
|
851
|
+
const binaryBody: Buffer = Buffer.alloc(8 * 1024 * 1024, 255);
|
|
852
|
+
const followUpBody: Buffer = Buffer.alloc(3 * 1024 * 1024, "a");
|
|
853
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
854
|
+
(request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
855
|
+
const body: Buffer =
|
|
856
|
+
request.url === "/binary" ? binaryBody : followUpBody;
|
|
857
|
+
response.writeHead(200, {
|
|
858
|
+
"content-length": body.length,
|
|
859
|
+
"content-type": "application/octet-stream",
|
|
860
|
+
});
|
|
861
|
+
response.end(body);
|
|
862
|
+
},
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
try {
|
|
866
|
+
const result: ReturnResult = await runInSandbox(
|
|
867
|
+
`
|
|
868
|
+
let firstError = '';
|
|
869
|
+
try {
|
|
870
|
+
await axios.get(
|
|
871
|
+
'http://127.0.0.1:${loopbackServer.port}/binary',
|
|
872
|
+
{ responseType: 'arraybuffer' }
|
|
873
|
+
);
|
|
874
|
+
} catch (error) {
|
|
875
|
+
firstError = error.message;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
try {
|
|
879
|
+
await axios.get(
|
|
880
|
+
'http://127.0.0.1:${loopbackServer.port}/follow-up'
|
|
881
|
+
);
|
|
882
|
+
return 'follow-up unexpectedly succeeded';
|
|
883
|
+
} catch (error) {
|
|
884
|
+
return firstError + ' | ' + error.message;
|
|
885
|
+
}
|
|
886
|
+
`,
|
|
887
|
+
allowLoopbackForWorkflow,
|
|
888
|
+
);
|
|
889
|
+
|
|
890
|
+
expect(result.scriptError).toBeUndefined();
|
|
891
|
+
expect(result.returnValue).toMatch(
|
|
892
|
+
/remote response exceeded the allowed serialized size/i,
|
|
893
|
+
);
|
|
894
|
+
expect(result.returnValue).toMatch(/remote response exceeded/i);
|
|
895
|
+
expect(loopbackServer.requestCount()).toBe(2);
|
|
896
|
+
} finally {
|
|
897
|
+
await loopbackServer.close();
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
test("base64-bridges control-heavy text before decoding it in the isolate", async () => {
|
|
902
|
+
const controlBody: Buffer = Buffer.alloc(7 * 1024 * 1024, 0);
|
|
903
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
904
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
905
|
+
response.writeHead(200, {
|
|
906
|
+
"content-length": controlBody.length,
|
|
907
|
+
"content-type": "text/plain",
|
|
908
|
+
});
|
|
909
|
+
response.end(controlBody);
|
|
910
|
+
},
|
|
911
|
+
);
|
|
912
|
+
|
|
913
|
+
try {
|
|
914
|
+
const result: ReturnResult = await runInSandbox(
|
|
915
|
+
`
|
|
916
|
+
const response = await axios.get(
|
|
917
|
+
'http://127.0.0.1:${loopbackServer.port}/control-text',
|
|
918
|
+
{ responseType: 'text' }
|
|
919
|
+
);
|
|
920
|
+
return {
|
|
921
|
+
length: response.data.length,
|
|
922
|
+
firstCodePoint: response.data.charCodeAt(0),
|
|
923
|
+
lastCodePoint: response.data.charCodeAt(response.data.length - 1)
|
|
924
|
+
};
|
|
925
|
+
`,
|
|
926
|
+
allowLoopbackForWorkflow,
|
|
927
|
+
);
|
|
928
|
+
|
|
929
|
+
expect(result.scriptError).toBeUndefined();
|
|
930
|
+
expect(result.returnValue).toEqual({
|
|
931
|
+
length: 7 * 1024 * 1024,
|
|
932
|
+
firstCodePoint: 0,
|
|
933
|
+
lastCodePoint: 0,
|
|
934
|
+
});
|
|
935
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
936
|
+
} finally {
|
|
937
|
+
await loopbackServer.close();
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test("preserves responseEncoding while decoding response bytes inside the isolate", async () => {
|
|
942
|
+
const latin1Body: Buffer = Buffer.from([0x63, 0x61, 0x66, 0xe9]);
|
|
943
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
944
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
945
|
+
response.end(latin1Body);
|
|
946
|
+
},
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
try {
|
|
950
|
+
const result: ReturnResult = await runInSandbox(
|
|
951
|
+
`
|
|
952
|
+
const response = await axios.get(
|
|
953
|
+
'http://127.0.0.1:${loopbackServer.port}/latin1',
|
|
954
|
+
{ responseType: 'text', responseEncoding: 'latin1' }
|
|
955
|
+
);
|
|
956
|
+
return response.data;
|
|
957
|
+
`,
|
|
958
|
+
allowLoopbackForWorkflow,
|
|
959
|
+
);
|
|
960
|
+
|
|
961
|
+
expect(result.scriptError).toBeUndefined();
|
|
962
|
+
expect(result.returnValue).toBe("café");
|
|
963
|
+
} finally {
|
|
964
|
+
await loopbackServer.close();
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
test("strips a UTF-8 BOM before default JSON parsing and text delivery", async () => {
|
|
969
|
+
const bomJsonBody: Buffer = Buffer.concat([
|
|
970
|
+
Buffer.from([0xef, 0xbb, 0xbf]),
|
|
971
|
+
Buffer.from('{"ok":true}', "utf8"),
|
|
972
|
+
]);
|
|
973
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
974
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
975
|
+
response.end(bomJsonBody);
|
|
976
|
+
},
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
try {
|
|
980
|
+
const result: ReturnResult = await runInSandbox(
|
|
981
|
+
`
|
|
982
|
+
const defaultResponse = await axios.get(
|
|
983
|
+
'http://127.0.0.1:${loopbackServer.port}/default'
|
|
984
|
+
);
|
|
985
|
+
const textResponse = await axios.get(
|
|
986
|
+
'http://127.0.0.1:${loopbackServer.port}/text',
|
|
987
|
+
{ responseType: 'text', responseEncoding: 'utf8' }
|
|
988
|
+
);
|
|
989
|
+
return {
|
|
990
|
+
defaultData: defaultResponse.data,
|
|
991
|
+
textData: textResponse.data,
|
|
992
|
+
firstTextCodePoint: textResponse.data.charCodeAt(0)
|
|
993
|
+
};
|
|
994
|
+
`,
|
|
995
|
+
allowLoopbackForWorkflow,
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
expect(result.scriptError).toBeUndefined();
|
|
999
|
+
expect(result.returnValue).toEqual({
|
|
1000
|
+
defaultData: { ok: true },
|
|
1001
|
+
textData: '{"ok":true}',
|
|
1002
|
+
firstTextCodePoint: 123,
|
|
1003
|
+
});
|
|
1004
|
+
} finally {
|
|
1005
|
+
await loopbackServer.close();
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
test("honors transitional forcedJSONParsing false", async () => {
|
|
1010
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
1011
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
1012
|
+
response.end('{"ok":true}');
|
|
1013
|
+
},
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
try {
|
|
1017
|
+
const result: ReturnResult = await runInSandbox(
|
|
1018
|
+
`
|
|
1019
|
+
const response = await axios.get(
|
|
1020
|
+
'http://127.0.0.1:${loopbackServer.port}/forced-off',
|
|
1021
|
+
{ transitional: { forcedJSONParsing: false } }
|
|
1022
|
+
);
|
|
1023
|
+
return {
|
|
1024
|
+
type: typeof response.data,
|
|
1025
|
+
data: response.data
|
|
1026
|
+
};
|
|
1027
|
+
`,
|
|
1028
|
+
allowLoopbackForWorkflow,
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
expect(result.scriptError).toBeUndefined();
|
|
1032
|
+
expect(result.returnValue).toEqual({
|
|
1033
|
+
type: "string",
|
|
1034
|
+
data: '{"ok":true}',
|
|
1035
|
+
});
|
|
1036
|
+
} finally {
|
|
1037
|
+
await loopbackServer.close();
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
test("honors strict JSON parsing and retains Axios error response context", async () => {
|
|
1042
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
1043
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
1044
|
+
response.writeHead(200, { "x-response-sentinel": "strict-json" });
|
|
1045
|
+
response.end("not-json");
|
|
1046
|
+
},
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
try {
|
|
1050
|
+
const result: ReturnResult = await runInSandbox(
|
|
1051
|
+
`
|
|
1052
|
+
try {
|
|
1053
|
+
await axios.get(
|
|
1054
|
+
'http://127.0.0.1:${loopbackServer.port}/strict-json',
|
|
1055
|
+
{
|
|
1056
|
+
responseType: 'json',
|
|
1057
|
+
transitional: { silentJSONParsing: false }
|
|
1058
|
+
}
|
|
1059
|
+
);
|
|
1060
|
+
return 'unexpected success';
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
return {
|
|
1063
|
+
isAxiosError: axios.isAxiosError(error),
|
|
1064
|
+
name: error.name,
|
|
1065
|
+
code: error.code,
|
|
1066
|
+
status: error.response.status,
|
|
1067
|
+
header: error.response.headers['x-response-sentinel'],
|
|
1068
|
+
configMethod: error.config.method,
|
|
1069
|
+
configUrl: error.config.url,
|
|
1070
|
+
sameConfigUrl:
|
|
1071
|
+
error.config.url === error.response.config.url,
|
|
1072
|
+
hasRequest: Boolean(error.request),
|
|
1073
|
+
hasResponseRequest: Boolean(error.response.request),
|
|
1074
|
+
data: error.response.data
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
`,
|
|
1078
|
+
allowLoopbackForWorkflow,
|
|
1079
|
+
);
|
|
1080
|
+
|
|
1081
|
+
expect(result.scriptError).toBeUndefined();
|
|
1082
|
+
expect(result.returnValue).toMatchObject({
|
|
1083
|
+
isAxiosError: true,
|
|
1084
|
+
name: "SyntaxError",
|
|
1085
|
+
code: "ERR_BAD_RESPONSE",
|
|
1086
|
+
status: 200,
|
|
1087
|
+
header: "strict-json",
|
|
1088
|
+
configMethod: "get",
|
|
1089
|
+
configUrl: `http://127.0.0.1:${loopbackServer.port}/strict-json`,
|
|
1090
|
+
sameConfigUrl: true,
|
|
1091
|
+
hasRequest: true,
|
|
1092
|
+
hasResponseRequest: true,
|
|
1093
|
+
data: "not-json",
|
|
1094
|
+
});
|
|
1095
|
+
} finally {
|
|
1096
|
+
await loopbackServer.close();
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
test("preserves a stricter caller maxContentLength and wraps its stream failure", async () => {
|
|
1101
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
1102
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
1103
|
+
response.writeHead(200, {
|
|
1104
|
+
"content-length": "4",
|
|
1105
|
+
"x-response-sentinel": "content-limit",
|
|
1106
|
+
});
|
|
1107
|
+
response.end("four");
|
|
1108
|
+
},
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
try {
|
|
1112
|
+
const result: ReturnResult = await runInSandbox(
|
|
1113
|
+
`
|
|
1114
|
+
try {
|
|
1115
|
+
await axios.get(
|
|
1116
|
+
'http://127.0.0.1:${loopbackServer.port}/content-limit',
|
|
1117
|
+
{ maxContentLength: 3 }
|
|
1118
|
+
);
|
|
1119
|
+
return 'unexpected success';
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
return {
|
|
1122
|
+
message: error.message,
|
|
1123
|
+
isAxiosError: axios.isAxiosError(error),
|
|
1124
|
+
status: error.response.status,
|
|
1125
|
+
header: error.response.headers['x-response-sentinel'],
|
|
1126
|
+
configLimit: error.config.maxContentLength,
|
|
1127
|
+
responseConfigLimit: error.response.config.maxContentLength,
|
|
1128
|
+
hasRequest: Boolean(error.request),
|
|
1129
|
+
hasResponseRequest: Boolean(error.response.request)
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
`,
|
|
1133
|
+
allowLoopbackForWorkflow,
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
expect(result.scriptError).toBeUndefined();
|
|
1137
|
+
expect(result.returnValue).toMatchObject({
|
|
1138
|
+
message: expect.stringMatching(/exceeded the allowed size/i),
|
|
1139
|
+
isAxiosError: true,
|
|
1140
|
+
status: 200,
|
|
1141
|
+
header: "content-limit",
|
|
1142
|
+
configLimit: 3,
|
|
1143
|
+
responseConfigLimit: 3,
|
|
1144
|
+
hasRequest: true,
|
|
1145
|
+
hasResponseRequest: true,
|
|
1146
|
+
});
|
|
1147
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
1148
|
+
} finally {
|
|
1149
|
+
await loopbackServer.close();
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
test("preserves a stricter caller maxBodyLength", async () => {
|
|
1154
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer();
|
|
1155
|
+
|
|
1156
|
+
try {
|
|
1157
|
+
const result: ReturnResult = await runInSandbox(
|
|
1158
|
+
`
|
|
1159
|
+
try {
|
|
1160
|
+
await axios.post(
|
|
1161
|
+
'http://127.0.0.1:${loopbackServer.port}/body-limit',
|
|
1162
|
+
'four',
|
|
1163
|
+
{ maxBodyLength: 3 }
|
|
1164
|
+
);
|
|
1165
|
+
return 'unexpected success';
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
return error.message;
|
|
1168
|
+
}
|
|
1169
|
+
`,
|
|
1170
|
+
allowLoopbackForWorkflow,
|
|
1171
|
+
);
|
|
1172
|
+
|
|
1173
|
+
expect(result.scriptError).toBeUndefined();
|
|
1174
|
+
expect(result.returnValue).toMatch(/maxBodyLength/i);
|
|
1175
|
+
expect(loopbackServer.requestCount()).toBe(0);
|
|
1176
|
+
} finally {
|
|
1177
|
+
await loopbackServer.close();
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
test("wraps a mid-body stream failure with Axios response context", async () => {
|
|
1182
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
1183
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
1184
|
+
response.writeHead(200, {
|
|
1185
|
+
"content-length": "100",
|
|
1186
|
+
"x-response-sentinel": "mid-body",
|
|
1187
|
+
});
|
|
1188
|
+
response.write("partial body");
|
|
1189
|
+
response.flushHeaders();
|
|
1190
|
+
setTimeout((): void => {
|
|
1191
|
+
response.destroy();
|
|
1192
|
+
}, 10);
|
|
1193
|
+
},
|
|
1194
|
+
);
|
|
1195
|
+
|
|
1196
|
+
try {
|
|
1197
|
+
const result: ReturnResult = await runInSandbox(
|
|
1198
|
+
`
|
|
1199
|
+
try {
|
|
1200
|
+
await axios.get(
|
|
1201
|
+
'http://127.0.0.1:${loopbackServer.port}/mid-body'
|
|
1202
|
+
);
|
|
1203
|
+
return 'unexpected success';
|
|
1204
|
+
} catch (error) {
|
|
1205
|
+
return {
|
|
1206
|
+
isAxiosError: axios.isAxiosError(error),
|
|
1207
|
+
status: error.response.status,
|
|
1208
|
+
header: error.response.headers['x-response-sentinel'],
|
|
1209
|
+
configMethod: error.config.method,
|
|
1210
|
+
configUrl: error.config.url,
|
|
1211
|
+
sameConfigUrl:
|
|
1212
|
+
error.config.url === error.response.config.url,
|
|
1213
|
+
hasRequest: Boolean(error.request),
|
|
1214
|
+
hasResponseRequest: Boolean(error.response.request),
|
|
1215
|
+
boundedData: error.response.data
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
`,
|
|
1219
|
+
allowLoopbackForWorkflow,
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
expect(result.scriptError).toBeUndefined();
|
|
1223
|
+
expect(result.returnValue).toMatchObject({
|
|
1224
|
+
isAxiosError: true,
|
|
1225
|
+
status: 200,
|
|
1226
|
+
header: "mid-body",
|
|
1227
|
+
configMethod: "get",
|
|
1228
|
+
configUrl: `http://127.0.0.1:${loopbackServer.port}/mid-body`,
|
|
1229
|
+
sameConfigUrl: true,
|
|
1230
|
+
hasRequest: true,
|
|
1231
|
+
hasResponseRequest: true,
|
|
1232
|
+
boundedData: "",
|
|
1233
|
+
});
|
|
1234
|
+
expect(loopbackServer.requestCount()).toBe(1);
|
|
1235
|
+
} finally {
|
|
1236
|
+
await loopbackServer.close();
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
test("preserves Axios's silent invalid-JSON response behavior", async () => {
|
|
1241
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
1242
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
1243
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
1244
|
+
response.end("not-json");
|
|
1245
|
+
},
|
|
1246
|
+
);
|
|
1247
|
+
|
|
1248
|
+
try {
|
|
1249
|
+
const result: ReturnResult = await runInSandbox(
|
|
1250
|
+
`
|
|
1251
|
+
const response = await axios.get(
|
|
1252
|
+
'http://127.0.0.1:${loopbackServer.port}/invalid-json',
|
|
1253
|
+
{ responseType: 'json' }
|
|
1254
|
+
);
|
|
1255
|
+
return response.data;
|
|
1256
|
+
`,
|
|
1257
|
+
allowLoopbackForWorkflow,
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
expect(result.scriptError).toBeUndefined();
|
|
1261
|
+
expect(result.returnValue).toBe("not-json");
|
|
1262
|
+
} finally {
|
|
1263
|
+
await loopbackServer.close();
|
|
1264
|
+
}
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
test("decodes and parses a bounded Axios error response inside the isolate", async () => {
|
|
1268
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer(
|
|
1269
|
+
(_request: http.IncomingMessage, response: http.ServerResponse): void => {
|
|
1270
|
+
response.writeHead(418, { "content-type": "application/json" });
|
|
1271
|
+
response.end('{"error":"teapot"}');
|
|
1272
|
+
},
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
try {
|
|
1276
|
+
const result: ReturnResult = await runInSandbox(
|
|
1277
|
+
`
|
|
1278
|
+
try {
|
|
1279
|
+
await axios.get(
|
|
1280
|
+
'http://127.0.0.1:${loopbackServer.port}/error'
|
|
1281
|
+
);
|
|
1282
|
+
return 'unexpected success';
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
return {
|
|
1285
|
+
status: error.response.status,
|
|
1286
|
+
data: error.response.data
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
`,
|
|
1290
|
+
allowLoopbackForWorkflow,
|
|
1291
|
+
);
|
|
1292
|
+
|
|
1293
|
+
expect(result.scriptError).toBeUndefined();
|
|
1294
|
+
expect(result.returnValue).toEqual({
|
|
1295
|
+
status: 418,
|
|
1296
|
+
data: { error: "teapot" },
|
|
1297
|
+
});
|
|
1298
|
+
} finally {
|
|
1299
|
+
await loopbackServer.close();
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
});
|
|
1303
|
+
|
|
161
1304
|
/*
|
|
162
1305
|
* The "is this already an absolute URL?" decision is what tells the guard
|
|
163
1306
|
* whether to fold in baseURL before validating, so it has to be scheme
|
|
@@ -194,4 +1337,31 @@ describe("VMRunner sandbox axios bridge — URL absolutization", () => {
|
|
|
194
1337
|
);
|
|
195
1338
|
expect(message).not.toMatch(REFUSAL);
|
|
196
1339
|
});
|
|
1340
|
+
|
|
1341
|
+
test.each([
|
|
1342
|
+
["protocol-relative", "//169.254.169.254/metadata"],
|
|
1343
|
+
["non-HTTP scheme", "file:///etc/passwd"],
|
|
1344
|
+
])(
|
|
1345
|
+
"treats an Axios-absolute %s URL as absolute before rejecting its final scheme",
|
|
1346
|
+
async (_label: string, requestedUrl: string): Promise<void> => {
|
|
1347
|
+
const loopbackServer: RunningLoopbackServer = await startLoopbackServer();
|
|
1348
|
+
|
|
1349
|
+
try {
|
|
1350
|
+
const message: string = await errorFromSandbox(
|
|
1351
|
+
`return await axios.get(${JSON.stringify(requestedUrl)}, {
|
|
1352
|
+
baseURL: 'http://127.0.0.1:${loopbackServer.port}/must-not-run'
|
|
1353
|
+
});`,
|
|
1354
|
+
{
|
|
1355
|
+
allowPrivateNetworkRequests: true,
|
|
1356
|
+
privateNetworkAccessIsAllowed: true,
|
|
1357
|
+
},
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
expect(message).toMatch(/absolute http or https/i);
|
|
1361
|
+
expect(loopbackServer.requestCount()).toBe(0);
|
|
1362
|
+
} finally {
|
|
1363
|
+
await loopbackServer.close();
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
);
|
|
197
1367
|
});
|