Skip to content
Closed
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# CHANGELOG

## 2026-08-14 - v0.21.1

### New Validators
- `sigmahq_trademark` — Titles must not contain trademarked terms
- `sigmahq_source_eventlog` — EventLog products must specify source/eventlog
- `sigmahq_license` — metadata.license must be a known SPDX identifier
- `sigmahq_event_id_process_creation` — EventID 1/4688 requires process_creation category
- `sigmahq_selection_single_value` — Single-item lists must be on one line
- `sigmahq_selection_alphabetical_order` — Multi-item lists must be sorted alphabetically

### Docs
- README updated with complete validator list (55 validators)

### Maintenance
- Bump check-jsonschema from 0.29.4 to 0.30.0 (CVE-2024-53848 fix)

## 2024-08-09

- Update taxonomy
Expand Down
59 changes: 57 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,63 @@ Create all validators specific to the requirements of the SigmaHQ rules reposito

## 🏗️ Validators

TBD

| Validator | Description |
|---|---|
| sigmahq_author_existence | Checks if a rule is missing the author field. |
| sigmahq_category_event_id | Checks if a rule uses an EventID field with a windows category logsource that doesn't require it. |
| sigmahq_category_windows_provider_name | Checks if a rule uses a Provider_Name field with a windows category logsource that doesn't require it. |
| sigmahq_correlation_filename_prefix | Check that correlation rule filenames start with 'correlation_'. |
| sigmahq_correlation_group_by_existence | Checks if a correlation rule has a group-by field for types that require it. |
| sigmahq_correlation_rules_minimum | Checks if temporal correlation rules have at least 2 rules. |
| sigmahq_date_existence | Checks if a rule is missing the date field. |
| sigmahq_description_existence | Checks if a rule is missing the description field |
| sigmahq_description_length | Checks if a rule has an overly brief description. |
| sigmahq_event_id_process_creation | Checks if a rule uses EventID 1 or 4688 with Provider_Name instead of process_creation category. |
| sigmahq_falsepositives_banned_word | Checks if a rule contains a falsepositive entry that is part of the banned word list. |
| sigmahq_falsepositives_capital | Checks if a rule falsepositive entry starts with a capital letter. |
| sigmahq_falsepositives_typo_word | Checks if a rule falsepositive entry contains a common typo. |
| sigmahq_field_duplicate_value | Check unique values in field lists. |
| sigmahq_field_user | Check a User field use a localized name. |
| sigmahq_fieldname_cast | Check field name have a cast error. |
| sigmahq_fields_existence | Checks if a rule is using the deprecated field fields. |
| sigmahq_filename_convention | Check a rule filename against SigmaHQ filename convention. |
| sigmahq_filename_prefix | Check a rule filename against SigmaHQ filename prefix convention. |
| sigmahq_github_link | Checks if a rule has a branch GitHub link |
| sigmahq_invalid_all_modifier | Check All modifier used with a single value. |
| sigmahq_invalid_fieldname | Check field name do not exist in the logsource. |
| sigmahq_invalid_hash_kv | Check field Sysmon Hash Key-Value search is valid. |
| sigmahq_level_existence | Checks if a rule is missing the level field |
| sigmahq_license | Checks if a rule has a malformed 'license' field. |
| sigmahq_link_in_description | Checks if a rule has a description field that contains a reference to a hyperlink. |
| sigmahq_logsource_unknown | Checks if a rule uses an unknown logsource. This validator verifies that all logsource keys (product_category_service) are registered in the data taxonomy. If not, it raises a HIGH severity validation issue. |
| sigmahq_missing_asterisk_condition | Check the use of the '1/all of ' keyword without an asterisk in the condition |
| sigmahq_mitre_link | Checks if a rule uses a MITRE link instead of tag |
| sigmahq_modified_date_order | Checks if a rule has a modified field that has value older than the date field. |
| sigmahq_modified_without_date | Checks if a rule has a modified field without a date field. |
| sigmahq_ofselection_condition | Check use of the 'All/X of ' format with only one selection in the detection section |
| sigmahq_ofthem_condition | Check use of the ' of them' keyword with only a single selection in the detection section |
| sigmahq_redundant_field | Check if a field name is already covered by the logsource. |
| sigmahq_redundant_modified | Checks if a rule has a redundant modified field |
| sigmahq_selection_alphabetical_order | Checks if any multi-item selection list is sorted alphabetically. |
| sigmahq_selection_single_value | Checks if any selection has a list with only 1 element. |
| sigmahq_source_eventlog | Checks if a detection contains 'Source: Eventlog' which is redundant. |
| sigmahq_space_field_name | Check if rules uses a field name that contains a space instead of an underscore. |
| sigmahq_status | Checks if a rule uses a status field with the value Deprecated or Unsupported, and its not located in the appropriate folder. |
| sigmahq_status_existence | Checks if a rule is missing the status field. |
| sigmahq_status_to_high | Checks if a new rule has a valid status regarding its age |
| sigmahq_sysmon_missing_eventid | Checks if a rule using Sysmon logsource is missing the EventID field. This validator ensures that all rules using Windows Sysmon logsource have at least one detection item with the EventID field, which is required for proper event filtering. |
| sigmahq_tags_detection | Checks if a rule in a specific folder has the corresponding detection tag. |
| sigmahq_tags_techniques_without_tactics | Ensures that MITRE ATT&CK technique tags have their corresponding tactic tags. |
| sigmahq_tags_tlp | Checks if a rule uses a non-authorized TLP tag. |
| sigmahq_tags_unique_detection | Ensures that the tag.namespace 'detection' is unique in the tags. |
| sigmahq_tags_unique_tlp | Ensures that the tag.namespace 'tlp' is unique in the tags. |
| sigmahq_title_case | Checks if a rule has a title with invalid casing. |
| sigmahq_title_end | Checks if a rule has a title that ends with a dot(.). |
| sigmahq_title_length | Checks if a rule has an excessively long title. |
| sigmahq_title_start | Checks if a rule title starts with the word 'Detect' or 'Detects'. |
| sigmahq_trademark | Checks if a rule title contains trademarked terms that should not be used. |
| sigmahq_unknown_field | Checks if a rule uses an unknown field. |
| sigmahq_unsupported_regex_group_construct | Checks if a rule uses an unsupported regular expression group constructs. |
## 🧬 Data

