From 1a9f48161afb896f0d68d888a327760393859634 Mon Sep 17 00:00:00 2001 From: Ernst Leierzopf Date: Mon, 6 Apr 2026 23:06:32 +0200 Subject: [PATCH 1/6] add new changes from NewValueDetector to NewEventDetector. --- .../detectors/new_event_detector.py | 53 ++++++++++--- .../test_detectors/test_new_event_detector.py | 77 ++++++++++++++++++- 2 files changed, 118 insertions(+), 12 deletions(-) diff --git a/src/detectmatelibrary/detectors/new_event_detector.py b/src/detectmatelibrary/detectors/new_event_detector.py index 51d345d..ad91813 100644 --- a/src/detectmatelibrary/detectors/new_event_detector.py +++ b/src/detectmatelibrary/detectors/new_event_detector.py @@ -1,23 +1,22 @@ from detectmatelibrary.common._config._compile import generate_detector_config from detectmatelibrary.common._config._formats import EventsConfig - -from detectmatelibrary.common.detector import CoreDetectorConfig, CoreDetector, get_configured_variables - +from detectmatelibrary.common.detector import CoreDetectorConfig, CoreDetector, get_configured_variables, \ + get_global_variables from detectmatelibrary.utils.persistency.event_data_structures.trackers.stability.stability_tracker import ( EventStabilityTracker ) +from detectmatelibrary.constants import GLOBAL_EVENT_ID from detectmatelibrary.utils.persistency.event_persistency import EventPersistency from detectmatelibrary.utils.data_buffer import BufferMode - from detectmatelibrary.schemas import ParserSchema, DetectorSchema - -from typing import Any +from tools.logging import logger class NewEventDetectorConfig(CoreDetectorConfig): method_type: str = "new_event_detector" - events: EventsConfig | dict[str, Any] = {} + use_stable_vars: bool = True + use_static_vars: bool = True class NewEventDetector(CoreDetector): @@ -50,6 +49,14 @@ def train(self, input_: ParserSchema) -> None: # type: ignore event_template=input_["template"], named_variables=configured_variables ) + if self.config.global_instances: + global_vars = get_global_variables(input_, self.config.global_instances) + if global_vars: + self.persistency.ingest_event( + event_id=GLOBAL_EVENT_ID, + event_template=input_["template"], + named_variables=global_vars + ) def detect( self, input_: ParserSchema, output_: DetectorSchema # type: ignore @@ -74,6 +81,17 @@ def detect( ) overall_score += 1.0 + if self.config.global_instances and GLOBAL_EVENT_ID in known_events: + global_vars = get_global_variables(input_, self.config.global_instances) + global_tracker = known_events[GLOBAL_EVENT_ID] + for var_name, multi_tracker in global_tracker.get_data().items(): + value = global_vars.get(var_name) + if value is None: + continue + if value not in multi_tracker.unique_set: + alerts[f"Global - {var_name}"] = f"Unknown value: '{value}'" + overall_score += 1.0 + if overall_score > 0: output_["score"] = overall_score output_["description"] = f"{self.name} detects values not encountered in training as anomalies." @@ -93,13 +111,26 @@ def configure(self, input_: ParserSchema) -> None: # type: ignore def set_configuration(self) -> None: variables = {} for event_id, tracker in self.auto_conf_persistency.get_events_data().items(): - classified_vars = (tracker.get_variables_by_classification("STABLE") + # type: ignore - tracker.get_variables_by_classification("STATIC")) # type: ignore - variables[event_id] = classified_vars + stable = [] + if self.config.use_stable_vars: + stable = tracker.get_features_by_classification("STABLE") # type: ignore + static = [] + if self.config.use_static_vars: + static = tracker.get_features_by_classification("STATIC") # type: ignore + vars_ = stable + static + if len(vars_) > 0: + variables[event_id] = vars_ config_dict = generate_detector_config( variable_selection=variables, detector_name=self.name, - method_type=self.config.method_type + method_type=self.config.method_type, ) # Update the config object from the dictionary instead of replacing it self.config = NewEventDetectorConfig.from_dict(config_dict, self.name) + events = self.config.events + if isinstance(events, EventsConfig) and not events.events: + logger.warning( + f"[{self.name}] auto_config=True generated an empty configuration. " + "No stable variables were found in configure-phase data. " + "The detector will produce no alerts." + ) diff --git a/tests/test_detectors/test_new_event_detector.py b/tests/test_detectors/test_new_event_detector.py index 49ece9a..2efaeb9 100644 --- a/tests/test_detectors/test_new_event_detector.py +++ b/tests/test_detectors/test_new_event_detector.py @@ -8,12 +8,14 @@ - Input/output schema validation """ -from detectmatelibrary.detectors.new_event_detector import NewEventDetector # , BufferMode +from detectmatelibrary.detectors.new_event_detector import NewEventDetector, NewEventDetectorConfig # , BufferMode from detectmatelibrary.parsers.template_matcher import MatcherParser from detectmatelibrary.helper.from_to import From import detectmatelibrary.schemas as schemas from detectmatelibrary.utils.aux import time_test_mode +from detectmatelibrary.common._core_op._fit_logic import ConfigState, TrainState +from detectmatelibrary.constants import GLOBAL_EVENT_ID # Set time test mode for consistent timestamps @@ -252,3 +254,76 @@ def test_audit_log_anomalies(self): detected_ids.add(log["logID"]) # assert detected_ids == {'1859', '1860', '1861', '1862', '1864', '1865', '1866', '1867'} + +class TestNewEventDetectorAutoConfig: + """Test that process() drives configure/set_configuration/train/detect + automatically.""" + + def test_audit_log_anomalies_via_process(self): + parser = MatcherParser(config=_PARSER_CONFIG) + detector = NewEventDetector() + + logs = list(From.log(parser, in_path="tests/test_folder/audit.log", do_process=True)) + + # Phase 1: configure — keep configuring for logs[:1800] + detector.fitlogic.configure_state = ConfigState.KEEP_CONFIGURE + for log in logs[:1800]: + detector.process(log) + + # Transition: stop configure so next process() call triggers set_configuration() + detector.fitlogic.configure_state = ConfigState.STOP_CONFIGURE + + # Phase 2: train — keep training for logs[:1800] + detector.fitlogic.train_state = TrainState.KEEP_TRAINING + for log in logs[:1800]: + detector.process(log) + + # Phase 3: detect — stop training so process() only calls detect() + detector.fitlogic.train_state = TrainState.STOP_TRAINING + detected_ids: set[str] = set() + for log in logs[1800:]: + if detector.process(log) is not None: + detected_ids.add(log["logID"]) + + assert detected_ids == {'1859', '1860', '1861', '1862', '1864', '1865', '1866', '1867'} + + +class TestNewEventDetectorGlobalInstances: + """Tests event-ID-independent global instance detection.""" + + def test_global_instance_detects_new_type(self): + """Global instance monitoring Type detects CRED_REFR, USER_AUTH, + USER_CMD which only appear after the training window (line 1800+).""" + parser = MatcherParser(config=_PARSER_CONFIG) + config_dict = { + "detectors": { + "NewEventDetector": { + "method_type": "new_event_detector", + "auto_config": False, + "global": { + "test": { + "header_variables": [{"pos": "Type"}] + } + } + } + } + } + config = NewEventDetectorConfig.from_dict(config_dict, "NewEventDetector") + detector = NewEventDetector(config=config) + + logs = list(From.log(parser, in_path="tests/test_folder/audit.log", do_process=True)) + + for log in logs[:1800]: + detector.train(log) + + # Global tracker must be populated under the sentinel event ID + assert GLOBAL_EVENT_ID in detector.persistency.get_events_data() + + detected_ids: set[str] = set() + for log in logs[1800:]: + output = schemas.DetectorSchema() + if detector.detect(log, output_=output): + assert all(key.startswith("Global -") for key in output["alertsObtain"]) + detected_ids.add(log["logID"]) + + assert len(detected_ids) > 0 From dfd2ca19f5b0ab002ee6df88fd916309b19d3f4a Mon Sep 17 00:00:00 2001 From: Ernst Leierzopf Date: Fri, 10 Apr 2026 12:43:09 +0200 Subject: [PATCH 2/6] finalize implementation of NewEventDetector. --- src/detectmatelibrary/common/core.py | 12 +- .../detectors/new_event_detector.py | 63 +--- .../test_detectors/test_new_event_detector.py | 291 +++++++----------- 3 files changed, 128 insertions(+), 238 deletions(-) diff --git a/src/detectmatelibrary/common/core.py b/src/detectmatelibrary/common/core.py index 02afb3c..be561f4 100644 --- a/src/detectmatelibrary/common/core.py +++ b/src/detectmatelibrary/common/core.py @@ -93,25 +93,25 @@ def process(self, data: BaseSchema | bytes) -> BaseSchema | bytes | None: return None if (fit_state := self.fitlogic.run()) == FitLogicState.DO_CONFIG: - logger.info(f"<<{self.name}>> use data for configuration") + logger.debug(f"<<{self.name}>> use data for configuration") self.configure(input_=data_buffered) return None elif self.fitlogic.finish_config(): - logger.info(f"<<{self.name}>> finalizing configuration") + logger.debug(f"<<{self.name}>> finalizing configuration") self.set_configuration() if fit_state == FitLogicState.DO_TRAIN: - logger.info(f"<<{self.name}>> use data for training") + logger.debug(f"<<{self.name}>> use data for training") self.train(input_=data_buffered) elif self.fitlogic.finish_training(): - logger.info(f"<<{self.name}>> finalizing training") + logger.debug(f"<<{self.name}>> finalizing training") self.post_train() output_ = self.output_schema() - logger.info(f"<<{self.name}>> processing data") + logger.debug(f"<<{self.name}>> processing data") return_schema = self.run(input_=data_buffered, output_=output_) if not return_schema: - logger.info(f"<<{self.name}>> returns None") + logger.debug(f"<<{self.name}>> returns None") return None logger.debug(f"<<{self.name}>> processed:\n{output_}") diff --git a/src/detectmatelibrary/detectors/new_event_detector.py b/src/detectmatelibrary/detectors/new_event_detector.py index ad91813..a5d521a 100644 --- a/src/detectmatelibrary/detectors/new_event_detector.py +++ b/src/detectmatelibrary/detectors/new_event_detector.py @@ -15,9 +15,6 @@ class NewEventDetectorConfig(CoreDetectorConfig): method_type: str = "new_event_detector" - use_stable_vars: bool = True - use_static_vars: bool = True - class NewEventDetector(CoreDetector): """Detect new values in log data as anomalies based on learned values.""" @@ -32,7 +29,7 @@ def __init__( config = NewEventDetectorConfig.from_dict(config, name) super().__init__(name=name, buffer_mode=BufferMode.NO_BUF, config=config) - self.config: NewEventDetectorConfig # type narrowing for IDE + self.config: NewEventDetectorConfig self.persistency = EventPersistency( event_data_class=EventStabilityTracker, ) @@ -43,19 +40,16 @@ def __init__( def train(self, input_: ParserSchema) -> None: # type: ignore """Train the detector by learning values from the input data.""" - configured_variables = get_configured_variables(input_, self.config.events) self.persistency.ingest_event( event_id=input_["EventID"], - event_template=input_["template"], - named_variables=configured_variables + event_template=input_["template"] ) if self.config.global_instances: global_vars = get_global_variables(input_, self.config.global_instances) if global_vars: self.persistency.ingest_event( event_id=GLOBAL_EVENT_ID, - event_template=input_["template"], - named_variables=global_vars + event_template=input_["template"] ) def detect( @@ -63,38 +57,25 @@ def detect( ) -> bool: """Detect new values in the input data.""" alerts: dict[str, str] = {} - configured_variables = get_configured_variables(input_, self.config.events) overall_score = 0.0 current_event_id = input_["EventID"] - known_events = self.persistency.get_events_data() - - if current_event_id in known_events: - event_tracker = known_events[current_event_id] - for var_name, multi_tracker in event_tracker.get_data().items(): - value = configured_variables.get(var_name) - if value is None: - continue - if value not in multi_tracker.unique_set: - alerts[f"EventID {current_event_id} - {var_name}"] = ( - f"Unknown value: '{value}'" - ) - overall_score += 1.0 + known_events = self.persistency.get_events_seen() if self.config.global_instances and GLOBAL_EVENT_ID in known_events: global_vars = get_global_variables(input_, self.config.global_instances) - global_tracker = known_events[GLOBAL_EVENT_ID] - for var_name, multi_tracker in global_tracker.get_data().items(): - value = global_vars.get(var_name) - if value is None: - continue - if value not in multi_tracker.unique_set: - alerts[f"Global - {var_name}"] = f"Unknown value: '{value}'" - overall_score += 1.0 + alerts[f"Global - {global_vars}"] = f"Unknown event ID: '{current_event_id}'" + overall_score += 1.0 + elif current_event_id not in known_events: + configured_variables = get_configured_variables(input_, self.config.events) + alerts[f"EventID {current_event_id} - {configured_variables}"] = ( + f"Unknown event ID: '{current_event_id}'" + ) + overall_score += 1.0 if overall_score > 0: output_["score"] = overall_score - output_["description"] = f"{self.name} detects values not encountered in training as anomalies." + output_["description"] = f"{self.name} detects event IDs not encountered in training as anomalies." output_["alertsObtain"].update(alerts) return True @@ -103,27 +84,17 @@ def detect( def configure(self, input_: ParserSchema) -> None: # type: ignore self.auto_conf_persistency.ingest_event( event_id=input_["EventID"], - event_template=input_["template"], - variables=input_["variables"], - named_variables=input_["logFormatVariables"], + event_template=input_["template"] ) def set_configuration(self) -> None: variables = {} - for event_id, tracker in self.auto_conf_persistency.get_events_data().items(): - stable = [] - if self.config.use_stable_vars: - stable = tracker.get_features_by_classification("STABLE") # type: ignore - static = [] - if self.config.use_static_vars: - static = tracker.get_features_by_classification("STATIC") # type: ignore - vars_ = stable + static - if len(vars_) > 0: - variables[event_id] = vars_ + for event_id in self.auto_conf_persistency.get_events_seen(): + variables[event_id] = {} config_dict = generate_detector_config( variable_selection=variables, detector_name=self.name, - method_type=self.config.method_type, + method_type=self.config.method_type ) # Update the config object from the dictionary instead of replacing it self.config = NewEventDetectorConfig.from_dict(config_dict, self.name) diff --git a/tests/test_detectors/test_new_event_detector.py b/tests/test_detectors/test_new_event_detector.py index 2efaeb9..61cc1e9 100644 --- a/tests/test_detectors/test_new_event_detector.py +++ b/tests/test_detectors/test_new_event_detector.py @@ -8,7 +8,7 @@ - Input/output schema validation """ -from detectmatelibrary.detectors.new_event_detector import NewEventDetector, NewEventDetectorConfig # , BufferMode +from detectmatelibrary.detectors.new_event_detector import NewEventDetector, NewEventDetectorConfig, BufferMode from detectmatelibrary.parsers.template_matcher import MatcherParser from detectmatelibrary.helper.from_to import From import detectmatelibrary.schemas as schemas @@ -24,195 +24,114 @@ config = { "detectors": { - # "CustomInit": { - # "method_type": "new_event_detector", - # "auto_config": False, - # "params": {}, - # "events": { - # 1: { - # "instance1": { - # "params": {}, - # "variables": [{ - # "pos": 0, "name": "sad", "params": {} - # }] - # } - # } - # } - # }, - # "MultipleDetector": { - # "method_type": "new_event_detector", - # "auto_config": False, - # "params": {}, - # "events": { - # 1: { - # "test": { - # "params": {}, - # "variables": [{ - # "pos": 1, "name": "test", "params": {} - # }], - # "header_variables": [{ - # "pos": "level", "params": {} - # }] - # } - # } - # } - # }, + "CustomInit": { + "method_type": "new_event_detector", + "auto_config": False, + "params": {} + }, + "MultipleDetector": { + "method_type": "new_event_detector", + "auto_config": False, + "params": {} + }, "NewEventDetector": { "method_type": "new_event_detector", "auto_config": False, - "params": {}, - # "events": { - # 3: { - # "test": { - # "params": {}, - # #"variables": [{ - # # "pos": 1, "name": "test", "params": {} - # #}], - # "header_variables": [{ - # "pos": "Type", "params": {} - # }] - # } - # } - # } - "events": {} + "params": {} } } } -# class TestNewEventDetectorInitialization: -# """Test NewEventDetector initialization and configuration.""" -# -# def test_default_initialization(self): -# """Test detector initialization with default parameters.""" -# detector = NewEventDetector() -# -# assert detector.name == "NewEventDetector" -# assert hasattr(detector, 'config') -# assert detector.data_buffer.mode == BufferMode.NO_BUF -# assert detector.input_schema == schemas.ParserSchema -# assert detector.output_schema == schemas.DetectorSchema -# assert hasattr(detector, 'persistency') -# -# def test_custom_config_initialization(self): -# """Test detector initialization with custom configuration.""" -# detector = NewEventDetector(name="CustomInit", config=config) -# -# assert detector.name == "CustomInit" -# assert hasattr(detector, 'persistency') -# assert isinstance(detector.persistency.events_data, dict) -# -# -# class TestNewEventDetectorTraining: -# """Test NewEventDetector training functionality.""" -# -# def test_train_multiple_values(self): -# """Test training with multiple different values.""" -# detector = NewEventDetector(config=config, name="MultipleDetector") -# # Train with multiple values (only event 1 should be tracked per config) -# for event in range(3): -# for level in ["INFO", "WARNING", "ERROR"]: -# parser_data = schemas.ParserSchema({ -# "parserType": "test", -# "EventID": event, -# "template": "test template", -# "variables": ["0", "assa"], -# "logID": "1", -# "parsedLogID": "1", -# "parserID": "test_parser", -# "log": "test log message", -# "logFormatVariables": {"level": level} -# }) -# detector.train(parser_data) -# -# # Only event 1 should be tracked (based on events config) -# assert len(detector.persistency.events_data) == 1 -# event_data = detector.persistency.get_event_data(1) -# assert event_data is not None -# # Check the level values -# assert "INFO" in event_data["level"].unique_set -# assert "WARNING" in event_data["level"].unique_set -# assert "ERROR" in event_data["level"].unique_set -# # Check the variable at position 1 (named "test") -# assert "assa" in event_data["test"].unique_set -# -# -# class TestNewEventDetectorDetection: -# """Test NewEventDetector detection functionality.""" -# -# def test_detect_known_value_no_alert(self): -# detector = NewEventDetector(config=config, name="MultipleDetector") -# -# # Train with a value -# train_data = schemas.ParserSchema({ -# "parserType": "test", -# "EventID": 1, -# "template": "test template", -# "variables": ["adsasd", "asdasd"], -# "logID": "1", -# "parsedLogID": "1", -# "parserID": "test_parser", -# "log": "test log message", -# "logFormatVariables": {"level": "INFO"} -# }) -# detector.train(train_data) -# -# # Detect with the same value -# test_data = schemas.ParserSchema({ -# "parserType": "test", -# "EventID": 12, -# "template": "test template", -# "variables": ["adsasd"], -# "logID": "2", -# "parsedLogID": "2", -# "parserID": "test_parser", -# "log": "test log message", -# "logFormatVariables": {"level": "CRITICAL"} -# }) -# output = schemas.DetectorSchema() -# -# result = detector.detect(test_data, output) -# -# assert not result -# assert output.score == 0.0 -# -# def test_detect_known_value_alert(self): -# detector = NewEventDetector(config=config, name="MultipleDetector") -# -# # Train with a value -# train_data = schemas.ParserSchema({ -# "parserType": "test", -# "EventID": 1, -# "template": "test template", -# "variables": ["adsasd", "asdasd"], -# "logID": "1", -# "parsedLogID": "1", -# "parserID": "test_parser", -# "log": "test log message", -# "logFormatVariables": {"level": "INFO"} -# }) -# detector.train(train_data) -# -# # Detect with the same value -# test_data = schemas.ParserSchema({ -# "parserType": "test", -# "EventID": 1, -# "template": "test template", -# "variables": ["adsasd", "asdasd"], -# "logID": "2", -# "parsedLogID": "2", -# "parserID": "test_parser", -# "log": "test log message", -# "logFormatVariables": {"level": "CRITICAL"} -# }) -# output = schemas.DetectorSchema() -# -# result = detector.detect(test_data, output) -# -# assert result -# assert output.score == 1.0 -# -# +class TestNewEventDetectorInitialization: + """Test NewEventDetector initialization and configuration.""" + + def test_default_initialization(self): + """Test detector initialization with default parameters.""" + detector = NewEventDetector() + + assert detector.name == "NewEventDetector" + assert hasattr(detector, 'config') + assert detector.data_buffer.mode == BufferMode.NO_BUF + assert detector.input_schema == schemas.ParserSchema + assert detector.output_schema == schemas.DetectorSchema + assert hasattr(detector, 'persistency') + + def test_custom_config_initialization(self): + """Test detector initialization with custom configuration.""" + detector = NewEventDetector(name="CustomInit", config=config) + + assert detector.name == "CustomInit" + assert hasattr(detector, 'persistency') + assert isinstance(detector.persistency.events_data, dict) + + +class TestNewEventDetectorTraining: + """Test NewEventDetector training functionality.""" + + def test_train_multiple_event_ids(self): + """Test training with multiple different event ids.""" + detector = NewEventDetector(config=config, name="MultipleDetector") + event_ids = {0, 3, 8, 9} + for event in event_ids: + parser_data = schemas.ParserSchema({ + "parserType": "test", + "EventID": event, + "template": "test template", + "variables": ["0", "assa"], + "logID": "1", + "parsedLogID": "1", + "parserID": "test_parser", + "log": "test log message", + "logFormatVariables": {"level": "INFO"} + }) + detector.train(parser_data) + + assert len(detector.persistency.events_seen) == len(event_ids) + event_seen = detector.persistency.get_events_seen() + assert event_seen == event_ids + + +class TestNewEventDetectorDetection: + """Test NewEventDetector detection functionality.""" + + def test_detect_known_event_id_no_alert(self): + detector = NewEventDetector(config=config, name="MultipleDetector") + + # Train with an event_id + train_data = schemas.ParserSchema({ + "parserType": "test", + "EventID": 1, + "template": "test template", + "variables": ["adsasd", "asdasd"], + "logID": "1", + "parsedLogID": "1", + "parserID": "test_parser", + "log": "test log message", + "logFormatVariables": {"level": "INFO"} + }) + detector.train(train_data) + + # Detect with the same event_id + test_data = schemas.ParserSchema({ + "parserType": "test", + "EventID": 1, + "template": "test template", + "variables": ["adsasd"], + "logID": "2", + "parsedLogID": "2", + "parserID": "test_parser", + "log": "test log message", + "logFormatVariables": {"level": "CRITICAL"} + }) + output = schemas.DetectorSchema() + + result = detector.detect(test_data, output) + + assert not result + assert output.score == 0.0 + + _PARSER_CONFIG = { "parsers": { "MatcherParser": { @@ -248,12 +167,12 @@ def test_audit_log_anomalies(self): detector.train(log) detected_ids: set[str] = set() - for log in logs[1800:]: + for i, log in enumerate(logs[1800:]): output = schemas.DetectorSchema() if detector.detect(log, output_=output): detected_ids.add(log["logID"]) - # assert detected_ids == {'1859', '1860', '1861', '1862', '1864', '1865', '1866', '1867'} + assert detected_ids == {"1863"} class TestNewEventDetectorAutoConfig: """Test that process() drives configure/set_configuration/train/detect @@ -285,7 +204,7 @@ def test_audit_log_anomalies_via_process(self): if detector.process(log) is not None: detected_ids.add(log["logID"]) - assert detected_ids == {'1859', '1860', '1861', '1862', '1864', '1865', '1866', '1867'} + assert detected_ids == {"1863"} class TestNewEventDetectorGlobalInstances: @@ -317,7 +236,7 @@ def test_global_instance_detects_new_type(self): detector.train(log) # Global tracker must be populated under the sentinel event ID - assert GLOBAL_EVENT_ID in detector.persistency.get_events_data() + assert GLOBAL_EVENT_ID in detector.persistency.get_events_seen() detected_ids: set[str] = set() for log in logs[1800:]: From 21a13b6393f77dc6a74a6775d39d43959528f1d0 Mon Sep 17 00:00:00 2001 From: Ernst Leierzopf Date: Fri, 10 Apr 2026 12:48:06 +0200 Subject: [PATCH 3/6] fix prek issues. --- src/detectmatelibrary/detectors/new_event_detector.py | 5 +++-- tests/test_detectors/test_new_event_detector.py | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/detectmatelibrary/detectors/new_event_detector.py b/src/detectmatelibrary/detectors/new_event_detector.py index a5d521a..edd998e 100644 --- a/src/detectmatelibrary/detectors/new_event_detector.py +++ b/src/detectmatelibrary/detectors/new_event_detector.py @@ -75,7 +75,8 @@ def detect( if overall_score > 0: output_["score"] = overall_score - output_["description"] = f"{self.name} detects event IDs not encountered in training as anomalies." + output_["description"] = \ + f"{self.name} detects event IDs not encountered in training as anomalies." output_["alertsObtain"].update(alerts) return True @@ -88,7 +89,7 @@ def configure(self, input_: ParserSchema) -> None: # type: ignore ) def set_configuration(self) -> None: - variables = {} + variables = {} # type: ignore for event_id in self.auto_conf_persistency.get_events_seen(): variables[event_id] = {} config_dict = generate_detector_config( diff --git a/tests/test_detectors/test_new_event_detector.py b/tests/test_detectors/test_new_event_detector.py index 61cc1e9..cfe4cc1 100644 --- a/tests/test_detectors/test_new_event_detector.py +++ b/tests/test_detectors/test_new_event_detector.py @@ -8,7 +8,8 @@ - Input/output schema validation """ -from detectmatelibrary.detectors.new_event_detector import NewEventDetector, NewEventDetectorConfig, BufferMode +from detectmatelibrary.detectors.new_event_detector import NewEventDetector, NewEventDetectorConfig, \ + BufferMode from detectmatelibrary.parsers.template_matcher import MatcherParser from detectmatelibrary.helper.from_to import From import detectmatelibrary.schemas as schemas @@ -174,6 +175,7 @@ def test_audit_log_anomalies(self): assert detected_ids == {"1863"} + class TestNewEventDetectorAutoConfig: """Test that process() drives configure/set_configuration/train/detect automatically.""" From 5f12c0f5d289abdf59b56c31ca9f4046a29ddd9d Mon Sep 17 00:00:00 2001 From: Ernst Leierzopf Date: Fri, 10 Apr 2026 23:32:39 +0200 Subject: [PATCH 4/6] add new_event docs. --- docs/detectors/new_event.md | 50 +++++++++++++++++++++++++++++++++++++ docs/detectors/new_value.md | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/detectors/new_event.md b/docs/detectors/new_event.md index e69de29..5573e86 100644 --- a/docs/detectors/new_event.md +++ b/docs/detectors/new_event.md @@ -0,0 +1,50 @@ +# New Event Detector + +The New Event Detector raises alerts when previously unseen log templates, distinguished by event IDs, appear in log data. It is useful to detect unexpected types of events in the environment. + +| | Schema | Description | +|------------|----------------------------|--------------------| +| **Input** | [ParserSchema](../schemas.md) | Structured log | +| **Output** | [DetectorSchema](../schemas.md) | Alert / finding | + +## Description + +This detector maintains a lightweight set of observed event IDs and emits an alert when an event ID not present in the set is seen for the first time (subject to configuration). + + +## Configuration example + +```yaml +detectors: + NewEventDetector: + method_type: new_event_detector + auto_config: False + params: {} +``` + + +## Example usage + +```python +from detectmatelibrary.detectors.new_value_detector import NewValueDetector, BufferMode +import detectmatelibrary.schemas as schemas + +detector = NewEventDetector(name="NewEventTest", config=cfg) + +parser_data = schemas.ParserSchema({ + "parserType": "test", + "EventID": 1, + "template": "test template", + "variables": ["var1"], + "logID": "1", + "parsedLogID": "1", + "parserID": "test_parser", + "log": "test log message", + "logFormatVariables": {"timestamp": "123456"} +}) + + +alert = detector.process(parsed_data) +``` + +Go back [Index](../index.md) diff --git a/docs/detectors/new_value.md b/docs/detectors/new_value.md index 5d044c4..5fa15e5 100644 --- a/docs/detectors/new_value.md +++ b/docs/detectors/new_value.md @@ -9,7 +9,7 @@ The New Value Detector raises alerts when previously unseen values appear in con ## Description -This detector maintains a lightweight set of observed values per monitored field and emits an alert when a value not present in the set is seen for the first time (subject to configuration). . +This detector maintains a lightweight set of observed values per monitored field and emits an alert when a value not present in the set is seen for the first time (subject to configuration). ## Configuration example From a7f13577435e1296ef64e90e3e6b64ed760fabf9 Mon Sep 17 00:00:00 2001 From: Ernst Leierzopf Date: Sat, 11 Apr 2026 00:28:40 +0200 Subject: [PATCH 5/6] add NewEventDetector to pipeline_config_default. --- config/pipeline_config_default.yaml | 5 +++++ docs/detectors/new_event.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config/pipeline_config_default.yaml b/config/pipeline_config_default.yaml index 4271752..84a53df 100644 --- a/config/pipeline_config_default.yaml +++ b/config/pipeline_config_default.yaml @@ -77,3 +77,8 @@ detectors: name: var1 header_variables: - pos: level + + NewEventDetector: + method_type: new_event_detector + auto_config: False + params: {} diff --git a/docs/detectors/new_event.md b/docs/detectors/new_event.md index 5573e86..5f7a028 100644 --- a/docs/detectors/new_event.md +++ b/docs/detectors/new_event.md @@ -26,7 +26,7 @@ detectors: ## Example usage ```python -from detectmatelibrary.detectors.new_value_detector import NewValueDetector, BufferMode +from detectmatelibrary.detectors.new_event_detector import NewEventDetector, BufferMode import detectmatelibrary.schemas as schemas detector = NewEventDetector(name="NewEventTest", config=cfg) From 0e2e81bbe526c5501edb77dc56de227bc97f9ffa Mon Sep 17 00:00:00 2001 From: viktorbeck98 Date: Tue, 14 Apr 2026 14:30:11 +0200 Subject: [PATCH 6/6] small change for auto config --- .../detectors/new_event_detector.py | 14 +------------- tests/test_detectors/test_new_event_detector.py | 4 ++++ 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/detectmatelibrary/detectors/new_event_detector.py b/src/detectmatelibrary/detectors/new_event_detector.py index edd998e..39097f7 100644 --- a/src/detectmatelibrary/detectors/new_event_detector.py +++ b/src/detectmatelibrary/detectors/new_event_detector.py @@ -1,5 +1,4 @@ from detectmatelibrary.common._config._compile import generate_detector_config -from detectmatelibrary.common._config._formats import EventsConfig from detectmatelibrary.common.detector import CoreDetectorConfig, CoreDetector, get_configured_variables, \ get_global_variables from detectmatelibrary.utils.persistency.event_data_structures.trackers.stability.stability_tracker import ( @@ -9,7 +8,6 @@ from detectmatelibrary.utils.persistency.event_persistency import EventPersistency from detectmatelibrary.utils.data_buffer import BufferMode from detectmatelibrary.schemas import ParserSchema, DetectorSchema -from tools.logging import logger class NewEventDetectorConfig(CoreDetectorConfig): @@ -89,20 +87,10 @@ def configure(self, input_: ParserSchema) -> None: # type: ignore ) def set_configuration(self) -> None: - variables = {} # type: ignore - for event_id in self.auto_conf_persistency.get_events_seen(): - variables[event_id] = {} config_dict = generate_detector_config( - variable_selection=variables, + variable_selection={}, detector_name=self.name, method_type=self.config.method_type ) # Update the config object from the dictionary instead of replacing it self.config = NewEventDetectorConfig.from_dict(config_dict, self.name) - events = self.config.events - if isinstance(events, EventsConfig) and not events.events: - logger.warning( - f"[{self.name}] auto_config=True generated an empty configuration. " - "No stable variables were found in configure-phase data. " - "The detector will produce no alerts." - ) diff --git a/tests/test_detectors/test_new_event_detector.py b/tests/test_detectors/test_new_event_detector.py index cfe4cc1..015c24f 100644 --- a/tests/test_detectors/test_new_event_detector.py +++ b/tests/test_detectors/test_new_event_detector.py @@ -8,6 +8,8 @@ - Input/output schema validation """ +import json + from detectmatelibrary.detectors.new_event_detector import NewEventDetector, NewEventDetectorConfig, \ BufferMode from detectmatelibrary.parsers.template_matcher import MatcherParser @@ -199,6 +201,8 @@ def test_audit_log_anomalies_via_process(self): for log in logs[:1800]: detector.process(log) + print(json.dumps(detector.config.get_config(), indent=2)) + # Phase 3: detect — stop training so process() only calls detect() detector.fitlogic.train_state = TrainState.STOP_TRAINING detected_ids: set[str] = set()