@oneuptime/common 12.0.22 → 12.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/DetectionRule.ts +55 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +584 -0
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +105 -0
- package/Models/DatabaseModels/Project.ts +68 -0
- package/Models/DatabaseModels/User.ts +68 -0
- package/Server/API/TelemetryAPI.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.ts +60 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Middleware/GzipRequestBody.ts +311 -0
- package/Server/Middleware/MultipartFormData.ts +87 -3
- package/Server/Services/DetectionRuleService.ts +73 -6
- package/Server/Services/Index.ts +207 -0
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +917 -0
- package/Server/Services/NetworkDeviceAutoImportRuleService.ts +181 -0
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
- package/Server/Services/NetworkDeviceService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
- package/Server/Services/ProjectService.ts +29 -19
- package/Server/Services/SecurityEventService.ts +90 -13
- package/Server/Services/StatusPageResourceService.ts +223 -0
- package/Server/Services/TelemetryAttributeService.ts +17 -0
- package/Server/Services/UserService.ts +4 -6
- package/Server/Types/Markdown.ts +161 -67
- package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
- package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
- package/Server/Utils/Attribution.ts +4 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +39 -1
- package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
- package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
- package/Server/Utils/StartServer.ts +12 -32
- package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
- package/Server/Utils/Telemetry/LlmMetricSpend.ts +130 -0
- package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
- package/Server/Views/Partials/Head.ejs +2 -1
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
- package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
- package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
- package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
- package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
- package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
- package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
- package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
- package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
- package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
- package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
- package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
- package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
- package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
- package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
- package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
- package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
- package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
- package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
- package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
- package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
- package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
- package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
- package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
- package/Tests/Server/Services/AutoImportScanCredentialSelect.test.ts +205 -0
- package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
- package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +933 -0
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
- package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
- package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
- package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
- package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
- package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
- package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +97 -1
- package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
- package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
- package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +368 -4
- package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +245 -0
- package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
- package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
- package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
- package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
- package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
- package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +193 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
- package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
- package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
- package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
- package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
- package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
- package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
- package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
- package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
- package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
- package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
- package/Tests/UI/Components/SideOver.test.tsx +1 -1
- package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
- package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
- package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
- package/Tests/UI/Components/Toggle.test.tsx +25 -0
- package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
- package/Tests/Utils/NetworkDiscovery/AutoImportRuleMatcher.test.ts +516 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.test.ts +272 -0
- package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
- package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
- package/Tests/Utils/NetworkDiscovery/ScanTargetUtil.test.ts +164 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
- package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +472 -0
- package/Types/Code/YamlSyntax.ts +396 -0
- package/Types/Exception/ExceptionCode.ts +1 -0
- package/Types/Exception/PayloadTooLargeException.ts +8 -0
- package/Types/Marketing/Attribution.ts +10 -0
- package/Types/Marketing/MarketingEvent.ts +4 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
- package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
- package/Types/NetworkAutomation/RuleRunResult.ts +84 -0
- package/Types/Permission.ts +47 -0
- package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
- package/Types/Telemetry/LlmMetricConventions.ts +124 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
- package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
- package/UI/Components/Detail/Detail.tsx +10 -0
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
- package/UI/Components/Forms/Fields/FormField.tsx +31 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
- package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
- package/UI/Components/Forms/Validation.ts +46 -0
- package/UI/Components/Input/Input.tsx +3 -0
- package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
- package/UI/Components/ModelTable/Column.ts +8 -0
- package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
- package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
- package/UI/Components/SideOver/SideOver.tsx +5 -5
- package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
- package/UI/Components/StatusPage/ResourceList.tsx +197 -38
- package/UI/Components/Toggle/Toggle.tsx +9 -1
- package/UI/Components/Types/FieldType.ts +1 -0
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
- package/Utils/NetworkDiscovery/AutoImportRuleMatcher.ts +257 -0
- package/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.ts +183 -0
- package/Utils/NetworkDiscovery/DiscoveredHostUtil.ts +95 -0
- package/Utils/NetworkDiscovery/DiscoveryImportEligibility.ts +66 -0
- package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +82 -0
- package/Utils/StatusPage/ResourceExplorer.ts +258 -0
- package/Utils/Telemetry/LlmMetricQuery.ts +204 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
- package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +609 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +97 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +76 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +76 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +17 -0
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.js +35 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789100000000-AddNetworkDeviceAutoImportRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789200000000-AddSysObjectIdPatternToAutoImportRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789300000000-AddAutoApplyVendorHealthTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
- package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
- package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
- package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
- package/build/dist/Server/Services/DetectionRuleService.js +49 -4
- package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +206 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +711 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +155 -0
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +25 -45
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/SecurityEventService.js +57 -5
- package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
- package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
- package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +2 -12
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +147 -61
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
- package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Attribution.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +26 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
- package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
- package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +12 -21
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
- package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +102 -0
- package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
- package/build/dist/Types/Code/YamlSyntax.js +212 -0
- package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js +1 -0
- package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
- package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
- package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
- package/build/dist/Types/Marketing/Attribution.js +10 -0
- package/build/dist/Types/Marketing/Attribution.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +24 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/Permission.js +41 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
- package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
- package/build/dist/Types/Telemetry/LlmMetricConventions.js +98 -0
- package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
- package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +9 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
- package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
- package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
- package/build/dist/UI/Components/Forms/Validation.js +27 -0
- package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +1 -1
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
- package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
- package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
- package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
- package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
- package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
- package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
- package/build/dist/UI/Components/Types/FieldType.js +1 -0
- package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js +149 -0
- package/build/dist/Utils/NetworkDiscovery/AutoImportRuleMatcher.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js +121 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredDeviceBuilder.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js +75 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveredHostUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js +59 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryImportEligibility.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
- package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +60 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js +119 -0
- package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +2 -1
- package/Tests/UI/Components/Toast.test.tsx +0 -197
- package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
- package/UI/Components/Toast/Toast.tsx +0 -189
- package/UI/Components/Toast/ToastInit.tsx +0 -110
- package/build/dist/UI/Components/Toast/Toast.js +0 -123
- package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
- package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
- package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
|
@@ -40,6 +40,7 @@ jest.mock("../../../../Server/Infrastructure/Queue", () => {
|
|
|
40
40
|
|
|
41
41
|
import MarketingEventUtil from "../../../../Server/Utils/Marketing/MarketingEventUtil";
|
|
42
42
|
import Attribution from "../../../../Server/Utils/Attribution";
|
|
43
|
+
import { UtmPropertyKeys } from "../../../../Types/Marketing/Attribution";
|
|
43
44
|
import {
|
|
44
45
|
MarketingEvent,
|
|
45
46
|
MarketingEventType,
|
|
@@ -120,12 +121,54 @@ describe("MarketingEventUtil", () => {
|
|
|
120
121
|
utmCampaign: "brand",
|
|
121
122
|
utmTerm: "oneuptime",
|
|
122
123
|
utmContent: "hero-b",
|
|
124
|
+
utmId: undefined,
|
|
125
|
+
utmSourcePlatform: undefined,
|
|
126
|
+
utmCreativeFormat: undefined,
|
|
127
|
+
utmMarketingTactic: undefined,
|
|
123
128
|
utmUrl: "https://oneuptime.com/?gclid=x",
|
|
124
129
|
clickIds: { gclid: "x" },
|
|
125
130
|
firstTouch: { utmSource: "linkedin" },
|
|
126
131
|
});
|
|
127
132
|
});
|
|
128
133
|
|
|
134
|
+
/*
|
|
135
|
+
* THE CONTRACT, ASSERTED MECHANICALLY.
|
|
136
|
+
*
|
|
137
|
+
* The browser captures every key in UtmWireKeyToPropertyKey, the signup
|
|
138
|
+
* door stores them and the Cal embed sends them — and for four of them the
|
|
139
|
+
* outbound conversion carried nothing at all, because this payload was
|
|
140
|
+
* hand-listed and nobody updated it. Nothing errored; conversions kept
|
|
141
|
+
* arriving, just unattributable on those dimensions.
|
|
142
|
+
*
|
|
143
|
+
* Built from the contract rather than a fixture, so a key added to
|
|
144
|
+
* Common/Types/Marketing/Attribution.ts is exercised here automatically
|
|
145
|
+
* instead of quietly going missing on the wire again.
|
|
146
|
+
*/
|
|
147
|
+
test("carries every UTM key the shared contract names", () => {
|
|
148
|
+
const source: Record<string, string> = {};
|
|
149
|
+
|
|
150
|
+
for (const propertyKey of UtmPropertyKeys) {
|
|
151
|
+
source[propertyKey] = `${propertyKey}-value`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const event: MarketingEvent = MarketingEventUtil.buildEvent({
|
|
155
|
+
eventType: MarketingEventType.SignUp,
|
|
156
|
+
eventId: "sign_up:u1",
|
|
157
|
+
occurredAt: new Date(),
|
|
158
|
+
attributionSource: source,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const attribution: Record<string, unknown> =
|
|
162
|
+
event.attribution as unknown as Record<string, unknown>;
|
|
163
|
+
|
|
164
|
+
// The fixture has to exercise the keys for this to mean anything.
|
|
165
|
+
expect(UtmPropertyKeys.length).toBeGreaterThanOrEqual(9);
|
|
166
|
+
|
|
167
|
+
for (const propertyKey of UtmPropertyKeys) {
|
|
168
|
+
expect(attribution[propertyKey]).toBe(`${propertyKey}-value`);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
129
172
|
/*
|
|
130
173
|
* A conversion carrying no attribution is still a conversion. The two
|
|
131
174
|
* container fields default to empty objects rather than undefined so a
|
|
@@ -200,30 +200,34 @@ describe("ProfileMonitorCriteria.isMonitorInstanceCriteriaFilterMet", () => {
|
|
|
200
200
|
});
|
|
201
201
|
|
|
202
202
|
/*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* the
|
|
203
|
+
* A hand-written inversion of the shipped default, not the default itself.
|
|
204
|
+
* Profiles ships the Logs polarity - profiles arriving is the sign of life,
|
|
205
|
+
* so the default offline criteria is "Profile Count == 0" (covered above)
|
|
206
|
+
* and the online one is "Profile Count > 0". Someone monitoring for the
|
|
207
|
+
* appearance of profiles rather than their absence can write "Profile
|
|
208
|
+
* Count >= 1" and point it at the offline status instead; these tests pin
|
|
209
|
+
* that the evaluator decides that rule the same way, since nothing in
|
|
210
|
+
* ProfileMonitorCriteria knows or cares which status a criteria carries.
|
|
207
211
|
*/
|
|
208
|
-
describe("profile-presence
|
|
209
|
-
const
|
|
212
|
+
describe("a user-authored profile-presence criteria (Profile Count >= 1)", () => {
|
|
213
|
+
const presenceCriteria: CriteriaFilter = {
|
|
210
214
|
checkOn: CheckOn.ProfileCount,
|
|
211
215
|
filterType: FilterType.GreaterThanOrEqualTo,
|
|
212
216
|
value: 1,
|
|
213
217
|
};
|
|
214
218
|
|
|
215
|
-
test("one matching profile → met
|
|
216
|
-
const result: string | null = await evaluate(1,
|
|
219
|
+
test("one matching profile → met", async () => {
|
|
220
|
+
const result: string | null = await evaluate(1, presenceCriteria);
|
|
217
221
|
expect(result).toBeTruthy();
|
|
218
222
|
expect(result).toContain("Profile Count");
|
|
219
223
|
});
|
|
220
224
|
|
|
221
225
|
test("many matching profiles → met", async () => {
|
|
222
|
-
expect(await evaluate(6718284,
|
|
226
|
+
expect(await evaluate(6718284, presenceCriteria)).toBeTruthy();
|
|
223
227
|
});
|
|
224
228
|
|
|
225
|
-
test("zero matching profiles → not met
|
|
226
|
-
expect(await evaluate(0,
|
|
229
|
+
test("zero matching profiles → not met", async () => {
|
|
230
|
+
expect(await evaluate(0, presenceCriteria)).toBeNull();
|
|
227
231
|
});
|
|
228
232
|
});
|
|
229
233
|
|
|
@@ -11,6 +11,10 @@ import SnmpInterface from "../../../../Types/Monitor/SnmpMonitor/SnmpInterface";
|
|
|
11
11
|
import SnmpMonitorResponse from "../../../../Types/Monitor/SnmpMonitor/SnmpMonitorResponse";
|
|
12
12
|
import LldpNeighbor from "../../../../Types/Monitor/SnmpMonitor/LldpNeighbor";
|
|
13
13
|
import CdpNeighbor from "../../../../Types/Monitor/SnmpMonitor/CdpNeighbor";
|
|
14
|
+
import SnmpOid from "../../../../Types/Monitor/SnmpMonitor/SnmpOid";
|
|
15
|
+
import SnmpVendorTemplateUtil, {
|
|
16
|
+
SnmpVendorTemplate,
|
|
17
|
+
} from "../../../../Types/Monitor/SnmpMonitor/SnmpVendorTemplate";
|
|
14
18
|
|
|
15
19
|
/*
|
|
16
20
|
* NetworkInventoryUtil.updateFromWalk is the single writer that keeps the
|
|
@@ -40,14 +44,21 @@ let interfaceUpdateSpy: jest.SpyInstance;
|
|
|
40
44
|
let interfaceCreateSpy: jest.SpyInstance;
|
|
41
45
|
let endpointUpsertSpy: jest.SpyInstance;
|
|
42
46
|
|
|
43
|
-
function mockServices(
|
|
47
|
+
function mockServices(
|
|
48
|
+
existingInterfaces: Array<NetworkInterface> = [],
|
|
49
|
+
deviceOverrides: Partial<NetworkDevice> = {},
|
|
50
|
+
): void {
|
|
44
51
|
/*
|
|
45
52
|
* The project-membership guard resolves the device (scoped to the given
|
|
46
53
|
* project) before any write; return a matching device so the write path
|
|
47
54
|
* runs. The cross-project-refusal case overrides this to null.
|
|
55
|
+
* deviceOverrides seeds the columns the vendor-template auto-apply reads
|
|
56
|
+
* (autoApplyVendorHealthTemplate, snmpOids) — absent by default, exactly
|
|
57
|
+
* like a device that never opted in.
|
|
48
58
|
*/
|
|
49
59
|
const ownedDevice: NetworkDevice = new NetworkDevice();
|
|
50
60
|
ownedDevice.id = new ObjectID(DEVICE_ID);
|
|
61
|
+
Object.assign(ownedDevice, deviceOverrides);
|
|
51
62
|
deviceFindSpy = jest
|
|
52
63
|
.spyOn(NetworkDeviceService, "findOneBy")
|
|
53
64
|
.mockResolvedValue(ownedDevice);
|
|
@@ -867,3 +878,88 @@ describe("NetworkInventoryUtil.updateFromWalk — endpoint discovery", () => {
|
|
|
867
878
|
]);
|
|
868
879
|
});
|
|
869
880
|
});
|
|
881
|
+
|
|
882
|
+
describe("NetworkInventoryUtil.updateFromWalk — vendor health template auto-apply", () => {
|
|
883
|
+
/*
|
|
884
|
+
* The automatic counterpart of the dashboard's vendor-template banner:
|
|
885
|
+
* a device that opted in (auto-imported devices do) gets its EMPTY Health
|
|
886
|
+
* OID list seeded from the vendor template its sysObjectID fingerprints,
|
|
887
|
+
* on the first poll that learns the vendor. An existing list — however it
|
|
888
|
+
* got there — is the operator's and is never touched.
|
|
889
|
+
*/
|
|
890
|
+
test("an opted-in device with no health OIDs is seeded from the fingerprinted vendor template", async () => {
|
|
891
|
+
mockServices([], { autoApplyVendorHealthTemplate: true });
|
|
892
|
+
|
|
893
|
+
await runWalk({
|
|
894
|
+
systemInfo: {
|
|
895
|
+
sysObjectId: CISCO_SYS_OBJECT_ID,
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
const update: DeviceUpdatePayload = deviceUpdatePayload();
|
|
900
|
+
const seeded: Array<SnmpOid> = update["snmpOids"] as Array<SnmpOid>;
|
|
901
|
+
|
|
902
|
+
const ciscoTemplate: SnmpVendorTemplate | undefined =
|
|
903
|
+
SnmpVendorTemplateUtil.matchBySysObjectId(CISCO_SYS_OBJECT_ID);
|
|
904
|
+
|
|
905
|
+
expect(ciscoTemplate).toBeDefined();
|
|
906
|
+
expect(seeded).toEqual(ciscoTemplate!.oids);
|
|
907
|
+
expect(seeded.length).toBeGreaterThan(0);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
test("a device that never opted in is not seeded", async () => {
|
|
911
|
+
mockServices();
|
|
912
|
+
|
|
913
|
+
await runWalk({
|
|
914
|
+
systemInfo: {
|
|
915
|
+
sysObjectId: CISCO_SYS_OBJECT_ID,
|
|
916
|
+
},
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
expect(deviceUpdatePayload()).not.toHaveProperty("snmpOids");
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
test("an existing health OID list is never touched, even with the toggle on", async () => {
|
|
923
|
+
const handPickedOids: Array<SnmpOid> = [
|
|
924
|
+
{ oid: "1.3.6.1.4.1.9.9.109.1.1.1.1.7.1", name: "CPU 5min" },
|
|
925
|
+
];
|
|
926
|
+
|
|
927
|
+
mockServices([], {
|
|
928
|
+
autoApplyVendorHealthTemplate: true,
|
|
929
|
+
snmpOids: handPickedOids,
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
await runWalk({
|
|
933
|
+
systemInfo: {
|
|
934
|
+
sysObjectId: CISCO_SYS_OBJECT_ID,
|
|
935
|
+
},
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
expect(deviceUpdatePayload()).not.toHaveProperty("snmpOids");
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
test("a walk that learned no sysObjectID seeds nothing", async () => {
|
|
942
|
+
mockServices([], { autoApplyVendorHealthTemplate: true });
|
|
943
|
+
|
|
944
|
+
await runWalk({
|
|
945
|
+
systemInfo: {
|
|
946
|
+
sysName: "core-sw-01",
|
|
947
|
+
},
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
expect(deviceUpdatePayload()).not.toHaveProperty("snmpOids");
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
test("an enterprise with no vendor template seeds nothing", async () => {
|
|
954
|
+
mockServices([], { autoApplyVendorHealthTemplate: true });
|
|
955
|
+
|
|
956
|
+
// Enterprise 99999 has no entry in ENTERPRISE_TEMPLATE_IDS.
|
|
957
|
+
await runWalk({
|
|
958
|
+
systemInfo: {
|
|
959
|
+
sysObjectId: "1.3.6.1.4.1.99999.1.1",
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
expect(deviceUpdatePayload()).not.toHaveProperty("snmpOids");
|
|
964
|
+
});
|
|
965
|
+
});
|
|
@@ -36,11 +36,13 @@ import IncidentSeverityService from "../../../../Server/Services/IncidentSeverit
|
|
|
36
36
|
import DetectionRuleService from "../../../../Server/Services/DetectionRuleService";
|
|
37
37
|
import SecurityEventService, {
|
|
38
38
|
DetectionMatchGroup,
|
|
39
|
+
FindDetectionMatchesData,
|
|
39
40
|
} from "../../../../Server/Services/SecurityEventService";
|
|
40
41
|
import OTelIngestService, {
|
|
41
42
|
TelemetryServiceMetadata,
|
|
42
43
|
} from "../../../../Server/Services/OpenTelemetryIngestService";
|
|
43
44
|
import logger from "../../../../Server/Utils/Logger";
|
|
45
|
+
import { Statement } from "../../../../Server/Utils/AnalyticsDatabase/Statement";
|
|
44
46
|
import MetricSeriesFingerprint from "../../../../Utils/Metrics/MetricSeriesFingerprint";
|
|
45
47
|
import ObjectID from "../../../../Types/ObjectID";
|
|
46
48
|
import OneUptimeDate from "../../../../Types/Date";
|
|
@@ -107,6 +109,9 @@ function buildRule(
|
|
|
107
109
|
lastEvaluatedAt?: Date;
|
|
108
110
|
evaluationIntervalInMinutes?: number;
|
|
109
111
|
id?: ObjectID;
|
|
112
|
+
groupByField?: string;
|
|
113
|
+
distinctCountField?: string;
|
|
114
|
+
matchCountThreshold?: number;
|
|
110
115
|
} = {},
|
|
111
116
|
): DetectionRule {
|
|
112
117
|
const rule: DetectionRule = new DetectionRule();
|
|
@@ -117,7 +122,15 @@ function buildRule(
|
|
|
117
122
|
rule.sigmaRuleYaml =
|
|
118
123
|
options.sigmaRuleYaml ?? sigmaYaml(options.level || "high");
|
|
119
124
|
rule.evaluationIntervalInMinutes = options.evaluationIntervalInMinutes ?? 5;
|
|
120
|
-
rule.groupByField = "principalUser";
|
|
125
|
+
rule.groupByField = options.groupByField ?? "principalUser";
|
|
126
|
+
|
|
127
|
+
if (options.distinctCountField !== undefined) {
|
|
128
|
+
rule.distinctCountField = options.distinctCountField;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (options.matchCountThreshold !== undefined) {
|
|
132
|
+
rule.matchCountThreshold = options.matchCountThreshold;
|
|
133
|
+
}
|
|
121
134
|
|
|
122
135
|
if (options.shouldCreateAlert !== undefined) {
|
|
123
136
|
rule.shouldCreateAlert = options.shouldCreateAlert;
|
|
@@ -156,10 +169,12 @@ function buildSeverity(idSuffix: string, name: string): AlertSeverity {
|
|
|
156
169
|
function buildGroup(
|
|
157
170
|
groupValue: string,
|
|
158
171
|
matchCount: number,
|
|
172
|
+
distinctCount: number = 0,
|
|
159
173
|
): DetectionMatchGroup {
|
|
160
174
|
return {
|
|
161
175
|
groupValue,
|
|
162
176
|
matchCount,
|
|
177
|
+
distinctCount,
|
|
163
178
|
sampleMessage: `sample event for ${groupValue || "all"}`,
|
|
164
179
|
sampleObservables: [groupValue, "10.0.0.9"].filter(
|
|
165
180
|
(observable: string): boolean => {
|
|
@@ -297,15 +312,24 @@ function createdIncident(spies: EvaluationSpies, index: number): Incident {
|
|
|
297
312
|
return (call as { data: Incident }).data;
|
|
298
313
|
}
|
|
299
314
|
|
|
315
|
+
function findMatchesArg(spies: EvaluationSpies): FindDetectionMatchesData {
|
|
316
|
+
return spies.findDetectionMatches.mock
|
|
317
|
+
.calls[0]?.[0] as FindDetectionMatchesData;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// The evaluator logs expected failures; keep test output clean.
|
|
321
|
+
function silenceLogger(): void {
|
|
322
|
+
getJestSpyOn(logger, "error").mockImplementation((() => {
|
|
323
|
+
return undefined;
|
|
324
|
+
}) as never);
|
|
325
|
+
getJestSpyOn(logger, "warn").mockImplementation((() => {
|
|
326
|
+
return undefined;
|
|
327
|
+
}) as never);
|
|
328
|
+
}
|
|
329
|
+
|
|
300
330
|
describe("DetectionRuleEvaluator", () => {
|
|
301
331
|
beforeEach(() => {
|
|
302
|
-
|
|
303
|
-
getJestSpyOn(logger, "error").mockImplementation((() => {
|
|
304
|
-
return undefined;
|
|
305
|
-
}) as never);
|
|
306
|
-
getJestSpyOn(logger, "warn").mockImplementation((() => {
|
|
307
|
-
return undefined;
|
|
308
|
-
}) as never);
|
|
332
|
+
silenceLogger();
|
|
309
333
|
});
|
|
310
334
|
|
|
311
335
|
afterEach(() => {
|
|
@@ -1042,6 +1066,29 @@ describe("DetectionRuleEvaluator", () => {
|
|
|
1042
1066
|
expect(select["shouldCreateAlert"]).toBe(true);
|
|
1043
1067
|
expect(select["alertSeverityId"]).toBe(true);
|
|
1044
1068
|
});
|
|
1069
|
+
|
|
1070
|
+
test("selects the distinct-count columns so thresholds do not silently read as 1", async () => {
|
|
1071
|
+
/*
|
|
1072
|
+
* Same drift as the incident columns: a rule fetched WITHOUT
|
|
1073
|
+
* matchCountThreshold in the select falls back to the
|
|
1074
|
+
* fire-on-any-match default, and one fetched without
|
|
1075
|
+
* distinctCountField silently thresholds the raw count instead of
|
|
1076
|
+
* the distinct count — neither failure trips any other assertion.
|
|
1077
|
+
*/
|
|
1078
|
+
const rulesFindBy: Spy = getJestSpyOn(
|
|
1079
|
+
DetectionRuleService,
|
|
1080
|
+
"findBy",
|
|
1081
|
+
).mockResolvedValue([] as never);
|
|
1082
|
+
|
|
1083
|
+
await DetectionRuleEvaluator.evaluateAllDueRules();
|
|
1084
|
+
|
|
1085
|
+
const select: JSONObject = (
|
|
1086
|
+
rulesFindBy.mock.calls[0]?.[0] as { select: JSONObject }
|
|
1087
|
+
).select;
|
|
1088
|
+
|
|
1089
|
+
expect(select["distinctCountField"]).toBe(true);
|
|
1090
|
+
expect(select["matchCountThreshold"]).toBe(true);
|
|
1091
|
+
});
|
|
1045
1092
|
});
|
|
1046
1093
|
|
|
1047
1094
|
describe("rule bookkeeping", () => {
|
|
@@ -1095,4 +1142,318 @@ describe("DetectionRuleEvaluator", () => {
|
|
|
1095
1142
|
).toBe(false);
|
|
1096
1143
|
});
|
|
1097
1144
|
});
|
|
1145
|
+
|
|
1146
|
+
describe("distinct count and match count threshold", () => {
|
|
1147
|
+
/*
|
|
1148
|
+
* matchCountThreshold holds a rule back until a group reaches N
|
|
1149
|
+
* events, and distinctCountField switches what N counts: raw
|
|
1150
|
+
* matches, or uniqExact of one field. The ClickHouse HAVING clause
|
|
1151
|
+
* already enforces the threshold server-side, but the evaluator
|
|
1152
|
+
* re-filters so the firing contract does not depend on which side
|
|
1153
|
+
* built the rows — these tests pin that in-process filter plus the
|
|
1154
|
+
* exact query contract handed to SecurityEventService.
|
|
1155
|
+
*/
|
|
1156
|
+
test("threshold on the raw count path: only groups at/above it fire and totalMatches sums firing groups only", async () => {
|
|
1157
|
+
const rule: DetectionRule = buildRule({
|
|
1158
|
+
matchCountThreshold: 3,
|
|
1159
|
+
shouldWriteDetectionFinding: false,
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
const spies: EvaluationSpies = installSpies({
|
|
1163
|
+
groups: [
|
|
1164
|
+
buildGroup("alice", 5),
|
|
1165
|
+
buildGroup("bob", 2), // below threshold — must not fire
|
|
1166
|
+
buildGroup("carol", 3), // exactly at threshold — fires
|
|
1167
|
+
],
|
|
1168
|
+
severities: [buildSeverity("0001", "Default")],
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
const result: DetectionRuleEvaluationResult =
|
|
1172
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1173
|
+
|
|
1174
|
+
expect(result.matchedGroups).toBe(2);
|
|
1175
|
+
// 5 + 3; bob's 2 sub-threshold events are not "matches" at all.
|
|
1176
|
+
expect(result.totalMatches).toBe(8);
|
|
1177
|
+
expect(result.alertsCreated).toBe(2);
|
|
1178
|
+
|
|
1179
|
+
const alertFingerprints: Array<string | undefined> = [
|
|
1180
|
+
createdAlert(spies, 0).seriesFingerprint,
|
|
1181
|
+
createdAlert(spies, 1).seriesFingerprint,
|
|
1182
|
+
];
|
|
1183
|
+
|
|
1184
|
+
expect(alertFingerprints).toContain(expectedFingerprint("alice"));
|
|
1185
|
+
expect(alertFingerprints).toContain(expectedFingerprint("carol"));
|
|
1186
|
+
expect(alertFingerprints).not.toContain(expectedFingerprint("bob"));
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
test("an unset threshold behaves as 1 — pre-threshold rules keep firing on any match", async () => {
|
|
1190
|
+
const rule: DetectionRule = buildRule({
|
|
1191
|
+
shouldWriteDetectionFinding: false,
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
const spies: EvaluationSpies = installSpies({
|
|
1195
|
+
groups: [buildGroup("alice", 1)],
|
|
1196
|
+
severities: [buildSeverity("0001", "Default")],
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
const result: DetectionRuleEvaluationResult =
|
|
1200
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1201
|
+
|
|
1202
|
+
expect(result.matchedGroups).toBe(1);
|
|
1203
|
+
expect(result.alertsCreated).toBe(1);
|
|
1204
|
+
expect(findMatchesArg(spies).minMatchCount).toBe(1);
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
test("a threshold of 0 behaves as 1, not as fire-on-nothing", async () => {
|
|
1208
|
+
const rule: DetectionRule = buildRule({
|
|
1209
|
+
matchCountThreshold: 0,
|
|
1210
|
+
shouldWriteDetectionFinding: false,
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
const spies: EvaluationSpies = installSpies({
|
|
1214
|
+
groups: [buildGroup("alice", 1)],
|
|
1215
|
+
severities: [buildSeverity("0001", "Default")],
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
const result: DetectionRuleEvaluationResult =
|
|
1219
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1220
|
+
|
|
1221
|
+
expect(result.matchedGroups).toBe(1);
|
|
1222
|
+
expect(result.alertsCreated).toBe(1);
|
|
1223
|
+
// The clamp happens before the query, not just in the re-filter.
|
|
1224
|
+
expect(findMatchesArg(spies).minMatchCount).toBe(1);
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
test("a distinct-count rule fires on distinctCount, not matchCount", async () => {
|
|
1228
|
+
const rule: DetectionRule = buildRule({
|
|
1229
|
+
distinctCountField: "principalIp",
|
|
1230
|
+
matchCountThreshold: 5,
|
|
1231
|
+
shouldWriteDetectionFinding: false,
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
const spies: EvaluationSpies = installSpies({
|
|
1235
|
+
groups: [
|
|
1236
|
+
// 50 raw matches from only 2 IPs — noisy, not distributed.
|
|
1237
|
+
buildGroup("alice", 50, 2),
|
|
1238
|
+
// 6 raw matches from 5 IPs — under on raw count, fires on distinct.
|
|
1239
|
+
buildGroup("bob", 6, 5),
|
|
1240
|
+
],
|
|
1241
|
+
severities: [buildSeverity("0001", "Default")],
|
|
1242
|
+
});
|
|
1243
|
+
|
|
1244
|
+
const result: DetectionRuleEvaluationResult =
|
|
1245
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1246
|
+
|
|
1247
|
+
expect(result.matchedGroups).toBe(1);
|
|
1248
|
+
expect(result.totalMatches).toBe(6);
|
|
1249
|
+
expect(result.alertsCreated).toBe(1);
|
|
1250
|
+
expect(createdAlert(spies, 0).seriesFingerprint).toBe(
|
|
1251
|
+
expectedFingerprint("bob"),
|
|
1252
|
+
);
|
|
1253
|
+
});
|
|
1254
|
+
|
|
1255
|
+
test("a group with zero matches never fires even when distinctCount clears the threshold", async () => {
|
|
1256
|
+
const rule: DetectionRule = buildRule({
|
|
1257
|
+
distinctCountField: "principalIp",
|
|
1258
|
+
matchCountThreshold: 5,
|
|
1259
|
+
shouldWriteDetectionFinding: false,
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
const spies: EvaluationSpies = installSpies({
|
|
1263
|
+
// Nonsense a buggy query could emit: distinct 7 over 0 rows.
|
|
1264
|
+
groups: [buildGroup("alice", 0, 7)],
|
|
1265
|
+
severities: [buildSeverity("0001", "Default")],
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
const result: DetectionRuleEvaluationResult =
|
|
1269
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1270
|
+
|
|
1271
|
+
expect(result.matchedGroups).toBe(0);
|
|
1272
|
+
expect(result.totalMatches).toBe(0);
|
|
1273
|
+
expect(spies.alertCreate).not.toHaveBeenCalled();
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
test("a plain rule queries with a null distinct expression, its threshold, and the existing group contract", async () => {
|
|
1277
|
+
const rule: DetectionRule = buildRule({
|
|
1278
|
+
matchCountThreshold: 4,
|
|
1279
|
+
shouldWriteDetectionFinding: false,
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
const spies: EvaluationSpies = installSpies();
|
|
1283
|
+
|
|
1284
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1285
|
+
|
|
1286
|
+
const args: FindDetectionMatchesData = findMatchesArg(spies);
|
|
1287
|
+
|
|
1288
|
+
expect(args.projectId.toString()).toBe(PROJECT_ID.toString());
|
|
1289
|
+
expect(args.minMatchCount).toBe(4);
|
|
1290
|
+
expect(args.distinctCountExpression).toBeNull();
|
|
1291
|
+
// groupByField still compiles to an expression; maxGroups is intact.
|
|
1292
|
+
expect(args.groupByExpression).toBeInstanceOf(Statement);
|
|
1293
|
+
expect(args.maxGroups).toBe(100);
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
test("a distinct-count rule queries with a compiled Statement for the distinct field", async () => {
|
|
1297
|
+
const rule: DetectionRule = buildRule({
|
|
1298
|
+
distinctCountField: "principalIp",
|
|
1299
|
+
matchCountThreshold: 5,
|
|
1300
|
+
shouldWriteDetectionFinding: false,
|
|
1301
|
+
});
|
|
1302
|
+
|
|
1303
|
+
const spies: EvaluationSpies = installSpies();
|
|
1304
|
+
|
|
1305
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1306
|
+
|
|
1307
|
+
const args: FindDetectionMatchesData = findMatchesArg(spies);
|
|
1308
|
+
|
|
1309
|
+
expect(args.distinctCountExpression).toBeInstanceOf(Statement);
|
|
1310
|
+
expect(args.minMatchCount).toBe(5);
|
|
1311
|
+
expect(args.groupByExpression).toBeInstanceOf(Statement);
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
test("a distinct rule's alert description explains the distinct count and threshold", async () => {
|
|
1315
|
+
const rule: DetectionRule = buildRule({
|
|
1316
|
+
distinctCountField: "principalIp",
|
|
1317
|
+
matchCountThreshold: 5,
|
|
1318
|
+
shouldWriteDetectionFinding: false,
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
const spies: EvaluationSpies = installSpies({
|
|
1322
|
+
groups: [buildGroup("alice", 6, 5)],
|
|
1323
|
+
severities: [buildSeverity("0001", "Default")],
|
|
1324
|
+
});
|
|
1325
|
+
|
|
1326
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1327
|
+
|
|
1328
|
+
expect(createdAlert(spies, 0).description).toContain(
|
|
1329
|
+
"5 distinct principalIp values across these events (rule threshold: 5).",
|
|
1330
|
+
);
|
|
1331
|
+
});
|
|
1332
|
+
|
|
1333
|
+
test("a plain rule's alert description never says distinct", async () => {
|
|
1334
|
+
const rule: DetectionRule = buildRule({
|
|
1335
|
+
shouldWriteDetectionFinding: false,
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
const spies: EvaluationSpies = installSpies({
|
|
1339
|
+
groups: [buildGroup("alice", 6)],
|
|
1340
|
+
severities: [buildSeverity("0001", "Default")],
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1344
|
+
|
|
1345
|
+
expect(createdAlert(spies, 0).description).not.toContain("distinct");
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
test("a distinct rule's findings carry the distinct-count attribute and message suffix", async () => {
|
|
1349
|
+
const rule: DetectionRule = buildRule({
|
|
1350
|
+
distinctCountField: "principalIp",
|
|
1351
|
+
matchCountThreshold: 5,
|
|
1352
|
+
shouldCreateAlert: false,
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
const spies: EvaluationSpies = installSpies({
|
|
1356
|
+
groups: [buildGroup("alice", 6, 5)],
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1360
|
+
|
|
1361
|
+
const row: JSONObject = (
|
|
1362
|
+
spies.insertJsonRows.mock.calls[0]?.[0] as Array<JSONObject>
|
|
1363
|
+
)[0]!;
|
|
1364
|
+
|
|
1365
|
+
expect(
|
|
1366
|
+
(row["attributes"] as JSONObject)["oneuptime.detection.distinct_count"],
|
|
1367
|
+
).toBe("5");
|
|
1368
|
+
expect(row["message"]).toContain("(6 events, 5 distinct principalIp)");
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
test("a plain rule's findings carry no distinct-count attribute or message suffix", async () => {
|
|
1372
|
+
const rule: DetectionRule = buildRule({
|
|
1373
|
+
shouldCreateAlert: false,
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
const spies: EvaluationSpies = installSpies({
|
|
1377
|
+
groups: [buildGroup("alice", 6)],
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1381
|
+
|
|
1382
|
+
const row: JSONObject = (
|
|
1383
|
+
spies.insertJsonRows.mock.calls[0]?.[0] as Array<JSONObject>
|
|
1384
|
+
)[0]!;
|
|
1385
|
+
|
|
1386
|
+
/*
|
|
1387
|
+
* Absent, not "0": consumers filter on attribute presence, and a
|
|
1388
|
+
* plain rule has no distinct semantics to report.
|
|
1389
|
+
*/
|
|
1390
|
+
expect(
|
|
1391
|
+
Object.prototype.hasOwnProperty.call(
|
|
1392
|
+
row["attributes"] as JSONObject,
|
|
1393
|
+
"oneuptime.detection.distinct_count",
|
|
1394
|
+
),
|
|
1395
|
+
).toBe(false);
|
|
1396
|
+
expect(row["message"]).not.toContain("distinct");
|
|
1397
|
+
});
|
|
1398
|
+
|
|
1399
|
+
test("flagship: distinct usernames per source IP — one qualifying group, one alert, one finding", async () => {
|
|
1400
|
+
/*
|
|
1401
|
+
* The scenario the feature was built for: password spraying.
|
|
1402
|
+
* Group by attacker IP, count distinct usernames tried, fire at 5.
|
|
1403
|
+
* One IP hammered a single account 40 times (lockout noise, not a
|
|
1404
|
+
* spray); another tried 6 accounts across 9 events.
|
|
1405
|
+
*/
|
|
1406
|
+
const rule: DetectionRule = buildRule({
|
|
1407
|
+
groupByField: "principalIp",
|
|
1408
|
+
distinctCountField: "principalUser",
|
|
1409
|
+
matchCountThreshold: 5,
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
const spies: EvaluationSpies = installSpies({
|
|
1413
|
+
groups: [
|
|
1414
|
+
buildGroup("203.0.113.7", 9, 6),
|
|
1415
|
+
buildGroup("198.51.100.4", 40, 1),
|
|
1416
|
+
],
|
|
1417
|
+
severities: [buildSeverity("0001", "High")],
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
const result: DetectionRuleEvaluationResult =
|
|
1421
|
+
await DetectionRuleEvaluator.evaluateRule(rule);
|
|
1422
|
+
|
|
1423
|
+
expect(result.matchedGroups).toBe(1);
|
|
1424
|
+
expect(result.totalMatches).toBe(9);
|
|
1425
|
+
expect(result.alertsCreated).toBe(1);
|
|
1426
|
+
expect(result.findingsWritten).toBe(1);
|
|
1427
|
+
expect(result.error).toBeNull();
|
|
1428
|
+
|
|
1429
|
+
// The query was built from the rule's three knobs.
|
|
1430
|
+
const args: FindDetectionMatchesData = findMatchesArg(spies);
|
|
1431
|
+
expect(args.groupByExpression).toBeInstanceOf(Statement);
|
|
1432
|
+
expect(args.distinctCountExpression).toBeInstanceOf(Statement);
|
|
1433
|
+
expect(args.minMatchCount).toBe(5);
|
|
1434
|
+
|
|
1435
|
+
const alert: Alert = createdAlert(spies, 0);
|
|
1436
|
+
|
|
1437
|
+
expect(alert.title).toContain("203.0.113.7");
|
|
1438
|
+
expect(alert.seriesFingerprint).toBe(expectedFingerprint("203.0.113.7"));
|
|
1439
|
+
expect(alert.description).toContain(
|
|
1440
|
+
"6 distinct principalUser values across these events (rule threshold: 5).",
|
|
1441
|
+
);
|
|
1442
|
+
|
|
1443
|
+
const rows: Array<JSONObject> = spies.insertJsonRows.mock
|
|
1444
|
+
.calls[0]?.[0] as Array<JSONObject>;
|
|
1445
|
+
|
|
1446
|
+
expect(rows).toHaveLength(1);
|
|
1447
|
+
|
|
1448
|
+
const row: JSONObject = rows[0]!;
|
|
1449
|
+
const attributes: JSONObject = row["attributes"] as JSONObject;
|
|
1450
|
+
|
|
1451
|
+
expect(attributes["oneuptime.detection.group_value"]).toBe("203.0.113.7");
|
|
1452
|
+
expect(attributes["oneuptime.detection.match_count"]).toBe("9");
|
|
1453
|
+
expect(attributes["oneuptime.detection.distinct_count"]).toBe("6");
|
|
1454
|
+
expect(row["message"]).toContain(
|
|
1455
|
+
"Detection: Brute Force Watch — 203.0.113.7 (9 events, 6 distinct principalUser)",
|
|
1456
|
+
);
|
|
1457
|
+
});
|
|
1458
|
+
});
|
|
1098
1459
|
});
|