@oneuptime/common 13.0.7 → 13.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.ts +1 -1
  2. package/Models/DatabaseModels/ProjectOnCallCalendarFeed.ts +1 -1
  3. package/Models/DatabaseModels/UserOnCallCalendarFeed.ts +1 -1
  4. package/Server/API/AIBillingAPI.ts +1 -0
  5. package/Server/API/AIInsightAPI.ts +1 -4
  6. package/Server/API/AIInvestigationAPI.ts +1 -4
  7. package/Server/API/AIReadinessAPI.ts +4 -5
  8. package/Server/API/AlertAPI.ts +1 -0
  9. package/Server/API/AutoRemediationAPI.ts +1 -4
  10. package/Server/API/BaseAPI.ts +13 -0
  11. package/Server/API/BaseAnalyticsAPI.ts +80 -9
  12. package/Server/API/BillingAPI.ts +2 -0
  13. package/Server/API/BillingInvoiceAPI.ts +1 -0
  14. package/Server/API/BillingPaymentMethodAPI.ts +2 -0
  15. package/Server/API/CodeFixRunAPI.ts +1 -3
  16. package/Server/API/CommonAPI.ts +68 -4
  17. package/Server/API/DashboardAPI.ts +12 -12
  18. package/Server/API/GlobalConfigAPI.ts +17 -0
  19. package/Server/API/IncidentAPI.ts +2 -0
  20. package/Server/API/IncidentEpisodeAPI.ts +1 -0
  21. package/Server/API/NotificationAPI.ts +1 -0
  22. package/Server/API/OnCallCalendarAPI.ts +4 -5
  23. package/Server/API/OnCallDutyPolicyAPI.ts +7 -0
  24. package/Server/API/OnCallReadinessAPI.ts +4 -1
  25. package/Server/API/OnCallScheduleTimelineAPI.ts +731 -0
  26. package/Server/API/ProjectAPI.ts +1 -0
  27. package/Server/API/ScheduledMaintenanceAPI.ts +1 -0
  28. package/Server/API/StatusPageAPI.ts +161 -32
  29. package/Server/API/TeamMemberAPI.ts +3 -0
  30. package/Server/API/UserCallAPI.ts +2 -0
  31. package/Server/API/UserEmailAPI.ts +2 -0
  32. package/Server/API/UserIncomingCallNumberAPI.ts +2 -0
  33. package/Server/API/UserMicrosoftTeamsAPI.ts +1 -0
  34. package/Server/API/UserSlackAPI.ts +1 -0
  35. package/Server/API/UserSmsAPI.ts +2 -0
  36. package/Server/API/UserTotpAuthAPI.ts +1 -0
  37. package/Server/API/UserWebAuthnAPI.ts +2 -0
  38. package/Server/API/UserWebhookAPI.ts +1 -0
  39. package/Server/API/UserWhatsAppAPI.ts +2 -0
  40. package/Server/Middleware/MasterAdminAuthorization.ts +13 -2
  41. package/Server/Middleware/UserAuthorization.ts +79 -5
  42. package/Server/Services/ApiKeyPermissionService.ts +101 -4
  43. package/Server/Services/DatabaseService.ts +245 -0
  44. package/Server/Services/DetectionRuleService.ts +5 -4
  45. package/Server/Services/OnCallDutyPolicyChildService.ts +261 -0
  46. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +2 -2
  47. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +2 -2
  48. package/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.ts +2 -2
  49. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +2 -2
  50. package/Server/Services/PayAsYouGoBillingService.ts +23 -28
  51. package/Server/Services/ProjectService.ts +18 -0
  52. package/Server/Services/RumSessionPinService.ts +274 -22
  53. package/Server/Services/TeamPermissionService.ts +120 -26
  54. package/Server/Services/ThreatIntelFeedService.ts +6 -3
  55. package/Server/Types/Database/Permissions/BasePermission.ts +9 -7
  56. package/Server/Types/Database/Permissions/Index.ts +75 -11
  57. package/Server/Types/Database/Permissions/TablePermission.ts +24 -28
  58. package/Server/Utils/AI/AIChatPrivacyFilter.ts +8 -0
  59. package/Server/Utils/AI/AIRunPrivacyFilter.ts +8 -0
  60. package/Server/Utils/MasterAdminPageAccess.ts +61 -0
  61. package/Server/Utils/Monitor/MonitorAlert.ts +3 -1
  62. package/Server/Utils/Monitor/MonitorIncident.ts +3 -1
  63. package/Server/Utils/OnCall/OnCallCalendarFeedRenderer.ts +1 -1
  64. package/Server/Utils/Realtime.ts +163 -45
  65. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +11 -28
  66. package/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.ts +170 -0
  67. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +1 -1
  68. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.ts +6 -2
  69. package/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.ts +10 -9
  70. package/Tests/App/Dashboard/AffectedResourcesPicker.test.tsx +1059 -0
  71. package/Tests/App/Dashboard/AffectedResourcesPickerEditForm.test.tsx +376 -0
  72. package/Tests/App/Dashboard/AffectedResourcesPickerHiddenTypes.test.tsx +757 -0
  73. package/Tests/App/Dashboard/AffectedResourcesSummaryStep.test.tsx +307 -0
  74. package/Tests/App/Dashboard/ArgumentsFormGroupByUnlistedAttributes.test.tsx +131 -0
  75. package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +1 -1
  76. package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +9 -1
  77. package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +24 -0
  78. package/Tests/App/Dashboard/DashboardVariableSelector.test.tsx +162 -8
  79. package/Tests/App/Dashboard/EntityFilterDropdownArchivedSlos.test.tsx +105 -6
  80. package/Tests/App/Dashboard/EntityFilterDropdownUnlistedSelection.test.tsx +434 -0
  81. package/Tests/App/Dashboard/EscalationRuleReadiness.test.tsx +276 -1
  82. package/Tests/App/Dashboard/OnCallCalendarFeedSideMenus.test.tsx +9 -1
  83. package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +58 -42
  84. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +42 -5
  85. package/Tests/App/Dashboard/ResourceAuditLogsSideMenus.test.tsx +34 -23
  86. package/Tests/App/Dashboard/ScheduleTimeline.test.tsx +1580 -0
  87. package/Tests/App/Dashboard/ScheduleTimelineAPI.test.ts +280 -0
  88. package/Tests/App/Dashboard/ScheduleTimelineModel.test.ts +673 -0
  89. package/Tests/App/Dashboard/SecurityEventConnectionDiagnostics.test.tsx +95 -68
  90. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +86 -3
  91. package/Tests/App/Dashboard/SecurityEventsEmptyState.test.tsx +28 -3
  92. package/Tests/App/Dashboard/SecurityEventsExplorer.test.tsx +761 -0
  93. package/Tests/App/Dashboard/SecurityEventsGuideContent.test.ts +1313 -0
  94. package/Tests/App/Dashboard/SecurityEventsHowItWorksCard.test.tsx +536 -0
  95. package/Tests/App/Dashboard/SecurityEventsNoResults.test.tsx +222 -0
  96. package/Tests/App/Dashboard/SecurityEventsThreatIntelPage.test.tsx +199 -0
  97. package/Tests/App/Dashboard/SecurityEventsVolumeChart.test.tsx +321 -0
  98. package/Tests/App/Dashboard/SloOverviewHero.test.tsx +171 -36
  99. package/Tests/App/Dashboard/SloViewSideMenu.test.tsx +215 -0
  100. package/Tests/App/Dashboard/SloViewSideMenuCounts.test.tsx +40 -3
  101. package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +12 -4
  102. package/Tests/App/PublicDashboard/PublicDashboardWidgetListAuth.test.tsx +623 -0
  103. package/Tests/Models/OnCallCalendarFeedModels.test.ts +23 -1
  104. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +138 -5
  105. package/Tests/Server/API/AIGenerationProjectAIToggle.test.ts +187 -0
  106. package/Tests/Server/API/AIInvestigationAvailabilityAuthorization.test.ts +51 -3
  107. package/Tests/Server/API/AIInvestigationEvidenceAPI.test.ts +55 -2
  108. package/Tests/Server/API/BaseAnalyticsAPIAggregateCache.test.ts +729 -0
  109. package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +93 -7
  110. package/Tests/Server/API/BillingPaymentMethodAPI.test.ts +36 -3
  111. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +1003 -24
  112. package/Tests/Server/API/DashboardPublicRateLimit.test.ts +6 -3
  113. package/Tests/Server/API/ExpiredSessionAIRoutes.test.ts +708 -0
  114. package/Tests/Server/API/ExpiredSessionCustomRoutes.test.ts +928 -0
  115. package/Tests/Server/API/ExpiredSessionMonitorTemplate.test.ts +319 -0
  116. package/Tests/Server/API/ExpiredSessionOnCallDutyPolicy.test.ts +336 -0
  117. package/Tests/Server/API/ExpiredSessionRouteGuards.test.ts +820 -0
  118. package/Tests/Server/API/GlobalConfigLicense.test.ts +140 -0
  119. package/Tests/Server/API/NotificationChannelVerificationAPI.test.ts +115 -12
  120. package/Tests/Server/API/OnCallCalendarAPI.test.ts +375 -8
  121. package/Tests/Server/API/OnCallReadinessAPI.test.ts +175 -17
  122. package/Tests/Server/API/OnCallScheduleTimelineAPI.test.ts +1513 -0
  123. package/Tests/Server/API/OnCallSetupReminderAPI.test.ts +119 -7
  124. package/Tests/Server/API/ProjectScopedCustomRouteAuth.test.ts +111 -4
  125. package/Tests/Server/API/PublicRouteInvalidAccessToken.test.ts +615 -0
  126. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +89 -2
  127. package/Tests/Server/API/SessionReplayAPI.test.ts +121 -0
  128. package/Tests/Server/API/StatusPageMasterPasswordRateLimit.test.ts +4 -2
  129. package/Tests/Server/API/StatusPagePostmortemVisibility.test.ts +441 -0
  130. package/Tests/Server/API/StatusPageTestEmailReportAuth.test.ts +664 -0
  131. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +7 -0
  132. package/Tests/Server/API/UserIncomingCallNumberApi.test.ts +6 -0
  133. package/Tests/Server/API/UserNotificationSettingAPI.test.ts +65 -4
  134. package/Tests/Server/API/UserProjectsAPI.test.ts +63 -3
  135. package/Tests/Server/API/UserTotpAuthAPI.test.ts +48 -2
  136. package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +11 -2
  137. package/Tests/Server/API/UserWebAuthnBackupCodeMinting.test.ts +46 -3
  138. package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +18 -2
  139. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +24 -7
  140. package/Tests/Server/Middleware/ExpiredSessionUserMiddleware.test.ts +563 -0
  141. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +299 -19
  142. package/Tests/Server/Middleware/PublicRouteUserMiddleware.test.ts +453 -0
  143. package/Tests/Server/Services/AIChatPrivacyPin.test.ts +24 -0
  144. package/Tests/Server/Services/ApiKeyPermissionSecurity.test.ts +2 -0
  145. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +3 -3
  146. package/Tests/Server/Services/DatabaseServiceCreateSuppliedId.test.ts +147 -0
  147. package/Tests/Server/Services/DatabaseServiceLoginBeforeHooks.test.ts +1078 -0
  148. package/Tests/Server/Services/DatabaseServiceTenantRelationGuard.test.ts +259 -0
  149. package/Tests/Server/Services/DatabaseServiceWriteOperatorValues.test.ts +147 -0
  150. package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +115 -1
  151. package/Tests/Server/Services/OnCallChildCreateAuthorizationPostgres.test.ts +711 -0
  152. package/Tests/Server/Services/OnCallDutyPolicyChildService.test.ts +733 -0
  153. package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +190 -140
  154. package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +4 -9
  155. package/Tests/Server/Services/PermissionBlockDeletion.test.ts +780 -0
  156. package/Tests/Server/Services/PermissionBlockDeletionPostgres.test.ts +628 -0
  157. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +77 -1
  158. package/Tests/Server/Services/RumSessionPinCreatePermission.test.ts +715 -0
  159. package/Tests/Server/Services/TeamMemberMultiTenantReadPostgres.test.ts +394 -0
  160. package/Tests/Server/Services/TeamPrivilegeEscalation.test.ts +6 -0
  161. package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +131 -0
  162. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +13 -2
  163. package/Tests/Server/Types/Database/Permissions/ModelPermissionAnonymousRefusal.test.ts +518 -0
  164. package/Tests/Server/Types/Database/Permissions/MultiTenantPermissions.test.ts +350 -0
  165. package/Tests/Server/Types/Database/Permissions/OnCallCalendarFeedPermissions.test.ts +282 -25
  166. package/Tests/Server/Types/Database/Permissions/TableLevelBlockPermission.test.ts +459 -0
  167. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +101 -9
  168. package/Tests/Server/Utils/AI/ChatAgentHelpers.test.ts +88 -4
  169. package/Tests/Server/Utils/AI/Toolbox/ExceptionTools.test.ts +459 -0
  170. package/Tests/Server/Utils/AI/Toolbox/LogTools.test.ts +794 -0
  171. package/Tests/Server/Utils/AI/Toolbox/RecentChangesTools.test.ts +574 -0
  172. package/Tests/Server/Utils/AI/Toolbox/SecurityEventTools.test.ts +596 -0
  173. package/Tests/Server/Utils/CaptchaRequestAndLogging.test.ts +395 -0
  174. package/Tests/Server/Utils/MasterAdminPageAccess.test.ts +251 -0
  175. package/Tests/Server/Utils/Monitor/MonitorActionTimestamps.test.ts +616 -0
  176. package/Tests/Server/Utils/Realtime.test.ts +502 -0
  177. package/Tests/Server/Utils/SecurityEvent/Connectors/GoogleSecOps/GoogleSecOpsConnectorSeverity.test.ts +216 -0
  178. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +142 -0
  179. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.test.ts +140 -0
  180. package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepairClickhouseIntegration.test.ts +542 -0
  181. package/Tests/Server/Utils/Telemetry/ChangeEventRow.test.ts +743 -0
  182. package/Tests/Server/Utils/Telemetry/RuntimeMetrics.test.ts +465 -0
  183. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Alert.test.ts +202 -0
  184. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/Incident.test.ts +194 -0
  185. package/Tests/Server/Utils/Workspace/MicrosoftTeams/Messages/ScheduledMaintenance.test.ts +231 -0
  186. package/Tests/Server/Utils/Workspace/Slack/Messages/Alert.test.ts +194 -0
  187. package/Tests/Server/Utils/Workspace/Slack/Messages/AlertEpisode.test.ts +194 -0
  188. package/Tests/Server/Utils/Workspace/Slack/Messages/Incident.test.ts +194 -0
  189. package/Tests/Server/Utils/Workspace/Slack/Messages/IncidentEpisode.test.ts +194 -0
  190. package/Tests/Server/Utils/Workspace/Slack/Messages/ScheduledMaintenance.test.ts +247 -0
  191. package/Tests/Types/AI/AIInsightEvidence.test.ts +206 -0
  192. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtilAnonymous.test.ts +144 -0
  193. package/Tests/Types/Call/CallProvider.test.ts +528 -0
  194. package/Tests/Types/Kubernetes/KubernetesCostIngest.test.ts +236 -0
  195. package/Tests/Types/OnCallDutyPolicy/ScheduleTimeline.test.ts +736 -0
  196. package/Tests/Types/OnCallDutyPolicy/ScheduleTimelineLayout.test.ts +996 -0
  197. package/Tests/Types/SecurityEvent/SigmaRule.test.ts +142 -0
  198. package/Tests/Types/Workflow/Components/JSON.test.ts +182 -0
  199. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +116 -1
  200. package/Tests/UI/Components/Dropdown/DropdownAsyncOptions.test.tsx +81 -0
  201. package/Tests/UI/Components/EditionLabelLicenseRefresh.test.tsx +89 -1
  202. package/Tests/UI/Components/EventAttachmentList.test.tsx +174 -0
  203. package/Tests/UI/Components/Forms/BasicFormBurnRateTemplateDefaults.test.tsx +395 -0
  204. package/Tests/UI/Components/Forms/BasicFormCollapsibleSections.test.tsx +286 -0
  205. package/Tests/UI/Components/Forms/FormSummary.test.tsx +128 -0
  206. package/Tests/UI/Components/ModelTable/BaseModelTableQueryChange.test.tsx +365 -0
  207. package/Tests/UI/Components/OpenAuthenticatedUrl.test.ts +358 -0
  208. package/Tests/UI/Components/SessionAwareImage.test.tsx +496 -0
  209. package/Tests/UI/Components/TelemetryHistogramZeroBuckets.test.tsx +190 -0
  210. package/Tests/UI/Components/TooltipInteractive.test.tsx +97 -0
  211. package/Tests/UI/MicrosoftTeams/MicrosoftTeamsChatsCardInstallGuidance.test.tsx +1 -1
  212. package/Tests/UI/Monitor/EvaluationLogListTimestamps.test.tsx +367 -0
  213. package/Tests/UI/Utils/API/BaseAPIRefreshSession.test.ts +1087 -0
  214. package/Tests/UI/Utils/API/DashboardHost.ts +12 -0
  215. package/Tests/UI/Utils/API/FakeAxiosServer.ts +183 -0
  216. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +10 -0
  217. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
  218. package/Tests/UI/Utils/Cookie.test.ts +440 -0
  219. package/Tests/UI/Utils/ModelAPIRequestClient.test.ts +278 -0
  220. package/Tests/UI/Utils/Navigation.test.ts +33 -0
  221. package/Tests/UI/Utils/PricingPlan.test.ts +295 -0
  222. package/Tests/UI/Utils/Realtime.test.ts +759 -0
  223. package/Tests/UI/Utils/Telemetry/Telemetry.test.ts +500 -0
  224. package/Tests/Utils/APIInstanceDispatch.test.ts +311 -0
  225. package/Tests/Utils/SecurityEvent/GoogleSecOpsAlertNormalizerSeverity.test.ts +565 -0
  226. package/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.ts +40 -0
  227. package/Types/Monitor/MonitorEvaluationSummary.ts +3 -0
  228. package/Types/OnCallDutyPolicy/ScheduleTimeline.ts +507 -0
  229. package/Types/OnCallDutyPolicy/ScheduleTimelineLayout.ts +589 -0
  230. package/Types/Realtime/EventName.ts +11 -0
  231. package/Types/SecurityEvent/DetectionFindingConstants.ts +14 -0
  232. package/Types/SecurityEvent/OcsfSeverity.ts +31 -26
  233. package/Types/SecurityEvent/SigmaRule.ts +51 -0
  234. package/Types/SecurityEvent/ThreatIntelConstants.ts +18 -0
  235. package/UI/Components/AttachmentList/EventAttachmentList.tsx +30 -2
  236. package/UI/Components/Dashboard/DashboardVariableControl.tsx +12 -2
  237. package/UI/Components/Dropdown/Dropdown.tsx +6 -1
  238. package/UI/Components/EditionLabel/EditionLabel.tsx +12 -0
  239. package/UI/Components/EventItem/EventItem.tsx +12 -1
  240. package/UI/Components/Forms/BasicForm.tsx +151 -126
  241. package/UI/Components/Forms/CollapsibleFormSection.tsx +63 -0
  242. package/UI/Components/Forms/Fields/FormField.tsx +10 -4
  243. package/UI/Components/Forms/FormSummary.tsx +7 -0
  244. package/UI/Components/Forms/Types/Field.ts +10 -0
  245. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +2 -1
  246. package/UI/Components/Markdown.tsx/SessionAwareImage.tsx +122 -0
  247. package/UI/Components/ModelTable/BaseModelTable.tsx +34 -5
  248. package/UI/Components/ModelTable/TableView.tsx +1 -1
  249. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +8 -1
  250. package/UI/Components/Tooltip/Tooltip.tsx +8 -1
  251. package/UI/Utils/API/API.ts +173 -12
  252. package/UI/Utils/API/RequestOptions.ts +11 -0
  253. package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +31 -7
  254. package/UI/Utils/ModelAPI/ModelAPI.ts +30 -9
  255. package/UI/Utils/Navigation.ts +9 -0
  256. package/UI/Utils/OpenAuthenticatedUrl.ts +154 -0
  257. package/UI/Utils/Realtime.ts +176 -30
  258. package/Utils/API.ts +14 -5
  259. package/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.ts +157 -7
  260. package/Utils/SecurityEvent/Sigma/SigmaRuleParser.ts +9 -17
  261. package/Utils/Slo/SloBurnRateTemplate.ts +2 -2
  262. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js +1 -1
  263. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleCalendarFeed.js.map +1 -1
  264. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js +1 -1
  265. package/build/dist/Models/DatabaseModels/ProjectOnCallCalendarFeed.js.map +1 -1
  266. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js +1 -1
  267. package/build/dist/Models/DatabaseModels/UserOnCallCalendarFeed.js.map +1 -1
  268. package/build/dist/Server/API/AIBillingAPI.js +1 -1
  269. package/build/dist/Server/API/AIBillingAPI.js.map +1 -1
  270. package/build/dist/Server/API/AIInsightAPI.js +1 -4
  271. package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
  272. package/build/dist/Server/API/AIInvestigationAPI.js +1 -4
  273. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
  274. package/build/dist/Server/API/AIReadinessAPI.js +1 -3
  275. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -1
  276. package/build/dist/Server/API/AlertAPI.js +1 -1
  277. package/build/dist/Server/API/AlertAPI.js.map +1 -1
  278. package/build/dist/Server/API/AutoRemediationAPI.js +1 -4
  279. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  280. package/build/dist/Server/API/BaseAPI.js +12 -0
  281. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  282. package/build/dist/Server/API/BaseAnalyticsAPI.js +55 -10
  283. package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
  284. package/build/dist/Server/API/BillingAPI.js +2 -2
  285. package/build/dist/Server/API/BillingAPI.js.map +1 -1
  286. package/build/dist/Server/API/BillingInvoiceAPI.js +1 -1
  287. package/build/dist/Server/API/BillingInvoiceAPI.js.map +1 -1
  288. package/build/dist/Server/API/BillingPaymentMethodAPI.js +2 -2
  289. package/build/dist/Server/API/BillingPaymentMethodAPI.js.map +1 -1
  290. package/build/dist/Server/API/CodeFixRunAPI.js +1 -3
  291. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  292. package/build/dist/Server/API/CommonAPI.js +50 -5
  293. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  294. package/build/dist/Server/API/DashboardAPI.js +12 -12
  295. package/build/dist/Server/API/DashboardAPI.js.map +1 -1
  296. package/build/dist/Server/API/GlobalConfigAPI.js +12 -0
  297. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  298. package/build/dist/Server/API/IncidentAPI.js +2 -2
  299. package/build/dist/Server/API/IncidentAPI.js.map +1 -1
  300. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  301. package/build/dist/Server/API/IncidentEpisodeAPI.js.map +1 -1
  302. package/build/dist/Server/API/NotificationAPI.js +1 -1
  303. package/build/dist/Server/API/NotificationAPI.js.map +1 -1
  304. package/build/dist/Server/API/OnCallCalendarAPI.js +1 -4
  305. package/build/dist/Server/API/OnCallCalendarAPI.js.map +1 -1
  306. package/build/dist/Server/API/OnCallDutyPolicyAPI.js +6 -0
  307. package/build/dist/Server/API/OnCallDutyPolicyAPI.js.map +1 -1
  308. package/build/dist/Server/API/OnCallReadinessAPI.js +4 -1
  309. package/build/dist/Server/API/OnCallReadinessAPI.js.map +1 -1
  310. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js +494 -0
  311. package/build/dist/Server/API/OnCallScheduleTimelineAPI.js.map +1 -0
  312. package/build/dist/Server/API/ProjectAPI.js +1 -1
  313. package/build/dist/Server/API/ProjectAPI.js.map +1 -1
  314. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  315. package/build/dist/Server/API/ScheduledMaintenanceAPI.js.map +1 -1
  316. package/build/dist/Server/API/StatusPageAPI.js +125 -33
  317. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  318. package/build/dist/Server/API/TeamMemberAPI.js +3 -3
  319. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  320. package/build/dist/Server/API/UserCallAPI.js +2 -2
  321. package/build/dist/Server/API/UserCallAPI.js.map +1 -1
  322. package/build/dist/Server/API/UserEmailAPI.js +2 -2
  323. package/build/dist/Server/API/UserEmailAPI.js.map +1 -1
  324. package/build/dist/Server/API/UserIncomingCallNumberAPI.js +2 -2
  325. package/build/dist/Server/API/UserIncomingCallNumberAPI.js.map +1 -1
  326. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +1 -1
  327. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -1
  328. package/build/dist/Server/API/UserSlackAPI.js +1 -1
  329. package/build/dist/Server/API/UserSlackAPI.js.map +1 -1
  330. package/build/dist/Server/API/UserSmsAPI.js +2 -2
  331. package/build/dist/Server/API/UserSmsAPI.js.map +1 -1
  332. package/build/dist/Server/API/UserTotpAuthAPI.js +1 -1
  333. package/build/dist/Server/API/UserTotpAuthAPI.js.map +1 -1
  334. package/build/dist/Server/API/UserWebAuthnAPI.js +2 -2
  335. package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
  336. package/build/dist/Server/API/UserWebhookAPI.js +1 -1
  337. package/build/dist/Server/API/UserWebhookAPI.js.map +1 -1
  338. package/build/dist/Server/API/UserWhatsAppAPI.js +2 -2
  339. package/build/dist/Server/API/UserWhatsAppAPI.js.map +1 -1
  340. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +11 -2
  341. package/build/dist/Server/Middleware/MasterAdminAuthorization.js.map +1 -1
  342. package/build/dist/Server/Middleware/UserAuthorization.js +64 -4
  343. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  344. package/build/dist/Server/Services/ApiKeyPermissionService.js +80 -4
  345. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  346. package/build/dist/Server/Services/DatabaseService.js +171 -0
  347. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  348. package/build/dist/Server/Services/DetectionRuleService.js +4 -4
  349. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  350. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js +170 -0
  351. package/build/dist/Server/Services/OnCallDutyPolicyChildService.js.map +1 -0
  352. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +2 -2
  353. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  354. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +2 -2
  355. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  356. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js +2 -2
  357. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleTeamService.js.map +1 -1
  358. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +2 -2
  359. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  360. package/build/dist/Server/Services/PayAsYouGoBillingService.js +20 -27
  361. package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -1
  362. package/build/dist/Server/Services/ProjectService.js +17 -0
  363. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  364. package/build/dist/Server/Services/RumSessionPinService.js +180 -16
  365. package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
  366. package/build/dist/Server/Services/TeamPermissionService.js +89 -27
  367. package/build/dist/Server/Services/TeamPermissionService.js.map +1 -1
  368. package/build/dist/Server/Services/ThreatIntelFeedService.js +5 -4
  369. package/build/dist/Server/Services/ThreatIntelFeedService.js.map +1 -1
  370. package/build/dist/Server/Types/Database/Permissions/BasePermission.js +9 -6
  371. package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
  372. package/build/dist/Server/Types/Database/Permissions/Index.js +60 -6
  373. package/build/dist/Server/Types/Database/Permissions/Index.js.map +1 -1
  374. package/build/dist/Server/Types/Database/Permissions/TablePermission.js +14 -13
  375. package/build/dist/Server/Types/Database/Permissions/TablePermission.js.map +1 -1
  376. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js +7 -0
  377. package/build/dist/Server/Utils/AI/AIChatPrivacyFilter.js.map +1 -1
  378. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +7 -0
  379. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -1
  380. package/build/dist/Server/Utils/MasterAdminPageAccess.js +39 -0
  381. package/build/dist/Server/Utils/MasterAdminPageAccess.js.map +1 -0
  382. package/build/dist/Server/Utils/Monitor/MonitorAlert.js +3 -1
  383. package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
  384. package/build/dist/Server/Utils/Monitor/MonitorIncident.js +3 -1
  385. package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
  386. package/build/dist/Server/Utils/Realtime.js +126 -37
  387. package/build/dist/Server/Utils/Realtime.js.map +1 -1
  388. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +7 -25
  389. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  390. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js +127 -0
  391. package/build/dist/Server/Utils/SecurityEvent/GoogleSecOpsSeverityRepair.js.map +1 -0
  392. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +1 -1
  393. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  394. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js +3 -2
  395. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelFeedPoller.js.map +1 -1
  396. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js +4 -8
  397. package/build/dist/Server/Utils/SecurityEvent/ThreatIntel/ThreatIntelMatcher.js.map +1 -1
  398. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js +31 -1
  399. package/build/dist/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.js.map +1 -1
  400. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js +321 -0
  401. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimeline.js.map +1 -0
  402. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js +372 -0
  403. package/build/dist/Types/OnCallDutyPolicy/ScheduleTimelineLayout.js.map +1 -0
  404. package/build/dist/Types/Realtime/EventName.js +10 -0
  405. package/build/dist/Types/Realtime/EventName.js.map +1 -1
  406. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +11 -0
  407. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  408. package/build/dist/Types/SecurityEvent/OcsfSeverity.js +30 -25
  409. package/build/dist/Types/SecurityEvent/OcsfSeverity.js.map +1 -1
  410. package/build/dist/Types/SecurityEvent/SigmaRule.js +47 -0
  411. package/build/dist/Types/SecurityEvent/SigmaRule.js.map +1 -1
  412. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js +14 -0
  413. package/build/dist/Types/SecurityEvent/ThreatIntelConstants.js.map +1 -1
  414. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js +9 -4
  415. package/build/dist/UI/Components/AttachmentList/EventAttachmentList.js.map +1 -1
  416. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +10 -2
  417. package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -1
  418. package/build/dist/UI/Components/Dropdown/Dropdown.js +5 -1
  419. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  420. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +11 -0
  421. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  422. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  423. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  424. package/build/dist/UI/Components/Forms/BasicForm.js +70 -64
  425. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  426. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js +27 -0
  427. package/build/dist/UI/Components/Forms/CollapsibleFormSection.js.map +1 -0
  428. package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -7
  429. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  430. package/build/dist/UI/Components/Forms/FormSummary.js +7 -0
  431. package/build/dist/UI/Components/Forms/FormSummary.js.map +1 -1
  432. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +2 -1
  433. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  434. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js +84 -0
  435. package/build/dist/UI/Components/Markdown.tsx/SessionAwareImage.js.map +1 -0
  436. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +14 -1
  437. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  438. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  439. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  440. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -1
  441. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  442. package/build/dist/UI/Components/Tooltip/Tooltip.js +2 -1
  443. package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
  444. package/build/dist/UI/Utils/API/API.js +143 -19
  445. package/build/dist/UI/Utils/API/API.js.map +1 -1
  446. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +21 -7
  447. package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
  448. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +26 -9
  449. package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
  450. package/build/dist/UI/Utils/Navigation.js +8 -0
  451. package/build/dist/UI/Utils/Navigation.js.map +1 -1
  452. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js +92 -0
  453. package/build/dist/UI/Utils/OpenAuthenticatedUrl.js.map +1 -0
  454. package/build/dist/UI/Utils/Realtime.js +120 -15
  455. package/build/dist/UI/Utils/Realtime.js.map +1 -1
  456. package/build/dist/Utils/API.js +13 -5
  457. package/build/dist/Utils/API.js.map +1 -1
  458. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js +123 -6
  459. package/build/dist/Utils/SecurityEvent/GoogleSecOpsAlertNormalizer.js.map +1 -1
  460. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js +4 -18
  461. package/build/dist/Utils/SecurityEvent/Sigma/SigmaRuleParser.js.map +1 -1
  462. package/build/dist/Utils/Slo/SloBurnRateTemplate.js +2 -2
  463. package/package.json +1 -1
