Skip to content

Add validator for tautological contains/startswith/endswith values - #617

Open
aditya226-sharma wants to merge 2 commits into
SigmaHQ:mainfrom
aditya226-sharma:validator/tautological-value
Open

aditya226-sharma wants to merge 2 commits into
SigmaHQ:mainfrom
aditya226-sharma:validator/tautological-value

Conversation

@aditya226-sharma

@aditya226-sharma aditya226-sharma commented Nov 10, 2026 •

Copy link
Copy Markdown

Summary

Adds TautologicalValueValidator (tautological_value), as suggested in #615. It reports contains, startswith and endswith values that are empty, and contains values that reduce to a single path separator (\ or /), because such values match (almost) every value of a field.

The validator is registered automatically through the existing sigma.validators.core discovery, lives next to DoubleWildcardValidator / EscapedWildcardValidator in sigma/validators/core/values.py, and is documented in docs/guides/rule_validation.rst.

from sigma.validators.core.values import TautologicalValueValidator

Why values, not only strings

SigmaString parses escape sequences, so the YAML value '\\' (two literal backslashes) becomes a single backslash. A list such as

CallTrace|contains:
  - '\Users\'
  - '\\'

therefore compiles to ... LIKE '%\Users\%' OR ... LIKE '%\%', and the second branch matches every Windows path. The whole detection item becomes a tautology even though the rule looks like it has two conditions. The validator inspects SigmaDetectionItem.original_value where available, since the modifiers add the wildcards for contains/startswith/endswith to value.

Scope

Reported:

  • empty values (|contains: '', |startswith: '', |endswith: '') — the modifier turns them into a bare *;
  • values that are exactly one \ or one / with the contains modifier — *\* matches every value containing a separator.

Not reported:

  • single separators with startswith/endswith — \* and *\ only anchor the match at one end of the value, so they stay selective and are not tautologies;
  • values with wildcards or placeholders (they are not plain strings and are covered by other validators);
  • values without one of the three modifiers;
  • detection items with the |all modifier. All values have to match there, so a single tautological value is redundant but does not make the detection item a tautology. This also avoids flagging the intentional path|contains|all rules in the current rule set;
  • correlation rules.

Severity is MEDIUM, consistent with "the meaning of the rule can be different from intended".

Impact on the SigmaHQ rule set

I ran the validator over SigmaHQ/sigma at 53b90df (3305 rules in rules/ and rules-threat-hunting/, all parsed successfully). It reports 2 detection items:

Rule Detection item Note
rules/windows/process_creation/proc_creation_win_susp_image_missing.yml Image|contains: '\\' intentional: the filter is meant to match absolute paths. A validation-config exclusion may be appropriate if this stays as is
rules-threat-hunting/windows/process_creation/proc_creation_win_powershell_susp_child_processes.yml CommandLine|contains: ['qfe list', ..., ''] the empty value makes the wmic filter match every CommandLine, so the other values are dead (tracked in SigmaHQ/sigma#6596)

The earlier version of this validator also flagged two FileName|startswith: '\\' rules from the same corpus; that check is now limited to contains.

For comparison, the existing validators report 56 EscapedWildcardIssue, 8 NumberAsStringIssue, 4 SpecificInsteadOfGenericLogsourceIssue and 1 InvalidATTACKTagIssue on the same corpus, so this is in line with the current signal level.

I have not touched the rule repository; the table is data for the maintainers to decide whether the flagged rules should be fixed or excluded.

Tests

tests/test_validators.py covers the reported repro, empty values for all three modifiers, a lone separator with contains, and the non-flagged cases (path values, UNC paths, wildcards, unrelated modifiers, |all, correlation rules, and lone separators with startswith/endswith).

Local checks:

  • pytest: 1964 passed, 1 skipped
  • black --check: clean
  • mypy -p sigma: clean

Disclosure

I used an AI assistant to help analyze the issue, write the implementation and tests, and measure the impact on the rule set. The code, test results and rule-set numbers above were verified locally against main at e522188.

Values that are empty or reduce to a single path separator match (almost) every value. This is easy to miss when escape sequences collapse, e.g. a value written as '\\' is parsed as one literal backslash, so a contains list with '\Users\' and '\\' matches every path.

Detection items with the all modifier are skipped because all values have to match and a single tautological value does not turn the whole item into a tautology.
@malapeiro

Copy link
Copy Markdown

Thanks for picking this up so quickly, and for running it over the rule set. I checked it against the original case from #615: with this branch the validator flags the CallTrace|contains: ['\Users\', '\\'] rule, and the new tests pass locally (15/15).

One note on my own suggestion, which your numbers seem to support: for startswith/endswith, a lone \ is much narrower than for contains (e.g. startswith: '\' only matches values beginning with a backslash, not C:\...). Two of the four hits are of that kind, so it may be worth limiting the single-separator check to contains, or lowering the severity for the other two. Happy to defer to the maintainers on that.

A lone `\` or `/` with `contains` matches any value containing that
separator, which is effectively every value for path-like fields. With
`startswith` and `endswith` the same value only anchors the match at one
end of the value, so it remains selective and is not a tautology.

Only report a single separator when the `contains` modifier is present.
Empty values are still reported for all three modifiers, where the
modifier adds a bare `*` and the value matches everything.

Update the issue description, validator docstring and rule-validation
docs to match. The tests now parametrize the single-separator cases over
`contains` only, cover empty values for all three modifiers, and list
`startswith`/`endswith` lone separators as explicit non-issues.
@aditya226-sharma

Copy link
Copy Markdown
Author

Thanks — agreed, and the corpus numbers back it up. I've limited the single-separator check to contains and kept empty-value detection for all three modifiers (commit 4a1186d).

The distinction is in the compiled pattern: contains: '\' becomes *\* and matches any value containing a separator, while startswith: '\' and endswith: '\' become \* / *\ and only anchor the match at one end, so they stay selective. An empty value, on the other hand, becomes a bare * with every one of the three modifiers, so it remains a real tautology.

Changes:

  • sigma/validators/core/values.py: single-separator check gated on contains; issue description and docstring updated.
  • tests/test_validators.py: single-separator cases parametrized over contains only; empty values covered for contains/startswith/endswith; lone \ and / with startswith/endswith added as explicit non-issues.
  • docs/guides/rule_validation.rst: description updated.

Severity stays MEDIUM, since both remaining cases are genuine tautologies. A LOW-severity report for startswith/endswith would still be noise as those values are not tautological; happy to add it as a separate validator if you'd prefer.

Re-ran over SigmaHQ/sigma at 53b90df: the validator now reports 2 items instead of 4. The two FileName|startswith: '\' rules no longer flag, while Image|contains: '\' and the empty-value contains list still do.

Local checks: pytest 1964 passed, 1 skipped; black --check clean; mypy -p sigma clean.

@malapeiro

Copy link
Copy Markdown

Thanks, this looks right to me. I agree a separate LOW validator for startswith/endswith isn't needed; those values are selective, so it would just be noise. Re-checked the updated branch: the #615 case is still flagged and the tautological tests pass (18/18).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants