Skip to content

Commit b7914f2

Browse files
authored
Refactor regex handling and optimize event processing
Refactor regex handling and improve event processing efficiency by pre-compiling regex patterns and using list comprehensions for filtering. Update event ID handling and ensure consistent entity type mapping.
1 parent 0f815a2 commit b7914f2

1 file changed

Lines changed: 42 additions & 33 deletions

File tree

spiderfoot/correlation.py

Lines changed: 42 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,11 @@ def build_db_criteria(self, matchrule: dict) -> dict:
170170
else:
171171
regexps = matchrule['value']
172172

173-
for r in regexps:
173+
# Pre-compile regex patterns for efficiency
174+
compiled_regexps = [re.compile(r) for r in regexps]
175+
for compiled_re in compiled_regexps:
174176
for t in self.types:
175-
if re.search(r, t[1]):
177+
if compiled_re.search(t[1]):
176178
criterias['eventType'].append(t[1])
177179

178180
if matchrule['method'] == 'exact':
@@ -227,19 +229,21 @@ def enrich_event_sources(self, events: dict) -> None:
227229
if not isinstance(events, dict):
228230
raise TypeError(f"events is {type(events)}; expected dict()")
229231

230-
event_chunks = [list(events.keys())[x:(x + 5000)] for x in range(0, len(list(events.keys())), 5000)]
232+
event_ids = list(events.keys())
233+
event_chunks = [event_ids[x:(x + 5000)] for x in range(0, len(event_ids), 5000)]
231234

232235
for chunk in event_chunks:
233236
# Get sources
234237
self.log.debug(f"Getting sources for {len(chunk)} events")
235238
source_data = self.dbh.scanElementSourcesDirect(self.scanId, chunk)
236239
for row in source_data:
240+
entity_type = self.type_entity_map.get(row[15])
237241
events[row[8]]['source'].append({
238242
'type': row[15],
239243
'data': row[2],
240244
'module': row[16],
241245
'id': row[9],
242-
'entity_type': self.type_entity_map[row[15]]
246+
'entity_type': entity_type
243247
})
244248

245249
def enrich_event_children(self, events: dict) -> None:
@@ -254,7 +258,8 @@ def enrich_event_children(self, events: dict) -> None:
254258
if not isinstance(events, dict):
255259
raise TypeError(f"events is {type(events)}; expected dict()")
256260

257-
event_chunks = [list(events.keys())[x:x + 5000] for x in range(0, len(list(events.keys())), 5000)]
261+
event_ids = list(events.keys())
262+
event_chunks = [event_ids[x:x + 5000] for x in range(0, len(event_ids), 5000)]
258263

259264
for chunk in event_chunks:
260265
# Get children
@@ -306,20 +311,23 @@ def enrich_event_entities(self, events: dict) -> None:
306311
self.log.debug(f"{len(entity_missing.keys())} entities are missing, going deeper...")
307312
new_missing = dict()
308313
self.log.debug(f"Getting sources for {len(entity_missing.keys())} items")
309-
if len(entity_missing.keys()) > 5000:
310-
chunks = [list(entity_missing.keys())[x:x + 5000] for x in range(0, len(list(entity_missing.keys())), 5000)]
314+
315+
missing_ids = list(entity_missing.keys())
316+
if len(missing_ids) > 5000:
317+
chunks = [missing_ids[x:x + 5000] for x in range(0, len(missing_ids), 5000)]
311318
entity_data = list()
312319
self.log.debug("Fetching data in chunks")
313320
for chunk in chunks:
314321
self.log.debug(f"chunk size: {len(chunk)}")
315322
entity_data.extend(self.dbh.scanElementSourcesDirect(self.scanId, chunk))
316323
else:
317324
self.log.debug(f"fetching sources for {len(entity_missing)} items")
318-
entity_data = self.dbh.scanElementSourcesDirect(self.scanId, list(entity_missing.keys()))
325+
entity_data = self.dbh.scanElementSourcesDirect(self.scanId, missing_ids)
319326

320327
for entity_candidate in entity_data:
321328
event_id = entity_missing[entity_candidate[8]]
322-
if self.type_entity_map[entity_candidate[15]] not in ['ENTITY', 'INTERNAL']:
329+
entity_type = self.type_entity_map.get(entity_candidate[15])
330+
if entity_type not in ['ENTITY', 'INTERNAL']:
323331
# key of this dictionary is the id we need to now get a source for,
324332
# and the value is the original ID of the item missing an entity
325333
new_missing[entity_candidate[9]] = event_id
@@ -329,13 +337,13 @@ def enrich_event_entities(self, events: dict) -> None:
329337
'data': entity_candidate[2],
330338
'module': entity_candidate[16],
331339
'id': entity_candidate[9],
332-
'entity_type': self.type_entity_map[entity_candidate[15]]
340+
'entity_type': entity_type
333341
})
334342

335343
if len(new_missing) == 0:
336344
break
337345

338-
entity_missing = deepcopy(new_missing)
346+
entity_missing = new_missing
339347