All the data value are in the json files in the tools directory
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "pySigma-validators-sigmahq"
version = "0.21.0"
version = "0.21.1"
description = "pySigma SigmaHQ validators"
license = "LGPL-2.1-only"
readme = "README.md"
Expand Down
55 changes: 55 additions & 0 deletions sigma/validators/sigmahq/eventid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from dataclasses import dataclass
from typing import ClassVar, List

from sigma.correlations import SigmaCorrelationRule
from sigma.rule import SigmaDetection, SigmaDetectionItem, SigmaRule
from sigma.validators.base import (
SigmaRuleValidator,
SigmaValidationIssue,
SigmaValidationIssueSeverity,
)


PROCESS_CREATION_PROVIDERS = (
"Microsoft-Windows-Security-Auditing",
"Microsoft-Windows-Sysmon",
)
MIGRATION_EVENT_IDS = (1, 4688)


@dataclass
class SigmahqEventIdProcessCreationIssue(SigmaValidationIssue):
description: ClassVar[str] = (
"Rule uses EventID 1 or 4688 with Provider_Name. "
"Please migrate to the process_creation category."
)
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.MEDIUM


def _iter_detection_items(detection) -> List[SigmaDetectionItem]:
"""Recursively yield all SigmaDetectionItem objects from a detection tree."""
items = []
for item in detection.detection_items:
if isinstance(item, SigmaDetectionItem):
items.append(item)
elif isinstance(item, SigmaDetection):
items.extend(_iter_detection_items(item))
return items


class SigmahqEventIdProcessCreationValidator(SigmaRuleValidator):
"""Checks if a rule uses EventID 1 or 4688 with Provider_Name instead of process_creation category."""

def validate(self, rule: SigmaRule | SigmaCorrelationRule) -> List[SigmaValidationIssue]:
if isinstance(rule, SigmaCorrelationRule):
return []

for detection in rule.detection.detections.values():
for item in _iter_detection_items(detection):
if item.field is not None and item.field.lower() == "provider_name":
for value in item.value:
value_str = str(value)
for provider in PROCESS_CREATION_PROVIDERS:
if provider in value_str:
return [SigmahqEventIdProcessCreationIssue([rule])]
return []
28 changes: 28 additions & 0 deletions sigma/validators/sigmahq/metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from dataclasses import dataclass
from typing import ClassVar, List

from sigma.correlations import SigmaCorrelationRule
from sigma.rule import SigmaRule
from sigma.validators.base import (
SigmaRuleValidator,
SigmaValidationIssue,
SigmaValidationIssueSeverity,
)


@dataclass
class SigmahqLicenseIssue(SigmaValidationIssue):
description: ClassVar[str] = "Rule has a malformed 'license' field (has to be a string)."
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.MEDIUM


class SigmahqLicenseValidator(SigmaRuleValidator):
"""Checks if a rule has a malformed 'license' field."""

def validate(self, rule: SigmaRule | SigmaCorrelationRule) -> List[SigmaValidationIssue]:
if not isinstance(rule, SigmaRule):
return []

if rule.license is not None and not isinstance(rule.license, str):
return [SigmahqLicenseIssue([rule])]
return []
44 changes: 44 additions & 0 deletions sigma/validators/sigmahq/selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from dataclasses import dataclass
from typing import ClassVar, List

from sigma.correlations import SigmaCorrelationRule
from sigma.rule import SigmaDetection, SigmaDetectionItem, SigmaRule
from sigma.validators.base import (
SigmaRuleValidator,
SigmaValidationIssue,
SigmaValidationIssueSeverity,
)


@dataclass
class SigmahqSelectionSingleValueIssue(SigmaValidationIssue):
description: ClassVar[str] = "Selection has a list with only 1 element."
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.MEDIUM
selection: str
field: str


