Files
sigma-rules/hunting/definitions.py
T
Sergey Polzunov 1fb60d6475 fix: type hinting fixes and additional code checks (#4790)
* first pass

* Adding a dedicated code checking workflow

* Type fixes

* linting config and python version bump

* Type hints

* Drop incorrect config option

* More fixes

* Style fixes

* CI adjustments

* Pyproject fixes

* CI & pyproject fixes

* Proper version bump

* Tests formatting

* Resolve cirtular dependency

* Test fixes

* Make sure the tests are formatted correctly

* Check tweaks

* Bumping python version in CI images

* Pin marshmallow do 3.x because 4.x is not supported

* License fix

* Convert path to str

* Making myself a codeowner

* Missing kwargs param

* Adding a missing kwargs to `set_score`

* Update .github/CODEOWNERS

Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com>

* Dropping unnecessary raise

* Dropping skipped test

* Drop unnecessary var

* Drop unused commented-out func

* Disable typehinting for the whole func

* Update linting command

* Invalid type hist on the input param

* Incorrect field type

* Incorrect value used fix

* Stricter values check

* Simpler function call

* Type condition fix

* TOML formatter fix

* Simpligy output conditions

* Formatting

* Use proper types instead of aliases

* MITRE attack fixes

* Using pathlib.Path for an argument

* Use proper method to update a set from a dict

* First round of `ruff` fixes

* More fixes

* More fixes

* Hack against cyclic dependency

* Ignore `PLC0415`

* Remove unused markers

* Cleanup

* Fixing the incorrect condition

* Update .github/CODEOWNERS

Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com>

* Set explicit default values for optional fields

* Update the guidelines

* Adding None Defaults

---------

Co-authored-by: Mika Ayenson, PhD <Mikaayenson@users.noreply.github.com>
Co-authored-by: eric-forte-elastic <eric.forte@elastic.co>
2025-07-01 08:20:55 -05:00

64 lines
2.3 KiB
Python

# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License
# 2.0; you may not use this file except in compliance with the Elastic License
# 2.0.
import re
from dataclasses import dataclass, field
from pathlib import Path
# Define the hunting directory path
HUNTING_DIR = Path(__file__).parent
# URLs for MITRE and Elastic documentation
ATLAS_URL = "https://atlas.mitre.org/techniques/"
ATTACK_URL = "https://attack.mitre.org/techniques/"
# Static mapping for specific integrations
STATIC_INTEGRATION_LINK_MAP = {"aws_bedrock.invocation": "aws_bedrock"}
@dataclass
class Hunt:
"""Dataclass to represent a hunt."""
author: str
description: str
integration: list[str]
uuid: str
name: str
language: list[str]
license: str
query: list[str]
notes: list[str] | None = field(default_factory=list) # type: ignore[reportUnknownVariableType]
mitre: list[str] = field(default_factory=list) # type: ignore[reportUnknownVariableType]
references: list[str] | None = field(default_factory=list) # type: ignore[reportUnknownVariableType]
def __post_init__(self) -> None:
"""Post-initialization to determine which validation to apply."""
if not self.query:
raise ValueError(f"Hunt: {self.name} - Query field must be provided.")
# Loop through each query in the array
for q in self.query:
query_start = q.strip().lower()
# Only validate queries that start with "from" (ESQL queries)
if query_start.startswith("from"):
self.validate_esql_query(q)
def validate_esql_query(self, query: str) -> None:
"""Validation logic for ESQL."""
query = query.lower()
if self.author == "Elastic":
# Regex patterns for checking "stats by" and "| keep"
stats_by_pattern = re.compile(r"\bstats\b.*?\bby\b", re.DOTALL)
keep_pattern = re.compile(r"\| keep", re.DOTALL)
# Check if either "stats by" or "| keep" exists in the query
if not stats_by_pattern.search(query) and not keep_pattern.search(query):
raise ValueError(
f"Hunt: {self.name} contains an ES|QL query that mustcontain either 'stats by' or 'keep' functions."
)