340348
def collect_from_db(self, matchrule: dict, fetchChildren: bool, fetchSources: bool, fetchEntities: bool) -> list:
341349
"""Collect event values from database.
@@ -362,12 +370,13 @@ def collect_from_db(self, matchrule: dict, fetchChildren: bool, fetchSources: bo
362370
query_args['instanceId'] = self.scanId
363371
self.log.debug(f"db query: {query_args}")
364372
for row in self.dbh.scanResultEvent(**query_args):
373+
entity_type = self.type_entity_map.get(row[4])
365374
events[row[8]] = {
366375
'type': row[4],
367376
'data': row[1],
368377
'module': row[3],
369378
'id': row[8],
370-
'entity_type': self.type_entity_map[row[4]],
379+
'entity_type': entity_type,
371380
'source': [],
372381
'child': [],
373382
'entity': []
@@ -407,13 +416,13 @@ def event_extract(self, event: dict, field: str) -> list:
407416

408417
return [event[field]]
409418

410-
def event_keep(self, event: dict, field: str, patterns: str, patterntype: str) -> bool:
419+
def event_keep(self, event: dict, field: str, patterns: list, patterntype: str) -> bool:
411420
"""Keep event field.
412421
413422
Args:
414423
event (dict): event
415424
field (str): TBD
416-
patterns (str): TBD
425+
patterns (list): TBD
417426
patterntype (str): TBD
418427
419428
Returns:
@@ -431,7 +440,7 @@ def event_keep(self, event: dict, field: str, patterns: str, patterntype: str) -
431440
for pattern in patterns:
432441
if pattern.startswith("not "):
433442
ret = True
434-
pattern = re.sub(r"^not\s+", "", pattern)
443+
pattern = pattern[4:].lstrip() # Faster than re.sub
435444
if value == pattern:
436445
return False
437446
else:
@@ -447,7 +456,7 @@ def event_keep(self, event: dict, field: str, patterns: str, patterntype: str) -
447456
for pattern in patterns:
448457
if pattern.startswith("not "):
449458
ret = True
450-
pattern = re.sub(r"^not\s+", "", pattern)
459+
pattern = pattern[4:].lstrip() # Faster than re.sub
451460
if re.search(pattern, value, re.IGNORECASE):
452461
return False
453462
else:
@@ -478,12 +487,8 @@ def refine_collection(self, matchrule: dict, events: list) -> None:
478487
field = matchrule['field']
479488
self.log.debug(f"attempting to match {patterns} against the {field} field in {len(events)} events")
480489

481-
# Go through each event, remove it if we shouldn't keep it
482-
# according to the match rule patterns.
483-
for event in events[:]:
484-
if not self.event_keep(event, field, patterns, matchrule['method']):
485-
self.log.debug(f"removing {event} because of {field}")
486-
events.remove(event)
490+
# Use list comprehension instead of .remove() in loop (O(n) instead of O(n²))
491+
events[:] = [e for e in events if self.event_keep(e, field, patterns, matchrule['method'])]
487492

488493
def collect_events(self, collection: dict, fetchChildren: bool, fetchSources: bool, fetchEntities: bool, collectIndex: int) -> list:
489494
"""Collect data for aggregation and analysis.
@@ -555,9 +560,8 @@ def event_strip(event: dict, field: str, value: str) -> None:
555560
"""
556561
topfield, subfield = field.split(".")
557562
if field.startswith(topfield + "."):
558-
for s in event[topfield]:
559-
if s[subfield] != value:
560-
event[topfield].remove(s)
563+
# Use list comprehension to avoid O(n²) removal
564+
event[topfield] = [s for s in event[topfield] if s[subfield] == value]
561565

562566
ret = dict()
563567
for e in events:
@@ -614,12 +618,12 @@ def analysis_match_all_to_first_collection(self, rule: dict, buckets: dict) -> N
614618
"""
615619
self.log.debug(f"called with buckets {buckets}")
616620

617-
def check_event(events: list, reference: list) -> bool:
621+
def check_event(events: list, reference: set) -> bool:
618622
"""Check event.
619623
620624
Args:
621625
events (list): TBD
622-
reference (list): TBD
626+
reference (set): TBD
623627
624628
Returns:
625629
bool: TBD
@@ -660,14 +664,20 @@ def check_event(events: list, reference: list) -> bool:
660664

661665
for bucket in list(buckets.keys()):
662666
pluszerocount = 0
663-
for event in buckets[bucket][:]:
667+
# Use list comprehension to filter events instead of .remove()
668+
filtered_events = []
669+
for event in buckets[bucket]:
664670
if event['_collection'] == 0:
671+
filtered_events.append(event)
665672
continue
666673
pluszerocount += 1
667674

668-
if not check_event(self.event_extract(event, rule['field']), reference):
669-
buckets[bucket].remove(event)
675+
if check_event(self.event_extract(event, rule['field']), reference):
676+
filtered_events.append(event)
677+
else:
670678
pluszerocount -= 1
679+
680+
buckets[bucket] = filtered_events
671681

672682
# delete the bucket if there are no events > collection 0
673683
if pluszerocount == 0:
@@ -923,6 +933,7 @@ def build_correlation_title(self, rule: dict, data: list) -> str:
923933
v = self.event_extract(data[0], m)[0]
924934
except Exception:
925935
self.log.error(f"Field requested was not available: {m}")
936+
continue
926937
title = title.replace("{" + m + "}", v.replace("\r", "").split("\n")[0])
927938
return title
928939

@@ -943,9 +954,7 @@ def create_correlation(self, rule: dict, data: list, readonly: bool = False) ->
943954
if readonly:
944955
return True
945956

946-
eventIds = list()
947-
for e in data:
948-
eventIds.append(e['id'])
957+
eventIds = [e['id'] for e in data]
949958

950959
corrId = self.dbh.correlationResultCreate(self.scanId,
951960
rule['id'],

0 commit comments

Comments
 (0)