def _iter_detection_items(detection) -> List[SigmaDetectionItem]:
"""Recursively yield all SigmaDetectionItem objects from a detection tree."""
items = []
for item in detection.detection_items:
if isinstance(item, SigmaDetectionItem):
items.append(item)
elif isinstance(item, SigmaDetection):
items.extend(_iter_detection_items(item))
return items


class SigmahqSelectionSingleValueValidator(SigmaRuleValidator):
"""Checks if any selection has a list with only 1 element."""

def validate(self, rule: SigmaRule | SigmaCorrelationRule) -> List[SigmaValidationIssue]:
if isinstance(rule, SigmaCorrelationRule):
return []

issues: List[SigmaValidationIssue] = []
for sel_name, detection in rule.detection.detections.items():
for item in _iter_detection_items(detection):
if item.field is not None and len(item.value) == 1:
issues.append(SigmahqSelectionSingleValueIssue([rule], sel_name, item.field))
return issues
41 changes: 41 additions & 0 deletions sigma/validators/sigmahq/selection_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from dataclasses import dataclass
from typing import ClassVar, List

from sigma.correlations import SigmaCorrelationRule
from sigma.rule import SigmaRule
from sigma.validators.base import (
SigmaRuleValidator,
SigmaValidationIssue,
SigmaValidationIssueSeverity,
)


@dataclass
class SigmahqSelectionAlphabeticalOrderIssue(SigmaValidationIssue):
description: ClassVar[str] = "Selection list is not in alphabetical order."
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.LOW
field: str
selection: str
values: List[str]


class SigmahqSelectionAlphabeticalOrderValidator(SigmaRuleValidator):
"""Checks if any multi-item selection list is sorted alphabetically."""

def validate(self, rule: SigmaRule | SigmaCorrelationRule) -> List[SigmaValidationIssue]:
if isinstance(rule, SigmaCorrelationRule):
return []

issues: List[SigmaValidationIssue] = []
for sel_name, detection in rule.detection.detections.items():
for item in detection.detection_items:
if hasattr(item, "field") and item.field and hasattr(item, "value"):
if isinstance(item.value, list) and len(item.value) >= 2:
values = [str(v) for v in item.value]
if values != sorted(values):
issues.append(
SigmahqSelectionAlphabeticalOrderIssue(
[rule], sel_name, item.field, values
)
)
return issues
43 changes: 43 additions & 0 deletions sigma/validators/sigmahq/source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from dataclasses import dataclass
from typing import ClassVar, List

from sigma.correlations import SigmaCorrelationRule
from sigma.rule import SigmaDetection, SigmaDetectionItem, SigmaRule
from sigma.validators.base import (
SigmaRuleValidator,
SigmaValidationIssue,
SigmaValidationIssueSeverity,
)


@dataclass
class SigmahqSourceEventlogIssue(SigmaValidationIssue):
description: ClassVar[str] = "Detection contains 'Source: Eventlog' which does not add value."
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.MEDIUM


def _iter_detection_items(detection) -> List[SigmaDetectionItem]:
"""Recursively yield all SigmaDetectionItem objects from a detection tree."""
items = []
for item in detection.detection_items:
if isinstance(item, SigmaDetectionItem):
items.append(item)
elif isinstance(item, SigmaDetection):
items.extend(_iter_detection_items(item))
return items


class SigmahqSourceEventlogValidator(SigmaRuleValidator):
"""Checks if a detection contains 'Source: Eventlog' which is redundant."""

def validate(self, rule: SigmaRule | SigmaCorrelationRule) -> List[SigmaValidationIssue]:
if isinstance(rule, SigmaCorrelationRule):
return []

for detection in rule.detection.detections.values():
for item in _iter_detection_items(detection):
if item.field is not None and item.field.lower() == "source":
for value in item.value:
if str(value).lower() == "eventlog":
return [SigmahqSourceEventlogIssue([rule])]
return []
34 changes: 34 additions & 0 deletions sigma/validators/sigmahq/trademark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from dataclasses import dataclass
from typing import ClassVar, List

from sigma.correlations import SigmaCorrelationRule
from sigma.rule import SigmaRule
from sigma.validators.base import (
SigmaRuleValidator,
SigmaValidationIssue,
SigmaValidationIssueSeverity,
)


TRADEMARKS = ("MITRE ATT&CK", "ATT&CK")


@dataclass
class SigmahqTrademarkIssue(SigmaValidationIssue):
description: ClassVar[str] = "Rule title contains a trademark violation."
severity: ClassVar[SigmaValidationIssueSeverity] = SigmaValidationIssueSeverity.MEDIUM
trademark: str


class SigmahqTrademarkValidator(SigmaRuleValidator):
"""Checks if a rule title contains trademarked terms that should not be used."""

def validate(self, rule: SigmaRule | SigmaCorrelationRule) -> List[SigmaValidationIssue]:
if not isinstance(rule, SigmaRule):
return []

issues: List[SigmaValidationIssue] = []
for trademark in TRADEMARKS:
if trademark in rule.title:
issues.append(SigmahqTrademarkIssue([rule], trademark))
return issues
Loading