@@ -575,10 +575,11 @@ def get_risk_events(self, force_update: bool = False) -> list[RiskEvent]:
575
575
self .logger .debug (f"Using cached risk events ({ len (self ._risk_events )} total)." )
576
576
return self ._risk_events
577
577
578
+ # TODO (#248): Refactor risk/notable querying to pin to a single savedsearch ID
578
579
# Search for all risk events from a single scheduled search (indicated by orig_sid)
579
580
query = (
580
581
f'search index=risk search_name="{ self .name } " [search index=risk search '
581
- f'search_name="{ self .name } " | head 1 | fields orig_sid] | tojson'
582
+ f'search_name="{ self .name } " | tail 1 | fields orig_sid] | tojson'
582
583
)
583
584
result_iterator = self ._search (query )
584
585
@@ -643,7 +644,7 @@ def get_notable_events(self, force_update: bool = False) -> list[NotableEvent]:
643
644
# Search for all notable events from a single scheduled search (indicated by orig_sid)
644
645
query = (
645
646
f'search index=notable search_name="{ self .name } " [search index=notable search '
646
- f'search_name="{ self .name } " | head 1 | fields orig_sid] | tojson'
647
+ f'search_name="{ self .name } " | tail 1 | fields orig_sid] | tojson'
647
648
)
648
649
result_iterator = self ._search (query )
649
650
@@ -686,15 +687,17 @@ def validate_risk_events(self) -> None:
686
687
check the risks/notables
687
688
:returns: an IntegrationTestResult on failure; None on success
688
689
"""
689
- # TODO (PEX-433): Re-enable this check once we have refined the logic and reduced the false
690
- # positive rate in risk/obseravble matching
691
690
# Create a mapping of the relevant observables to counters
692
- # observables = CorrelationSearch._get_relevant_observables(self.detection.tags.observable)
693
- # observable_counts: dict[str, int] = {str(x): 0 for x in observables}
694
- # if len(observables) != len(observable_counts):
695
- # raise ClientError(
696
- # f"At least two observables in '{self.detection.name}' have the same name."
697
- # )
691
+ observables = CorrelationSearch ._get_relevant_observables (self .detection .tags .observable )
692
+ observable_counts : dict [str , int ] = {str (x ): 0 for x in observables }
693
+
694
+ # NOTE: we intentionally want this to be an error state and not a failure state, as
695
+ # ultimately this validation should be handled during the build process
696
+ if len (observables ) != len (observable_counts ):
697
+ raise ClientError (
698
+ f"At least two observables in '{ self .detection .name } ' have the same name; "
699
+ "each observable for a detection should be unique."
700
+ )
698
701
699
702
# Get the risk events; note that we use the cached risk events, expecting they were
700
703
# saved by a prior call to risk_event_exists
@@ -710,25 +713,29 @@ def validate_risk_events(self) -> None:
710
713
)
711
714
event .validate_against_detection (self .detection )
712
715
713
- # TODO (PEX-433): Re-enable this check once we have refined the logic and reduced the
714
- # false positive rate in risk/obseravble matching
715
716
# Update observable count based on match
716
- # matched_observable = event.get_matched_observable(self.detection.tags.observable)
717
- # self.logger.debug(
718
- # f"Matched risk event ({event.risk_object}, {event.risk_object_type}) to observable "
719
- # f"({matched_observable.name}, {matched_observable.type}, {matched_observable.role})"
720
- # )
721
- # observable_counts[str(matched_observable)] += 1
722
-
723
- # TODO (PEX-433): test my new contentctl logic against an old ESCU build; my logic should
724
- # detect the faulty attacker events -> this was the issue from the 4.28/4.27 release;
725
- # recreate by testing against one of those old builds w/ the bad config
726
- # TODO (PEX-433): Re-enable this check once we have refined the logic and reduced the false
727
- # positive
728
- # rate in risk/obseravble matching
729
- # TODO (PEX-433): I foresee issues here if for example a parent and child process share a
730
- # name (matched observable could be either) -> these issues are confirmed to exist, e.g.
731
- # `Windows Steal Authentication Certificates Export Certificate`
717
+ matched_observable = event .get_matched_observable (self .detection .tags .observable )
718
+ self .logger .debug (
719
+ f"Matched risk event (object={ event .risk_object } , type={ event .risk_object_type } ) "
720
+ f"to observable (name={ matched_observable .name } , type={ matched_observable .type } , "
721
+ f"role={ matched_observable .role } ) using the source field "
722
+ f"'{ event .source_field_name } '"
723
+ )
724
+ observable_counts [str (matched_observable )] += 1
725
+
726
+ # Report any observables which did not have at least one match to a risk event
727
+ for observable in observables :
728
+ self .logger .debug (
729
+ f"Matched observable (name={ observable .name } , type={ observable .type } , "
730
+ f"role={ observable .role } ) to { observable_counts [str (observable )]} risk events."
731
+ )
732
+ if observable_counts [str (observable )] == 0 :
733
+ raise ValidationFailed (
734
+ f"Observable (name={ observable .name } , type={ observable .type } , "
735
+ f"role={ observable .role } ) was not matched to any risk events."
736
+ )
737
+
738
+ # TODO (#250): Re-enable and refactor code that validates the specific risk counts
732
739
# Validate risk events in aggregate; we should have an equal amount of risk events for each
733
740
# relevant observable, and the total count should match the total number of events
734
741
# individual_count: Optional[int] = None
0 commit comments