Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,6 @@ cython_debug/
local/
test.ipynb
test.py

# claude code
CLAUDE.md
2 changes: 0 additions & 2 deletions config/pipeline_config_default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ detectors:
NewValueComboDetector:
method_type: new_value_combo_detector
auto_config: False
params:
comb_size: 3
events:
1:
test:
Expand Down
2 changes: 1 addition & 1 deletion docs/detectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ The `set_configuration()` method queries the tracker results and generates the f
def set_configuration(self):
variables = {}
for event_id, tracker in self.auto_conf_persistency.get_events_data().items():
stable_vars = tracker.get_variables_by_classification("STABLE")
stable_vars = tracker.get_features_by_classification("STABLE")
variables[event_id] = stable_vars

config_dict = generate_detector_config(
Expand Down
2 changes: 1 addition & 1 deletion docs/detectors/combo.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ detectors:
method_type: new_value_combo_detector
auto_config: False
params:
comb_size: 3
max_combo_size: 3
events:
1:
test:
Expand Down
4 changes: 2 additions & 2 deletions src/detectmatelibrary/common/_config/_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def generate_detector_config(
detector_name: Name of the detector, used as the base instance_id.
method_type: Type of detection method (e.g., "new_value_detector").
**additional_params: Additional parameters for the detector's params
dict (e.g., comb_size=3).
dict (e.g., max_combo_size=3).

Returns:
Dictionary with structure compatible with detector config classes.
Expand All @@ -162,7 +162,7 @@ def generate_detector_config(
variable_selection={1: [("username", "src_ip"), ("var_0", "var_1")]},
detector_name="MyDetector",
method_type="new_value_combo_detector",
comb_size=2,
max_combo_size=2,
)
"""
var_pattern = re.compile(r"^var_(\d+)$")
Expand Down
26 changes: 18 additions & 8 deletions src/detectmatelibrary/detectors/new_value_combo_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ class NewValueComboDetectorConfig(CoreDetectorConfig):

events: EventsConfig | dict[str, Any] = {}
global_instances: Dict[str, _EventInstance] = {}
comb_size: int = 2

max_combo_size: int = 3
use_stable_vars: bool = True
use_static_vars: bool = False


class NewValueComboDetector(CoreDetector):
Expand Down Expand Up @@ -162,7 +165,7 @@ def configure(self, input_: ParserSchema) -> None: # type: ignore
named_variables=input_["logFormatVariables"],
)

def set_configuration(self, max_combo_size: int = 3) -> None:
def set_configuration(self, max_combo_size: int | None = None) -> None:
"""Set the detector configuration based on the stability of variable
combinations.

Expand All @@ -172,17 +175,18 @@ def set_configuration(self, max_combo_size: int = 3) -> None:
3. Re-ingest all events to learn the stability of these combos (testing all possible combos right away
would explode combinatorially).
"""
config = cast(NewValueComboDetectorConfig, self.config)
# run WITH auto_conf_persistency
variable_combos = {}
for event_id, tracker in self.auto_conf_persistency.get_events_data().items():
stable_vars = tracker.get_variables_by_classification("STABLE") # type: ignore
stable_vars = tracker.get_features_by_classification("STABLE") # type: ignore
if len(stable_vars) > 1:
variable_combos[event_id] = stable_vars
config_dict = generate_detector_config(
variable_selection=variable_combos,
detector_name=self.name,
method_type=self.config.method_type,
comb_size=max_combo_size
max_combo_size=max_combo_size or config.max_combo_size
)
# Update the config object from the dictionary instead of replacing it
self.config = NewValueComboDetectorConfig.from_dict(config_dict, self.name)
Expand All @@ -199,15 +203,21 @@ def set_configuration(self, max_combo_size: int = 3) -> None:
# rerun to set final config WITH auto_conf_persistency_combos
combo_selection = {}
for event_id, tracker in self.auto_conf_persistency_combos.get_events_data().items():
stable_combos = tracker.get_variables_by_classification("STABLE") # type: ignore
stable_combos = []
if self.config.use_stable_vars:
stable_combos = tracker.get_features_by_classification("STABLE") # type: ignore
static_combos = []
if self.config.use_static_vars:
static_combos = tracker.get_features_by_classification("STATIC") # type: ignore
combos = stable_combos + static_combos
# Keep combos as tuples - each will become a separate config entry
if len(stable_combos) >= 1:
combo_selection[event_id] = stable_combos
if len(combos) > 0:
combo_selection[event_id] = combos
config_dict = generate_detector_config(
variable_selection=combo_selection,
detector_name=self.name,
method_type=self.config.method_type,
comb_size=max_combo_size
max_combo_size=max_combo_size or self.config.max_combo_size
)
# Update the config object from the dictionary instead of replacing it
self.config = NewValueComboDetectorConfig.from_dict(config_dict, self.name)
13 changes: 11 additions & 2 deletions src/detectmatelibrary/detectors/new_value_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class NewValueDetectorConfig(CoreDetectorConfig):

events: EventsConfig | dict[str, Any] = {}
global_instances: Dict[str, _EventInstance] = {}
use_stable_vars: bool = True
use_static_vars: bool = True


class NewValueDetector(CoreDetector):
Expand Down Expand Up @@ -118,8 +120,15 @@ 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():
stable_vars = tracker.get_variables_by_classification("STABLE") # type: ignore
variables[event_id] = stable_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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class MultiStabilityTracker(MultiTracker):
"""Tracks multiple features (e.g. variables or variable combos) using
individual trackers."""

def get_variables_by_classification(
def get_features_by_classification(
self,
classification_type: Literal["INSUFFICIENT_DATA", "STATIC", "RANDOM", "STABLE", "UNSTABLE"]
) -> List[str]:
Expand All @@ -99,9 +99,9 @@ def __init__(self, converter_function: Callable[[Any], Any] = lambda x: x) -> No
converter_function=converter_function,
)

def get_variables_by_classification(
def get_features_by_classification(
self, classification_type: Literal["INSUFFICIENT_DATA", "STATIC", "RANDOM", "STABLE", "UNSTABLE"]
) -> List[str]:
"""Get a list of variable names that are classified as the given
type."""
return self.multi_tracker.get_variables_by_classification(classification_type)
return self.multi_tracker.get_features_by_classification(classification_type)
31 changes: 11 additions & 20 deletions tests/test_detectors/test_new_value_combo_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

from detectmatelibrary.utils.aux import time_test_mode

import pytest

# Set time test mode for consistent timestamps
time_test_mode()

Expand All @@ -21,7 +19,7 @@
"method_type": "new_value_combo_detector",
"auto_config": False,
"params": {
"comb_size": 4
"max_combo_size": 4
},
"events": {
1: {
Expand All @@ -38,7 +36,7 @@
"method_type": "new_value_combo_detector",
"auto_config": False,
"params": {
"comb_size": 2
"max_combo_size": 2
},
"events": {
1: {
Expand Down Expand Up @@ -72,7 +70,7 @@ def test_custom_config_initialization(self):
detector = NewValueComboDetector(name="CustomInit", config=config)

assert detector.name == "CustomInit"
assert detector.config.comb_size == 4
assert detector.config.max_combo_size == 4


class TestNewValueComboDetectorTraining:
Expand Down Expand Up @@ -215,14 +213,14 @@ def test_generate_detector_config_basic(self):
variable_selection=variable_selection,
detector_name="TestDetector",
method_type="new_value_combo_detector",
comb_size=2
max_combo_size=2
)

assert "detectors" in config_dict
assert "TestDetector" in config_dict["detectors"]
detector_config = config_dict["detectors"]["TestDetector"]
assert detector_config["method_type"] == "new_value_combo_detector"
assert detector_config["params"]["comb_size"] == 2
assert detector_config["params"]["max_combo_size"] == 2
assert len(detector_config["events"]) == 1

def test_generate_detector_config_multiple_events(self):
Expand Down Expand Up @@ -291,7 +289,7 @@ def test_set_configuration_updates_config(self):

# Verify config was updated
assert detector.config.events is not None
assert detector.config.comb_size == 2
assert detector.config.max_combo_size == 2

def test_configuration_workflow(self):
"""Test complete configuration workflow like in notebook."""
Expand Down Expand Up @@ -362,8 +360,8 @@ def test_set_configuration_with_combo_size(self):
# Set configuration with specific combo size
detector.set_configuration(max_combo_size=4)

# Verify comb_size was updated
assert detector.config.comb_size == 4
# Verify max_combo_size was updated
assert detector.config.max_combo_size == 4

def test_configuration_with_no_stable_variables(self):
"""Test configuration when no stable variables are found."""
Expand Down Expand Up @@ -551,22 +549,15 @@ def test_configure_only_selects_stable_event_types(self):
"MatcherParser": {
"method_type": "matcher_parser",
"auto_config": False,
"log_format": "type=<Type> msg=audit\\(<Time>\\): <Content>",
"time_format": None,
"params": {
"remove_spaces": True,
"remove_punctuation": True,
"lowercase": True,
"path_templates": "tests/test_folder/audit_templates.txt",
},
"log_format": "type=<Type> msg=audit(<Time>): <Content>",
"params": {"path_templates": "tests/test_folder/audit_templates.txt"},
}
}
}


class TestNewValueComboDetectorEndToEndWithRealData:
"""Regression test: full configure/train/detect pipeline on audit.log."""
@pytest.mark.skip(reason="no way of currently testing this")
def test_audit_log_anomalies(self):
parser = MatcherParser(config=_PARSER_CONFIG)
detector = NewValueComboDetector()
Expand All @@ -586,4 +577,4 @@ def test_audit_log_anomalies(self):
if detector.detect(log, output_=output):
detected_ids.add(log["logID"])

print(detected_ids)
assert detected_ids == {"1859", "1862", "1865", "1866"}
4 changes: 0 additions & 4 deletions tests/test_detectors/test_new_value_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@

from detectmatelibrary.utils.aux import time_test_mode

import pytest

# Set time test mode for consistent timestamps
time_test_mode()

Expand Down Expand Up @@ -245,7 +243,6 @@ class TestNewValueDetectorAutoConfig:
"""Test that process() drives configure/set_configuration/train/detect
automatically."""

@pytest.mark.skip(reason="This test is too late")
def test_audit_log_anomalies_via_process(self):
parser = MatcherParser(config=_PARSER_CONFIG)
detector = NewValueDetector()
Expand Down Expand Up @@ -278,7 +275,6 @@ def test_audit_log_anomalies_via_process(self):
class TestNewValueDetectorGlobalInstances:
"""Tests event-ID-independent global instance detection."""

@pytest.mark.skip(reason="This test is too late")
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+)."""
Expand Down
3 changes: 0 additions & 3 deletions tests/test_pipelines/test_configuration_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from detectmatelibrary.parsers.template_matcher import MatcherParser
from detectmatelibrary.helper.from_to import From

import pytest
import json

AUDIT_LOG = "tests/test_folder/audit.log"
Expand Down Expand Up @@ -33,7 +32,6 @@ def load_expected_anomaly_ids() -> set[str]:

class TestConfigurationEngineManual:
"""Mirrors the manual flow in 05_configuration_engine/detect.py."""
@pytest.mark.skip(reason="no way of currently testing this")
def test_configure_train_detect(self) -> None:
parser = MatcherParser(config=parser_config)
detector = NewValueDetector()
Expand All @@ -60,7 +58,6 @@ def test_configure_train_detect(self) -> None:
class TestConfigurationEngineAutomatic:
"""Tests the automated configure phase via process()."""

@pytest.mark.skip(reason="no way of currently testing this")
def test_process_configure_train_detect(self) -> None:
parser = MatcherParser(config=parser_config)
config = NewValueDetectorConfig(data_use_configure=TRAIN_UNTIL)
Expand Down
3 changes: 0 additions & 3 deletions tests/test_utils/test_aux.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from datetime import datetime
from time import sleep

import pytest


class TestTimestamp:
def test_test_mode(self) -> None:
Expand All @@ -16,7 +14,6 @@ def test_no_test_mode(self) -> None:
time_test_mode(False)
assert get_timestamp() != 0

@pytest.mark.skip(reason="This test is too slow")
def test_get_timestamp(self) -> None:
time = get_timestamp()
sleep(1)
Expand Down
8 changes: 4 additions & 4 deletions tests/test_utils/test_stability_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ def test_get_stable_variables(self):
"random_var": f"unique_{i}",
})

stable_vars = trackers.get_variables_by_classification("STABLE")
stable_vars = trackers.get_features_by_classification("STABLE")
assert isinstance(stable_vars, list)
# "stable_var" should be classified as STATIC

Expand Down Expand Up @@ -360,7 +360,7 @@ def test_get_stable_variables(self):
"random_var": f"unique_{i}",
})

stable_vars = evt.get_variables_by_classification("STABLE")
stable_vars = evt.get_features_by_classification("STABLE")
assert isinstance(stable_vars, list)

def test_integration_with_stability_tracker(self):
Expand All @@ -377,7 +377,7 @@ def test_integration_with_stability_tracker(self):

# Get data and variables
var_names = evt.get_variables()
stable_vars = evt.get_variables_by_classification("STABLE")
stable_vars = evt.get_features_by_classification("STABLE")

assert len(var_names) == 3
assert isinstance(stable_vars, list)
Expand Down Expand Up @@ -529,7 +529,7 @@ def test_event_variable_tracker_real_world_scenario(self):
})

var_names = evt.get_variables()
stable_vars = evt.get_variables_by_classification("STABLE")
stable_vars = evt.get_features_by_classification("STABLE")

assert len(var_names) == 5
assert isinstance(stable_vars, list)
Expand Down
Loading