@@ -0,0 +1,1313 @@
1
+ /*
2
+ * The alerting module pulls the native isolated-vm addon through its
3
+ * template renderer; stub it out before anything imports it.
4
+ */
5
+ jest.mock("isolated-vm", () => {
6
+ return {};
7
+ });
8
+
9
+ import { describe, expect, test } from "@jest/globals";
10
+ import DetectionRulesGuide, {
11
+ DETECTION_RULE_EXAMPLE_YAML,
12
+ DETECTION_THRESHOLD_EXAMPLES,
13
+ DetectionThresholdExample,
14
+ SIGMA_COLUMN_DOCS,
15
+ SIGMA_FIELD_ALIAS_DOCS,
16
+ SIGMA_MODIFIER_DOCS,
17
+ SIGMA_SELECTIONS_EXAMPLE_YAML,
18
+ SigmaFieldDoc,
19
+ SigmaModifierDoc,
20
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/HowItWorks/DetectionRulesGuide";
21
+ import ThreatIntelGuide, {
22
+ INDICATOR_TYPE_DOCS,
23
+ SUPPORTED_COMBINED_PATTERN_EXAMPLES,
24
+ THREAT_INTEL_SIGMA_EXAMPLE_YAML,
25
+ THREAT_LEVEL_BANDS,
26
+ ThreatLevelBand,
27
+ UNSCORED_CONFIDENCE,
28
+ UNSUPPORTED_PATTERN_EXAMPLES,
29
+ formatBand,
30
+ threatLevelForBand,
31
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/HowItWorks/ThreatIntelGuide";
32
+ import {
33
+ SecurityEventsGuide,
34
+ SecurityEventsGuideLevel,
35
+ SecurityEventsGuideSection,
36
+ getGuideSection,
37
+ guideToMarkdown,
38
+ markdownTable,
39
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/SecurityEvents/HowItWorks/SecurityEventsGuide";
40
+ import SigmaRuleParser from "../../../Utils/SecurityEvent/Sigma/SigmaRuleParser";
41
+ import SigmaClickhouseCompiler, {
42
+ FIELD_ALIASES,
43
+ buildSigmaDistinctCountExpression,
44
+ buildSigmaFieldExpression,
45
+ resolveSigmaField,
46
+ } from "../../../Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler";
47
+ import { pickSeverityByPrecedence } from "../../../Server/Utils/SecurityEvent/SecurityEventAlerting";
48
+ import { Statement } from "../../../Server/Utils/AnalyticsDatabase/Statement";
49
+ import StixPatternParser, {
50
+ ParsedIndicatorValue,
51
+ } from "../../../Utils/SecurityEvent/ThreatIntel/StixPatternParser";
52
+ import SigmaRule, {
53
+ SIGMA_DEFAULT_LEVEL,
54
+ SIGMA_LEVEL_TO_OCSF_SEVERITY,
55
+ SIGMA_SUPPORTED_MODIFIERS,
56
+ SigmaFieldRequirement,
57
+ SigmaLevel,
58
+ isSevereSigmaLevel,
59
+ } from "../../../Types/SecurityEvent/SigmaRule";
60
+ import OcsfSeverity from "../../../Types/SecurityEvent/OcsfSeverity";
61
+ import {
62
+ DETECTION_DISTINCT_COUNT_ATTRIBUTE,
63
+ DETECTION_EVALUATION_INTERVAL_MAX_IN_MINUTES,
64
+ DETECTION_EVALUATION_INTERVAL_MIN_IN_MINUTES,
65
+ DETECTION_FINDING_CLASS_NAME,
66
+ DETECTION_FINDING_CLASS_UID,
67
+ DETECTION_GROUP_VALUE_ATTRIBUTE,
68
+ DETECTION_MATCH_COUNT_ATTRIBUTE,
69
+ DETECTION_MATCH_COUNT_THRESHOLD_MAX,
70
+ DETECTION_MATCH_COUNT_THRESHOLD_MIN,
71
+ DETECTION_MAX_GROUPS_PER_EVALUATION,
72
+ DETECTION_MAX_LOOKBACK_IN_MINUTES,
73
+ DETECTION_RULE_ID_ATTRIBUTE,
74
+ DETECTION_RULE_NAME_ATTRIBUTE,
75
+ DETECTION_SIGMA_ID_ATTRIBUTE,
76
+ } from "../../../Types/SecurityEvent/DetectionFindingConstants";
77
+ import {
78
+ ENRICHMENT_CONFIDENCE_ATTRIBUTE,
79
+ ENRICHMENT_FEED_ATTRIBUTE,
80
+ ENRICHMENT_FEED_ID_ATTRIBUTE,
81
+ ENRICHMENT_INDICATOR_ID_ATTRIBUTE,
82
+ ENRICHMENT_INDICATOR_TYPE_ATTRIBUTE,
83
+ ENRICHMENT_INDICATOR_VALUE_ATTRIBUTE,
84
+ ENRICHMENT_MATCHED_ATTRIBUTE,
85
+ ENRICHMENT_MATCH_COUNT_ATTRIBUTE,
86
+ THREAT_CONFIDENCE_ATTRIBUTE,
87
+ THREAT_FEED_ID_ATTRIBUTE,
88
+ THREAT_FEED_NAME_ATTRIBUTE,
89
+ THREAT_INDICATOR_ID_ATTRIBUTE,
90
+ THREAT_INDICATOR_TYPE_ATTRIBUTE,
91
+ THREAT_INDICATOR_VALUE_ATTRIBUTE,
92
+ THREAT_INTEL_DEFAULT_POLL_INTERVAL_IN_MINUTES,
93
+ THREAT_INTEL_DEFAULT_VALID_DAYS,
94
+ THREAT_INTEL_FIRST_MATCH_WINDOW_IN_MINUTES,
95
+ THREAT_INTEL_MATCH_MAX_LOOKBACK_IN_MINUTES,
96
+ THREAT_INTEL_MAX_INDICATORS_PER_EVALUATION,
97
+ THREAT_INTEL_MINIMUM_CONFIDENCE_MAX,
98
+ THREAT_INTEL_MINIMUM_CONFIDENCE_MIN,
99
+ THREAT_INTEL_POLL_INTERVAL_MAX_IN_MINUTES,
100
+ THREAT_INTEL_POLL_INTERVAL_MIN_IN_MINUTES,
101
+ THREAT_INTEL_PRODUCT_NAME,
102
+ THREAT_MATCH_COUNT_ATTRIBUTE,
103
+ ThreatIntelIndicatorType,
104
+ ocsfSeverityForConfidence,
105
+ } from "../../../Types/SecurityEvent/ThreatIntelConstants";
106
+ import ObjectID from "../../../Types/ObjectID";
107
+
108
+ /*
109
+ * The Detection Rules and Threat Intel guides are what a customer reads to
110
+ * decide whether to trust a detection — so a guide that is wrong is worse
111
+ * than no guide. Nothing about a wrong sentence fails to render, so every
112
+ * claim the guides make is pinned here against the code that actually
113
+ * behaves that way: examples go through the real Sigma parser, compiler
114
+ * and STIX pattern parser; tables are compared with the engine's own
115
+ * mappings, in both directions where a table claims to be complete; and
116
+ * every number is checked against the constant the engine runs on.
117
+ */
118
+
119
+ const GUIDES: Array<SecurityEventsGuide> = [
120
+ DetectionRulesGuide,
121
+ ThreatIntelGuide,
122
+ ];
123
+
124
+ const GUIDE_CASES: Array<[string, SecurityEventsGuide]> = GUIDES.map(
125
+ (guide: SecurityEventsGuide): [string, SecurityEventsGuide] => {
126
+ return [guide.id, guide];
127
+ },
128
+ );
129
+
130
+ const MODIFIER_CASES: Array<[string, SigmaModifierDoc]> =
131
+ SIGMA_MODIFIER_DOCS.map(
132
+ (doc: SigmaModifierDoc): [string, SigmaModifierDoc] => {
133
+ return [doc.modifier, doc];
134
+ },
135
+ );
136
+
137
+ const ALIAS_CASES: Array<[string, string]> = SIGMA_FIELD_ALIAS_DOCS.flatMap(
138
+ (doc: SigmaFieldDoc): Array<[string, string]> => {
139
+ return doc.aliases.map((alias: string): [string, string] => {
140
+ return [alias, doc.column];
141
+ });
142
+ },
143
+ );
144
+
145
+ const THRESHOLD_CASES: Array<[string, DetectionThresholdExample]> =
146
+ DETECTION_THRESHOLD_EXAMPLES.map(
147
+ (
148
+ example: DetectionThresholdExample,
149
+ ): [string, DetectionThresholdExample] => {
150
+ return [example.goal, example];
151
+ },
152
+ );
153
+
154
+ const BAND_CASES: Array<[string, ThreatLevelBand]> = THREAT_LEVEL_BANDS.map(
155
+ (band: ThreatLevelBand): [string, ThreatLevelBand] => {
156
+ return [formatBand(band), band];
157
+ },
158
+ );
159
+
160
+ const SIGMA_LEVELS_IN_ORDER: Array<SigmaLevel> = [
161
+ SigmaLevel.Informational,
162
+ SigmaLevel.Low,
163
+ SigmaLevel.Medium,
164
+ SigmaLevel.High,
165
+ SigmaLevel.Critical,
166
+ ];
167
+
168
+ const YAML_BLOCK_PATTERN: RegExp = /```yaml\n([\s\S]*?)```/g;
169
+ const FENCE_PATTERN: RegExp = /```/g;
170
+
171
+ function sectionMarkdown(
172
+ guide: SecurityEventsGuide,
173
+ sectionId: string,
174
+ ): string {
175
+ const section: SecurityEventsGuideSection | undefined = getGuideSection(
176
+ guide,
177
+ sectionId,
178
+ );
179
+
180
+ expect(section).toBeDefined();
181
+
182
+ return section!.markdown;
183
+ }
184
+
185
+ function allMarkdown(guide: SecurityEventsGuide): string {
186
+ return guide.sections
187
+ .map((section: SecurityEventsGuideSection): string => {
188
+ return section.markdown;
189
+ })
190
+ .join("\n");
191
+ }
192
+
193
+ function ruleWithSelectionLine(line: string): string {
194
+ return `title: Guide example
195
+ detection:
196
+ selection:
197
+ ${line}
198
+ condition: selection
199
+ `;
200
+ }
201
+
202
+ function compileLine(line: string): Statement {
203
+ return SigmaClickhouseCompiler.compileYaml(ruleWithSelectionLine(line));
204
+ }
205
+
206
+ function paramValues(statement: Statement): Array<unknown> {
207
+ return Object.values(statement.query_params);
208
+ }
209
+
210
+ function yamlBlocks(markdown: string): Array<string> {
211
+ return Array.from(markdown.matchAll(YAML_BLOCK_PATTERN)).map(
212
+ (match: RegExpMatchArray): string => {
213
+ return match[1] || "";
214
+ },
215
+ );
216
+ }
217
+
218
+ describe("Security Events guides: structure", () => {
219
+ test.each(GUIDE_CASES)(
220
+ "%s has a title, summary, four steps and a levels strip",
221
+ (_id: string, guide: SecurityEventsGuide) => {
222
+ expect(guide.title.trim()).not.toBe("");
223
+ expect(guide.summary.trim()).not.toBe("");
224
+ expect(guide.steps).toHaveLength(4);
225
+
226
+ for (const step of guide.steps) {
227
+ expect(step.title.trim()).not.toBe("");
228
+ expect(step.description.trim()).not.toBe("");
229
+ expect(step.icon).toBeDefined();
230
+ }
231
+
232
+ expect(guide.levels.items.length).toBeGreaterThan(0);
233
+ },
234
+ );
235
+
236
+ test.each(GUIDE_CASES)(
237
+ "%s has unique section ids and titles (the tabs are keyed by title)",
238
+ (_id: string, guide: SecurityEventsGuide) => {
239
+ const ids: Array<string> = guide.sections.map(
240
+ (section: SecurityEventsGuideSection) => {
241
+ return section.id;
242
+ },
243
+ );
244
+ const titles: Array<string> = guide.sections.map(
245
+ (section: SecurityEventsGuideSection) => {
246
+ return section.title;
247
+ },
248
+ );
249
+
250
+ expect(new Set(ids).size).toBe(ids.length);
251
+ expect(new Set(titles).size).toBe(titles.length);
252
+ expect(guide.sections[0]?.id).toBe("overview");
253
+ expect(guide.sections[guide.sections.length - 1]?.id).toBe(
254
+ "troubleshooting",
255
+ );
256
+ },
257
+ );
258
+
259
+ test.each(GUIDE_CASES)(
260
+ "%s's levels strip links to a section that exists",
261
+ (_id: string, guide: SecurityEventsGuide) => {
262
+ expect(getGuideSection(guide, guide.levels.sectionId)).toBeDefined();
263
+ },
264
+ );
265
+
266
+ test.each(GUIDE_CASES)(
267
+ "%s's markdown carries no failed interpolation",
268
+ (_id: string, guide: SecurityEventsGuide) => {
269
+ const text: string = [
270
+ guide.title,
271
+ guide.summary,
272
+ guide.guideTitle,
273
+ guide.guideDescription,
274
+ guide.levels.title,
275
+ guide.levels.description,
276
+ ...guide.steps.map((step: { description: string }) => {
277
+ return step.description;
278
+ }),
279
+ allMarkdown(guide),
280
+ ].join("\n");
281
+
282
+ expect(text).not.toContain("undefined");
283
+ expect(text).not.toContain("NaN");
284
+ expect(text).not.toContain("[object Object]");
285
+ expect(text).not.toContain("${");
286
+ },
287
+ );
288
+
289
+ test.each(GUIDE_CASES)(
290
+ "%s never leaves a code fence open (an open fence swallows the rest of the tab)",
291
+ (_id: string, guide: SecurityEventsGuide) => {
292
+ for (const section of guide.sections) {
293
+ const fences: number = (section.markdown.match(FENCE_PATTERN) || [])
294
+ .length;
295
+ expect(fences % 2).toBe(0);
296
+ }
297
+ },
298
+ );
299
+
300
+ test.each(GUIDE_CASES)(
301
+ "every YAML sample in %s is a Sigma rule the save-time validator accepts",
302
+ (_id: string, guide: SecurityEventsGuide) => {
303
+ const blocks: Array<string> = yamlBlocks(allMarkdown(guide));
304
+
305
+ expect(blocks.length).toBeGreaterThan(0);
306
+
307
+ for (const block of blocks) {
308
+ expect(() => {
309
+ SigmaClickhouseCompiler.compileYaml(block);
310
+ }).not.toThrow();
311
+ }
312
+ },
313
+ );
314
+
315
+ test("the two guides use different ids, so their collapsed state is stored apart", () => {
316
+ expect(DetectionRulesGuide.id).not.toBe(ThreatIntelGuide.id);
317
+ });
318
+
319
+ test("each guide points at a public documentation path", () => {
320
+ expect(DetectionRulesGuide.documentationPath).toBe(
321
+ "/telemetry/security-events",
322
+ );
323
+ expect(ThreatIntelGuide.documentationPath).toBe(
324
+ "/telemetry/threat-intelligence",
325
+ );
326
+ });
327
+ });
328
+
329
+ describe("guideToMarkdown", () => {
330
+ test("includes every section, headed by its title, in order", () => {
331
+ const markdown: string = guideToMarkdown(DetectionRulesGuide);
332
+
333
+ let lastIndex: number = -1;
334
+
335
+ for (const section of DetectionRulesGuide.sections) {
336
+ const headingIndex: number = markdown.indexOf(`### ${section.title}\n`);
337
+ expect(headingIndex).toBeGreaterThan(lastIndex);
338
+ expect(markdown).toContain(section.markdown.trim());
339
+ lastIndex = headingIndex;
340
+ }
341
+ });
342
+
343
+ test("separates sections with a rule", () => {
344
+ const markdown: string = guideToMarkdown(ThreatIntelGuide);
345
+
346
+ expect(markdown.split("\n\n---\n\n")).toHaveLength(
347
+ ThreatIntelGuide.sections.length,
348
+ );
349
+ });
350
+ });
351
+
352
+ describe("markdownTable", () => {
353
+ test("renders a header, a separator and one line per row", () => {
354
+ expect(
355
+ markdownTable(
356
+ ["A", "B"],
357
+ [
358
+ ["1", "2"],
359
+ ["3", "4"],
360
+ ],
361
+ ),
362
+ ).toBe("| A | B |\n| --- | --- |\n| 1 | 2 |\n| 3 | 4 |");
363
+ });
364
+
365
+ test("escapes pipes so a Sigma field|modifier key cannot split its cell", () => {
366
+ expect(markdownTable(["Key"], [["`CommandLine|contains`"]])).toBe(
367
+ "| Key |\n| --- |\n| `CommandLine\\|contains` |",
368
+ );
369
+ });
370
+ });
371
+
372
+ describe("Detection Rules guide: the walkthrough rule", () => {
373
+ test("parses and compiles, so the example would save", () => {
374
+ expect(() => {
375
+ SigmaClickhouseCompiler.compileYaml(DETECTION_RULE_EXAMPLE_YAML);
376
+ }).not.toThrow();
377
+ });
378
+
379
+ test("is high level, as the overview says", () => {
380
+ expect(SigmaRuleParser.parse(DETECTION_RULE_EXAMPLE_YAML).level).toBe(
381
+ SigmaLevel.High,
382
+ );
383
+ });
384
+
385
+ test("its ATT&CK id tags become the finding's technique and tactic, as the tags row says", () => {
386
+ const rule: SigmaRule = SigmaRuleParser.parse(DETECTION_RULE_EXAMPLE_YAML);
387
+
388
+ expect(rule.mitreTechniques).toEqual(["T1110"]);
389
+ expect(rule.mitreTactics).toEqual(["TA0006"]);
390
+ });
391
+
392
+ test("named ATT&CK tags are not mapped, as the tags row warns", () => {
393
+ const rule: SigmaRule = SigmaRuleParser.parse(`title: Named tags
394
+ tags:
395
+ - attack.credential_access
396
+ detection:
397
+ selection:
398
+ className: Authentication
399
+ condition: selection
400
+ `);
401
+
402
+ expect(rule.mitreTactics).toEqual([]);
403
+ expect(rule.mitreTechniques).toEqual([]);
404
+ });
405
+
406
+ test("excludes internal IPs the way the overview describes", () => {
407
+ const statement: Statement = SigmaClickhouseCompiler.compileYaml(
408
+ DETECTION_RULE_EXAMPLE_YAML,
409
+ );
410
+
411
+ expect(statement.query).toContain("NOT ");
412
+ expect(statement.query).toContain("principalIp ILIKE");
413
+ expect(paramValues(statement)).toContain("10.%");
414
+ });
415
+
416
+ test("the overview walks through it with a Group By Field and threshold the engine accepts", () => {
417
+ const overview: string = sectionMarkdown(DetectionRulesGuide, "overview");
418
+
419
+ expect(overview).toContain(DETECTION_RULE_EXAMPLE_YAML);
420
+ expect(resolveSigmaField("principalIp").kind).toBe("textColumn");
421
+ });
422
+ });
423
+
424
+ describe("Detection Rules guide: selections and conditions", () => {
425
+ test("the selections sample has the three shapes it describes", () => {
426
+ const rule: SigmaRule = SigmaRuleParser.parse(
427
+ SIGMA_SELECTIONS_EXAMPLE_YAML,
428
+ );
429
+
430
+ const byName: Map<string, SigmaRule["selections"][number]> = new Map(
431
+ rule.selections.map((selection: SigmaRule["selections"][number]) => {
432
+ return [selection.name, selection];
433
+ }),
434
+ );
435
+
436
+ // Field map: one map whose statusName carries two OR-ed values.
437
+ const logon: SigmaRule["selections"][number] | undefined =
438
+ byName.get("selection_logon");
439
+ expect(logon?.fieldMaps).toHaveLength(1);
440
+ const statusName: SigmaFieldRequirement | undefined =
441
+ logon?.fieldMaps[0]?.find((requirement: SigmaFieldRequirement) => {
442
+ return requirement.field === "statusName";
443
+ });
444
+ expect(statusName?.values).toEqual(["Failure", "Blocked"]);
445
+
446
+ // List of maps: two alternatives.
447
+ expect(byName.get("selection_admin")?.fieldMaps).toHaveLength(2);
448
+
449
+ // Keywords.
450
+ expect(byName.get("keywords")?.keywords).toEqual([
451
+ "password spray",
452
+ "credential stuffing",
453
+ ]);
454
+ });
455
+
456
+ test("keywords match anywhere in the message, ignoring case", () => {
457
+ const statement: Statement = SigmaClickhouseCompiler.compileYaml(
458
+ SIGMA_SELECTIONS_EXAMPLE_YAML,
459
+ );
460
+
461
+ expect(statement.query).toContain("message ILIKE");
462
+ expect(paramValues(statement)).toContain("%password spray%");
463
+ });
464
+
465
+ test("the documented quantifiers are all accepted", () => {
466
+ for (const condition of [
467
+ "1 of selection_*",
468
+ "all of selection_*",
469
+ "any of them",
470
+ "all of them",
471
+ "selection_a and not (selection_b or selection_c)",
472
+ ]) {
473
+ expect(() => {
474
+ SigmaClickhouseCompiler.compileYaml(`title: Quantifiers
475
+ detection:
476
+ selection_a:
477
+ className: Authentication
478
+ selection_b:
479
+ statusName: Failure
480
+ selection_c:
481
+ principalUser: root
482
+ condition: ${condition}
483
+ `);
484
+ }).not.toThrow();
485
+ }
486
+ });
487
+
488
+ test("aggregations are rejected at save, as the guide says", () => {
489
+ expect(() => {
490
+ SigmaRuleParser.parse(`title: Aggregation
491
+ detection:
492
+ selection:
493
+ className: Authentication
494
+ condition: selection | count() by principalIp > 5
495
+ `);
496
+ }).toThrow(/aggregation/i);
497
+ });
498
+
499
+ test("a timeframe under detection is accepted and ignored", () => {
500
+ const withTimeframe: Statement =
501
+ SigmaClickhouseCompiler.compileYaml(`title: T
502
+ detection:
503
+ selection:
504
+ className: Authentication
505
+ timeframe: 5m
506
+ condition: selection
507
+ `);
508
+ const without: Statement = SigmaClickhouseCompiler.compileYaml(
509
+ ruleWithSelectionLine("className: Authentication"),
510
+ );
511
+
512
+ expect(withTimeframe.query).toBe(without.query);
513
+ expect(withTimeframe.query_params).toEqual(without.query_params);
514
+ });
515
+
516
+ test("logsource does not narrow the search, as the structure table warns", () => {
517
+ const withLogsource: Statement =
518
+ SigmaClickhouseCompiler.compileYaml(`title: T
519
+ logsource:
520
+ category: authentication
521
+ product: okta
522
+ detection:
523
+ selection:
524
+ className: Authentication
525
+ condition: selection
526
+ `);
527
+ const without: Statement = SigmaClickhouseCompiler.compileYaml(
528
+ ruleWithSelectionLine("className: Authentication"),
529
+ );
530
+
531
+ expect(withLogsource.query).toBe(without.query);
532
+ expect(withLogsource.query_params).toEqual(without.query_params);
533
+ });
534
+
535
+ test("a missing or unknown level reads as the documented default", () => {
536
+ expect(
537
+ SigmaRuleParser.parse(ruleWithSelectionLine("className: X")).level,
538
+ ).toBe(SIGMA_DEFAULT_LEVEL);
539
+ expect(
540
+ SigmaRuleParser.parse(
541
+ `level: severe\n${ruleWithSelectionLine("className: X")}`,
542
+ ).level,
543
+ ).toBe(SIGMA_DEFAULT_LEVEL);
544
+ expect(sectionMarkdown(DetectionRulesGuide, "writing-rules")).toContain(
545
+ `read as \`${SIGMA_DEFAULT_LEVEL}\``,
546
+ );
547
+ });
548
+ });
549
+
550
+ describe("Detection Rules guide: matching semantics", () => {
551
+ test("plain values ignore case", () => {
552
+ const statement: Statement = compileLine("message: 'Mimikatz Ran'");
553
+
554
+ expect(statement.query).toContain("lowerUTF8(message)");
555
+ expect(paramValues(statement)).toEqual(["mimikatz ran"]);
556
+ });
557
+
558
+ test("|cased matches case-sensitively", () => {
559
+ const statement: Statement = compileLine("message|cased: 'Mimikatz'");
560
+
561
+ expect(statement.query).not.toContain("lowerUTF8");
562
+ expect(paramValues(statement)).toEqual(["Mimikatz"]);
563
+ });
564
+
565
+ test("regular expressions are case-sensitive (no case folding is applied)", () => {
566
+ const statement: Statement = compileLine("principalUser|re: '^Adm'");
567
+
568
+ expect(statement.query).toContain("match(principalUser");
569
+ expect(statement.query).not.toContain("lowerUTF8");
570
+ expect(statement.query).not.toContain("ILIKE");
571
+ });
572
+
573
+ test("* and ? are wildcards", () => {
574
+ const statement: Statement = compileLine("targetResource: '/tmp/*.sh'");
575
+
576
+ expect(statement.query).toContain("targetResource ILIKE");
577
+ expect(paramValues(statement)).toEqual(["/tmp/%.sh"]);
578
+ });
579
+
580
+ test("null matches an empty column or a missing attribute", () => {
581
+ expect(compileLine("principalUser: null").query).toContain(
582
+ "principalUser = ''",
583
+ );
584
+ expect(compileLine("threat.matched: null").query).toContain(
585
+ "NOT mapContains(attributes",
586
+ );
587
+ });
588
+
589
+ test("list columns: a plain value needs exact membership, |contains matches inside items ignoring case", () => {
590
+ expect(compileLine("observables: 'Alice'").query).toContain(
591
+ "has(observables",
592
+ );
593
+
594
+ const contains: Statement = compileLine("observables|contains: 'ali'");
595
+ expect(contains.query).toContain("arrayExists(x -> x ILIKE");
596
+ });
597
+
598
+ test("any modifier outside the documented list is rejected at save", () => {
599
+ expect(() => {
600
+ SigmaRuleParser.parse(ruleWithSelectionLine("CommandLine|base64: 'x'"));
601
+ }).toThrow(/unsupported field modifier/);
602
+ });
603
+
604
+ test("a nested map value is rejected at save, as troubleshooting says", () => {
605
+ expect(() => {
606
+ SigmaRuleParser.parse(`title: Nested
607
+ detection:
608
+ selection:
609
+ principal:
610
+ user: root
611
+ condition: selection
612
+ `);
613
+ }).toThrow(/nested object/);
614
+ });
615
+
616
+ test("a condition naming a missing selection is rejected at save", () => {
617
+ expect(() => {
618
+ SigmaRuleParser.parse(`title: Missing
619
+ detection:
620
+ selection:
621
+ className: X
622
+ condition: selection and filter
623
+ `);
624
+ }).toThrow();
625
+
626
+ expect(() => {
627
+ SigmaRuleParser.parse(`title: No match
628
+ detection:
629
+ selection:
630
+ className: X
631
+ condition: 1 of filter_*
632
+ `);
633
+ }).toThrow(/matches no selection/);
634
+ });
635
+ });
636
+
637
+ describe("Detection Rules guide: the modifier table", () => {
638
+ test("lists exactly the modifiers the parser accepts — no more, no fewer", () => {
639
+ const documented: Array<string> = SIGMA_MODIFIER_DOCS.map(
640
+ (doc: SigmaModifierDoc) => {
641
+ return doc.modifier;
642
+ },
643
+ );
644
+
645
+ expect([...documented].sort()).toEqual(
646
+ [...SIGMA_SUPPORTED_MODIFIERS].sort(),
647
+ );
648
+ expect(new Set(documented).size).toBe(documented.length);
649
+ });
650
+
651
+ test.each(MODIFIER_CASES)(
652
+ "the %s example parses, uses its modifier, and compiles",
653
+ (_modifier: string, doc: SigmaModifierDoc) => {
654
+ const rule: SigmaRule = SigmaRuleParser.parse(
655
+ ruleWithSelectionLine(doc.example),
656
+ );
657
+ const requirement: SigmaFieldRequirement | undefined =
658
+ rule.selections[0]?.fieldMaps[0]?.[0];
659
+
660
+ expect(requirement?.modifiers).toContain(doc.modifier);
661
+ expect(() => {
662
+ SigmaClickhouseCompiler.compile(rule);
663
+ }).not.toThrow();
664
+ },
665
+ );
666
+
667
+ /*
668
+ * Each description promises a behavior; the compiled SQL is where that
669
+ * behavior lives.
670
+ */
671
+ const EXPECTED_SQL: Record<string, (statement: Statement) => void> = {
672
+ contains: (statement: Statement) => {
673
+ expect(statement.query).toContain("ILIKE");
674
+ expect(paramValues(statement)).toEqual(["%-EncodedCommand%"]);
675
+ },
676
+ startswith: (statement: Statement) => {
677
+ expect(paramValues(statement)).toEqual(["10.%"]);
678
+ },
679
+ endswith: (statement: Statement) => {
680
+ expect(paramValues(statement)).toEqual(["%.ps1"]);
681
+ },
682
+ all: (statement: Statement) => {
683
+ expect(statement.query).toContain(" AND ");
684
+ expect(statement.query).not.toContain(" OR ");
685
+ },
686
+ re: (statement: Statement) => {
687
+ expect(statement.query).toContain("match(");
688
+ },
689
+ cased: (statement: Statement) => {
690
+ expect(statement.query).toContain(" LIKE ");
691
+ expect(statement.query).not.toContain("ILIKE");
692
+ },
693
+ gt: (statement: Statement) => {
694
+ expect(statement.query).toContain("severityId > ");
695
+ },
696
+ gte: (statement: Statement) => {
697
+ expect(statement.query).toContain("toFloat64OrNull(attributes[");
698
+ expect(statement.query).toContain(" >= ");
699
+ },
700
+ lt: (statement: Statement) => {
701
+ expect(statement.query).toContain("targetPort < ");
702
+ },
703
+ lte: (statement: Statement) => {
704
+ expect(statement.query).toContain("targetPort <= ");
705
+ },
706
+ cidr: (statement: Statement) => {
707
+ expect(statement.query).toContain("isIPAddressInRange(principalIp");
708
+ },
709
+ exists: (statement: Statement) => {
710
+ expect(statement.query).toContain("mapContains(attributes");
711
+ expect(statement.query).not.toContain("NOT mapContains");
712
+ },
713
+ windash: (statement: Statement) => {
714
+ expect(paramValues(statement)).toEqual(
715
+ expect.arrayContaining(["% -exec %", "% /exec %"]),
716
+ );
717
+ expect(statement.query).toContain(" OR ");
718
+ },
719
+ };
720
+
721
+ test("every modifier has a behavior check below", () => {
722
+ expect(Object.keys(EXPECTED_SQL).sort()).toEqual(
723
+ [...SIGMA_SUPPORTED_MODIFIERS].sort(),
724
+ );
725
+ });
726
+
727
+ test.each(MODIFIER_CASES)(
728
+ "the %s example compiles to what its description says",
729
+ (modifier: string, doc: SigmaModifierDoc) => {
730
+ EXPECTED_SQL[modifier]!(compileLine(doc.example));
731
+ },
732
+ );
733
+
734
+ test("the table is rendered into the Writing Rules section", () => {
735
+ const writing: string = sectionMarkdown(
736
+ DetectionRulesGuide,
737
+ "writing-rules",
738
+ );
739
+
740
+ for (const doc of SIGMA_MODIFIER_DOCS) {
741
+ expect(writing).toContain(`\`${doc.modifier}\``);
742
+ expect(writing).toContain(doc.example.replace(/\|/g, "\\|"));
743
+ }
744
+ });
745
+ });
746
+
747
+ describe("Detection Rules guide: field names", () => {
748
+ test.each(ALIAS_CASES)(
749
+ "%s resolves to %s",
750
+ (alias: string, column: string) => {
751
+ const resolved: ReturnType<typeof resolveSigmaField> =
752
+ resolveSigmaField(alias);
753
+
754
+ expect(resolved.kind).not.toBe("attribute");
755
+ expect((resolved as { column: string }).column).toBe(column);
756
+ },
757
+ );
758
+
759
+ test("every alias the compiler knows is documented", () => {
760
+ const documented: Set<string> = new Set(
761
+ SIGMA_FIELD_ALIAS_DOCS.flatMap((doc: SigmaFieldDoc) => {
762
+ return doc.aliases.map((alias: string) => {
763
+ return alias.toLowerCase();
764
+ });
765
+ }),
766
+ );
767
+
768
+ for (const alias of Object.keys(FIELD_ALIASES)) {
769
+ expect(documented).toContain(alias);
770
+ }
771
+
772
+ expect(documented.size).toBe(Object.keys(FIELD_ALIASES).length);
773
+ });
774
+
775
+ test("every documented column is a typed column, reachable by its own name", () => {
776
+ const columns: Array<string> = SIGMA_COLUMN_DOCS.flatMap(
777
+ (entry: { columns: Array<string> }) => {
778
+ return entry.columns;
779
+ },
780
+ );
781
+
782
+ expect(new Set(columns).size).toBe(columns.length);
783
+
784
+ for (const column of columns) {
785
+ const resolved: ReturnType<typeof resolveSigmaField> =
786
+ resolveSigmaField(column);
787
+ expect(resolved.kind).not.toBe("attribute");
788
+ expect((resolved as { column: string }).column).toBe(column);
789
+ // "names are case-insensitive"
790
+ expect(
791
+ (resolveSigmaField(column.toUpperCase()) as { column: string }).column,
792
+ ).toBe(column);
793
+ }
794
+ });
795
+
796
+ test("the three columns the guide calls lists are list columns", () => {
797
+ for (const column of ["observables", "mitreTactics", "mitreTechniques"]) {
798
+ expect(resolveSigmaField(column).kind).toBe("arrayColumn");
799
+ }
800
+ });
801
+
802
+ test("any other name is an attribute key, spelled exactly — an attributes. prefix is NOT stripped", () => {
803
+ expect(resolveSigmaField("threat.matched")).toEqual({
804
+ kind: "attribute",
805
+ attributeKey: "threat.matched",
806
+ });
807
+ expect(resolveSigmaField("metadata.product.name")).toEqual({
808
+ kind: "attribute",
809
+ attributeKey: "metadata.product.name",
810
+ });
811
+ // The prefix would look up a key that does not exist.
812
+ expect(resolveSigmaField("attributes.threat.matched")).toEqual({
813
+ kind: "attribute",
814
+ attributeKey: "attributes.threat.matched",
815
+ });
816
+
817
+ const writing: string = sectionMarkdown(
818
+ DetectionRulesGuide,
819
+ "writing-rules",
820
+ );
821
+ expect(writing).toContain("Do not add an `attributes.` prefix");
822
+ });
823
+ });
824
+
825
+ describe("Detection Rules guide: evaluation", () => {
826
+ test.each(THRESHOLD_CASES)(
827
+ "worked example %s uses real columns and a legal threshold",
828
+ (_goal: string, example: DetectionThresholdExample) => {
829
+ for (const field of [example.groupByField, example.distinctCountField]) {
830
+ if (field) {
831
+ expect(resolveSigmaField(field).kind).not.toBe("attribute");
832
+ }
833
+ }
834
+
835
+ expect(example.threshold).toBeGreaterThanOrEqual(
836
+ DETECTION_MATCH_COUNT_THRESHOLD_MIN,
837
+ );
838
+ expect(example.threshold).toBeLessThanOrEqual(
839
+ DETECTION_MATCH_COUNT_THRESHOLD_MAX,
840
+ );
841
+ },
842
+ );
843
+
844
+ test("distinct counting skips 0 in number columns, as the guide says of targetPort", () => {
845
+ expect(buildSigmaDistinctCountExpression("targetPort").query).toContain(
846
+ "nullIf(targetPort, 0)",
847
+ );
848
+ });
849
+
850
+ test("Group By accepts the same names as the rule itself", () => {
851
+ expect(buildSigmaFieldExpression("SourceIp").query).toBe("principalIp");
852
+ expect(buildSigmaFieldExpression("threat.feed").query).toContain(
853
+ "attributes[",
854
+ );
855
+ });
856
+
857
+ test("states the interval bounds, the lookback cap, the group cap and the threshold range", () => {
858
+ const evaluation: string = sectionMarkdown(
859
+ DetectionRulesGuide,
860
+ "evaluation",
861
+ );
862
+
863
+ expect(evaluation).toContain(
864
+ `from ${DETECTION_EVALUATION_INTERVAL_MIN_IN_MINUTES} to ${DETECTION_EVALUATION_INTERVAL_MAX_IN_MINUTES}`,
865
+ );
866
+ expect(evaluation).toContain(
867
+ `last ${DETECTION_MAX_LOOKBACK_IN_MINUTES / 60} hours`,
868
+ );
869
+ expect(evaluation).toContain(
870
+ `At most ${DETECTION_MAX_GROUPS_PER_EVALUATION} groups`,
871
+ );
872
+ expect(evaluation).toContain(`\`${DETECTION_MATCH_COUNT_THRESHOLD_MIN}\``);
873
+ expect(evaluation).toContain(
874
+ DETECTION_MATCH_COUNT_THRESHOLD_MAX.toLocaleString("en-US"),
875
+ );
876
+ });
877
+
878
+ test("the schedule step on the card states the interval bounds", () => {
879
+ expect(DetectionRulesGuide.steps[1]?.description).toContain(
880
+ `${DETECTION_EVALUATION_INTERVAL_MIN_IN_MINUTES}–${DETECTION_EVALUATION_INTERVAL_MAX_IN_MINUTES} minutes`,
881
+ );
882
+ });
883
+ });
884
+
885
+ describe("Detection Rules guide: severity levels", () => {
886
+ test("the card lists every Sigma level, in order, with the severity findings get", () => {
887
+ expect(
888
+ DetectionRulesGuide.levels.items.map((item: SecurityEventsGuideLevel) => {
889
+ return item.label;
890
+ }),
891
+ ).toEqual(SIGMA_LEVELS_IN_ORDER);
892
+
893
+ for (const item of DetectionRulesGuide.levels.items) {
894
+ expect(item.severity).toBe(
895
+ SIGMA_LEVEL_TO_OCSF_SEVERITY[item.label as SigmaLevel],
896
+ );
897
+ }
898
+
899
+ expect(Object.values(SigmaLevel).sort()).toEqual(
900
+ [...SIGMA_LEVELS_IN_ORDER].sort(),
901
+ );
902
+ });
903
+
904
+ test("the finding severity table matches the engine's mapping", () => {
905
+ const levels: string = sectionMarkdown(
906
+ DetectionRulesGuide,
907
+ "severity-levels",
908
+ );
909
+
910
+ for (const level of SIGMA_LEVELS_IN_ORDER) {
911
+ expect(levels).toContain(
912
+ `| \`${level}\` | ${SIGMA_LEVEL_TO_OCSF_SEVERITY[level]} |`,
913
+ );
914
+ }
915
+ });
916
+
917
+ test("the rank fallback splits the levels exactly where the engine does", () => {
918
+ const levels: string = sectionMarkdown(
919
+ DetectionRulesGuide,
920
+ "severity-levels",
921
+ );
922
+
923
+ expect(levels).toContain(
924
+ "Otherwise `high` and `critical` rules use your **most severe** alert severity",
925
+ );
926
+ expect(levels).toContain(
927
+ "`informational`, `low` and `medium` rules your **least severe**",
928
+ );
929
+
930
+ for (const level of SIGMA_LEVELS_IN_ORDER) {
931
+ expect(isSevereSigmaLevel(level)).toBe(
932
+ level === SigmaLevel.High || level === SigmaLevel.Critical,
933
+ );
934
+ }
935
+ });
936
+
937
+ test("the documented precedence is the one the alerting code applies", () => {
938
+ type Severity = { id: ObjectID; name: string };
939
+
940
+ const mostSevere: Severity = {
941
+ id: new ObjectID("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"),
942
+ name: "P1",
943
+ };
944
+ const named: Severity = {
945
+ id: new ObjectID("bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"),
946
+ name: "HIGH",
947
+ };
948
+ const leastSevere: Severity = {
949
+ id: new ObjectID("cccccccc-cccc-4ccc-8ccc-cccccccccccc"),
950
+ name: "P4",
951
+ };
952
+ const severities: Array<Severity> = [mostSevere, named, leastSevere];
953
+
954
+ // 1. The rule's own severity wins over a name match.
955
+ expect(
956
+ pickSeverityByPrecedence({
957
+ severities,
958
+ explicitSeverityId: leastSevere.id,
959
+ severityLabel: SigmaLevel.High,
960
+ isSevere: true,
961
+ })?.toString(),
962
+ ).toBe(leastSevere.id.toString());
963
+
964
+ // 2. A severity named after the level, ignoring case.
965
+ expect(
966
+ pickSeverityByPrecedence({
967
+ severities,
968
+ explicitSeverityId: undefined,
969
+ severityLabel: SigmaLevel.High,
970
+ isSevere: isSevereSigmaLevel(SigmaLevel.High),
971
+ })?.toString(),
972
+ ).toBe(named.id.toString());
973
+
974
+ // 3. By rank.
975
+ for (const level of SIGMA_LEVELS_IN_ORDER.filter(
976
+ (candidate: SigmaLevel) => {
977
+ return candidate !== SigmaLevel.High;
978
+ },
979
+ )) {
980
+ const expected: Severity = isSevereSigmaLevel(level)
981
+ ? mostSevere
982
+ : leastSevere;
983
+
984
+ expect(
985
+ pickSeverityByPrecedence({
986
+ severities,
987
+ explicitSeverityId: undefined,
988
+ severityLabel: level,
989
+ isSevere: isSevereSigmaLevel(level),
990
+ })?.toString(),
991
+ ).toBe(expected.id.toString());
992
+ }
993
+ });
994
+ });
995
+
996
+ describe("Detection Rules guide: alerts and findings", () => {
997
+ test("names every attribute a finding carries, and its class and product", () => {
998
+ const findings: string = sectionMarkdown(
999
+ DetectionRulesGuide,
1000
+ "alerts-and-findings",
1001
+ );
1002
+
1003
+ for (const attribute of [
1004
+ DETECTION_RULE_ID_ATTRIBUTE,
1005
+ DETECTION_RULE_NAME_ATTRIBUTE,
1006
+ DETECTION_MATCH_COUNT_ATTRIBUTE,
1007
+ DETECTION_DISTINCT_COUNT_ATTRIBUTE,
1008
+ DETECTION_GROUP_VALUE_ATTRIBUTE,
1009
+ DETECTION_SIGMA_ID_ATTRIBUTE,
1010
+ ]) {
1011
+ expect(findings).toContain(`\`${attribute}\``);
1012
+ }
1013
+
1014
+ expect(findings).toContain(`\`${DETECTION_FINDING_CLASS_NAME}\``);
1015
+ expect(findings).toContain(`OCSF ${DETECTION_FINDING_CLASS_UID}`);
1016
+ expect(findings).toContain("`OneUptime Detections`");
1017
+ });
1018
+
1019
+ test("the alert title format matches the engine's", () => {
1020
+ const findings: string = sectionMarkdown(
1021
+ DetectionRulesGuide,
1022
+ "alerts-and-findings",
1023
+ );
1024
+
1025
+ expect(findings).toContain("`[Detection] <rule name> — <group value>`");
1026
+ expect(findings).toContain("`[Detection] <rule name>`");
1027
+ });
1028
+
1029
+ test("no longer tells anyone to prefix attribute keys with attributes.", () => {
1030
+ expect(guideToMarkdown(DetectionRulesGuide)).not.toContain(
1031
+ "`attributes.<key>`",
1032
+ );
1033
+ });
1034
+ });
1035
+
1036
+ describe("Threat Intel guide: threat levels", () => {
1037
+ test("the confidence bands tile 1–100 with no gap or overlap", () => {
1038
+ const covered: Array<number> = [];
1039
+
1040
+ for (const band of THREAT_LEVEL_BANDS) {
1041
+ expect(band.minimumConfidence).toBeLessThanOrEqual(
1042
+ band.maximumConfidence,
1043
+ );
1044
+
1045
+ for (
1046
+ let confidence: number = band.minimumConfidence;
1047
+ confidence <= band.maximumConfidence;
1048
+ confidence++
1049
+ ) {
1050
+ covered.push(confidence);
1051
+ }
1052
+ }
1053
+
1054
+ covered.sort((a: number, b: number) => {
1055
+ return a - b;
1056
+ });
1057
+
1058
+ const expected: Array<number> = [];
1059
+ for (let confidence: number = 1; confidence <= 100; confidence++) {
1060
+ expected.push(confidence);
1061
+ }
1062
+
1063
+ expect(covered).toEqual(expected);
1064
+ });
1065
+
1066
+ test.each(BAND_CASES)(
1067
+ "every confidence in %s maps to the band's threat level",
1068
+ (_label: string, band: ThreatLevelBand) => {
1069
+ const level: OcsfSeverity = threatLevelForBand(band);
1070
+
1071
+ for (
1072
+ let confidence: number = band.minimumConfidence;
1073
+ confidence <= band.maximumConfidence;
1074
+ confidence++
1075
+ ) {
1076
+ expect(ocsfSeverityForConfidence(confidence)).toBe(level);
1077
+ }
1078
+ },
1079
+ );
1080
+
1081
+ test("adjacent bands are different levels, so no band could be merged away", () => {
1082
+ for (let index: number = 1; index < THREAT_LEVEL_BANDS.length; index++) {
1083
+ expect(threatLevelForBand(THREAT_LEVEL_BANDS[index]!)).not.toBe(
1084
+ threatLevelForBand(THREAT_LEVEL_BANDS[index - 1]!),
1085
+ );
1086
+ }
1087
+ });
1088
+
1089
+ test("the bands read Critical, High, Medium, Low from the top", () => {
1090
+ expect(THREAT_LEVEL_BANDS.map(threatLevelForBand)).toEqual([
1091
+ OcsfSeverity.Critical,
1092
+ OcsfSeverity.High,
1093
+ OcsfSeverity.Medium,
1094
+ OcsfSeverity.Low,
1095
+ ]);
1096
+ });
1097
+
1098
+ test("unscored — 0 or missing — reads as the level the card shows", () => {
1099
+ const unscored: SecurityEventsGuideLevel | undefined =
1100
+ ThreatIntelGuide.levels.items[ThreatIntelGuide.levels.items.length - 1];
1101
+
1102
+ expect(unscored?.label).toBe("Not scored");
1103
+ expect(ocsfSeverityForConfidence(UNSCORED_CONFIDENCE)).toBe(
1104
+ unscored?.severity,
1105
+ );
1106
+ expect(ocsfSeverityForConfidence(Number.NaN)).toBe(unscored?.severity);
1107
+ expect(unscored?.severity).toBe(OcsfSeverity.Medium);
1108
+ });
1109
+
1110
+ test("the card lists one chip per band plus unscored", () => {
1111
+ expect(ThreatIntelGuide.levels.items).toHaveLength(
1112
+ THREAT_LEVEL_BANDS.length + 1,
1113
+ );
1114
+
1115
+ THREAT_LEVEL_BANDS.forEach((band: ThreatLevelBand, index: number) => {
1116
+ expect(ThreatIntelGuide.levels.items[index]).toEqual({
1117
+ label: `Confidence ${formatBand(band)}`,
1118
+ severity: threatLevelForBand(band),
1119
+ });
1120
+ });
1121
+ });
1122
+
1123
+ test("the Threat Levels tab tabulates every band", () => {
1124
+ const levels: string = sectionMarkdown(ThreatIntelGuide, "threat-levels");
1125
+
1126
+ for (const band of THREAT_LEVEL_BANDS) {
1127
+ expect(levels).toContain(
1128
+ `| ${formatBand(band)} | ${threatLevelForBand(band)} |`,
1129
+ );
1130
+ }
1131
+
1132
+ expect(levels).toContain(
1133
+ `| Not scored (${UNSCORED_CONFIDENCE} or missing) | ${OcsfSeverity.Medium} |`,
1134
+ );
1135
+ });
1136
+
1137
+ test("a Minimum Confidence of 70 keeps only High and Critical, as the example says", () => {
1138
+ for (let confidence: number = 1; confidence <= 100; confidence++) {
1139
+ const level: OcsfSeverity = ocsfSeverityForConfidence(confidence);
1140
+ const isHighOrCritical: boolean =
1141
+ level === OcsfSeverity.High || level === OcsfSeverity.Critical;
1142
+
1143
+ expect(isHighOrCritical).toBe(confidence >= 70);
1144
+ }
1145
+
1146
+ expect(sectionMarkdown(ThreatIntelGuide, "threat-levels")).toContain(
1147
+ "a minimum of 70 keeps only High and Critical",
1148
+ );
1149
+ });
1150
+ });
1151
+
1152
+ describe("Threat Intel guide: indicators", () => {
1153
+ test("documents every indicator type the parser can produce", () => {
1154
+ expect(Object.keys(INDICATOR_TYPE_DOCS).sort()).toEqual(
1155
+ Object.values(ThreatIntelIndicatorType).sort(),
1156
+ );
1157
+ });
1158
+
1159
+ test.each(Object.values(ThreatIntelIndicatorType))(
1160
+ "the %s example pattern parses to exactly that type",
1161
+ (type: ThreatIntelIndicatorType) => {
1162
+ const parsed: Array<ParsedIndicatorValue> | null =
1163
+ StixPatternParser.parse(INDICATOR_TYPE_DOCS[type].examplePattern);
1164
+
1165
+ expect(parsed).not.toBeNull();
1166
+ expect(parsed).toHaveLength(1);
1167
+ expect(parsed![0]!.type).toBe(type);
1168
+ // "Values are stored in lowercase"
1169
+ expect(parsed![0]!.value).toBe(parsed![0]!.value.toLowerCase());
1170
+ },
1171
+ );
1172
+
1173
+ test.each(SUPPORTED_COMBINED_PATTERN_EXAMPLES)(
1174
+ "the OR example %s is supported and yields both values",
1175
+ (pattern: string) => {
1176
+ expect(StixPatternParser.parse(pattern)).toHaveLength(2);
1177
+ },
1178
+ );
1179
+
1180
+ test.each(UNSUPPORTED_PATTERN_EXAMPLES)(
1181
+ "the unsupported example %s is skipped whole",
1182
+ (pattern: string) => {
1183
+ expect(StixPatternParser.parse(pattern)).toBeNull();
1184
+ },
1185
+ );
1186
+
1187
+ test("the Indicators tab shows every example and the default validity", () => {
1188
+ const indicators: string = sectionMarkdown(ThreatIntelGuide, "indicators");
1189
+
1190
+ for (const type of Object.values(ThreatIntelIndicatorType)) {
1191
+ expect(indicators).toContain(INDICATOR_TYPE_DOCS[type].examplePattern);
1192
+ expect(indicators).toContain(`\`${type}\``);
1193
+ }
1194
+
1195
+ for (const pattern of SUPPORTED_COMBINED_PATTERN_EXAMPLES) {
1196
+ expect(indicators).toContain(pattern);
1197
+ }
1198
+
1199
+ expect(indicators).toContain(`${THREAT_INTEL_DEFAULT_VALID_DAYS} days`);
1200
+ expect(indicators).toContain(`\`${THREAT_INTEL_MINIMUM_CONFIDENCE_MIN}\``);
1201
+ });
1202
+ });
1203
+
1204
+ describe("Threat Intel guide: enrichment and matching", () => {
1205
+ test("the detection rule sample compiles and reads threat.* as attributes", () => {
1206
+ const rule: SigmaRule = SigmaRuleParser.parse(
1207
+ THREAT_INTEL_SIGMA_EXAMPLE_YAML,
1208
+ );
1209
+
1210
+ expect(rule.level).toBe(SigmaLevel.Critical);
1211
+ expect(resolveSigmaField(ENRICHMENT_MATCHED_ATTRIBUTE).kind).toBe(
1212
+ "attribute",
1213
+ );
1214
+ expect(resolveSigmaField(ENRICHMENT_CONFIDENCE_ATTRIBUTE).kind).toBe(
1215
+ "attribute",
1216
+ );
1217
+
1218
+ const statement: Statement = SigmaClickhouseCompiler.compile(rule);
1219
+
1220
+ expect(statement.query).toContain("toFloat64OrNull(attributes[");
1221
+ expect(paramValues(statement)).toEqual(
1222
+ expect.arrayContaining([
1223
+ ENRICHMENT_MATCHED_ATTRIBUTE,
1224
+ ENRICHMENT_CONFIDENCE_ATTRIBUTE,
1225
+ ]),
1226
+ );
1227
+ });
1228
+
1229
+ test("names every attribute stamped at ingest", () => {
1230
+ const enrichment: string = sectionMarkdown(
1231
+ ThreatIntelGuide,
1232
+ "enrichment-and-matching",
1233
+ );
1234
+
1235
+ for (const attribute of [
1236
+ ENRICHMENT_MATCHED_ATTRIBUTE,
1237
+ ENRICHMENT_INDICATOR_ID_ATTRIBUTE,
1238
+ ENRICHMENT_INDICATOR_TYPE_ATTRIBUTE,
1239
+ ENRICHMENT_INDICATOR_VALUE_ATTRIBUTE,
1240
+ ENRICHMENT_FEED_ATTRIBUTE,
1241
+ ENRICHMENT_FEED_ID_ATTRIBUTE,
1242
+ ENRICHMENT_CONFIDENCE_ATTRIBUTE,
1243
+ ENRICHMENT_MATCH_COUNT_ATTRIBUTE,
1244
+ ]) {
1245
+ expect(enrichment).toContain(`\`${attribute}\``);
1246
+ }
1247
+ });
1248
+
1249
+ test("names every attribute a Threat Intel finding carries, and its class and product", () => {
1250
+ const enrichment: string = sectionMarkdown(
1251
+ ThreatIntelGuide,
1252
+ "enrichment-and-matching",
1253
+ );
1254
+
1255
+ for (const attribute of [
1256
+ THREAT_FEED_ID_ATTRIBUTE,
1257
+ THREAT_FEED_NAME_ATTRIBUTE,
1258
+ THREAT_INDICATOR_ID_ATTRIBUTE,
1259
+ THREAT_INDICATOR_TYPE_ATTRIBUTE,
1260
+ THREAT_INDICATOR_VALUE_ATTRIBUTE,
1261
+ THREAT_CONFIDENCE_ATTRIBUTE,
1262
+ THREAT_MATCH_COUNT_ATTRIBUTE,
1263
+ ]) {
1264
+ expect(enrichment).toContain(`\`${attribute}\``);
1265
+ }
1266
+
1267
+ expect(enrichment).toContain(`\`${DETECTION_FINDING_CLASS_NAME}\``);
1268
+ expect(enrichment).toContain(`OCSF ${DETECTION_FINDING_CLASS_UID}`);
1269
+ expect(enrichment).toContain(`\`${THREAT_INTEL_PRODUCT_NAME}\``);
1270
+ expect(enrichment).toContain(
1271
+ "`[Threat Intel] <feed name> — <indicator value>`",
1272
+ );
1273
+ });
1274
+
1275
+ test("states the matcher's first window, lookback cap and per-check cap", () => {
1276
+ const enrichment: string = sectionMarkdown(
1277
+ ThreatIntelGuide,
1278
+ "enrichment-and-matching",
1279
+ );
1280
+
1281
+ expect(enrichment).toContain(
1282
+ `last ${THREAT_INTEL_FIRST_MATCH_WINDOW_IN_MINUTES} minutes`,
1283
+ );
1284
+ expect(enrichment).toContain(
1285
+ `last ${THREAT_INTEL_MATCH_MAX_LOOKBACK_IN_MINUTES / 60} hours`,
1286
+ );
1287
+ expect(enrichment).toContain(
1288
+ `at most ${THREAT_INTEL_MAX_INDICATORS_PER_EVALUATION} indicator values`,
1289
+ );
1290
+ });
1291
+ });
1292
+
1293
+ describe("Threat Intel guide: feeds and polling", () => {
1294
+ test("states the poll interval bounds and default", () => {
1295
+ const feeds: string = sectionMarkdown(
1296
+ ThreatIntelGuide,
1297
+ "feeds-and-polling",
1298
+ );
1299
+
1300
+ expect(feeds).toContain(
1301
+ `${THREAT_INTEL_POLL_INTERVAL_MIN_IN_MINUTES}–${THREAT_INTEL_POLL_INTERVAL_MAX_IN_MINUTES}, default ${THREAT_INTEL_DEFAULT_POLL_INTERVAL_IN_MINUTES}`,
1302
+ );
1303
+ expect(feeds).toContain(
1304
+ `${THREAT_INTEL_MINIMUM_CONFIDENCE_MIN}–${THREAT_INTEL_MINIMUM_CONFIDENCE_MAX}`,
1305
+ );
1306
+ });
1307
+
1308
+ test("the polling step on the card states the same bounds and default", () => {
1309
+ expect(ThreatIntelGuide.steps[0]?.description).toContain(
1310
+ `${THREAT_INTEL_POLL_INTERVAL_MIN_IN_MINUTES}–${THREAT_INTEL_POLL_INTERVAL_MAX_IN_MINUTES} minutes (default ${THREAT_INTEL_DEFAULT_POLL_INTERVAL_IN_MINUTES})`,
1311
+ );
1312
+ });
1313
+ });