feat(ttp): E.3.8 R0041-R0048 email cohort
8 YAMLs for the email cohort per Appendix B: open-relay abuse, mass phishing, phishing-kit X-Mailer signatures, IDN/punycode URLs, sender masquerade, malicious attachment, BEC, encoded payload in body. EmailLifter (E.3.12) consumes by rule_id. test_email_rules.py: YAML-present + inert-in-v0 + xfail(strict) precision case gated on E.3.12.
This commit is contained in:
54
tests/ttp/rule_precision/test_email_rules.py
Normal file
54
tests/ttp/rule_precision/test_email_rules.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""R0041-R0048 — email cohort.
|
||||
|
||||
EmailLifter (E.3.12) consumes these by rule_id. The v0
|
||||
:class:`RuleEngine` cannot parse SMTP envelopes, walk attachment
|
||||
trees, or compose header / body / attachment signals — so these
|
||||
rules are inert under the regex matcher.
|
||||
|
||||
Asserts each YAML compiles, none fire from the v0 engine, and a
|
||||
strict-xfail precision case that flips green when E.3.12 lands.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from decnet.ttp.impl.rule_engine import RuleEngine
|
||||
from decnet.ttp.store.base import RuleState
|
||||
from decnet.ttp.store.impl.filesystem import _parse_and_compile
|
||||
from tests.ttp.rule_precision.conftest import CorpusRow, make_event
|
||||
|
||||
CohortLoader = Callable[[str], list[CorpusRow]]
|
||||
|
||||
_RULE_IDS = [f"R{n:04d}" for n in range(41, 49)]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rule_id", _RULE_IDS)
|
||||
def test_rule_yaml_present(rule_id: str) -> None:
|
||||
path = Path("rules/ttp") / f"{rule_id}.yaml"
|
||||
assert path.exists(), f"missing YAML: {path}"
|
||||
compiled = _parse_and_compile(path, RuleState())
|
||||
assert compiled.rule_id == rule_id
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rule_id", _RULE_IDS)
|
||||
async def test_lifter_bound_inert_in_v0(
|
||||
rule_id: str,
|
||||
precision_engine: RuleEngine,
|
||||
corpus_loader: CohortLoader,
|
||||
) -> None:
|
||||
fired: set[str] = set()
|
||||
for row in corpus_loader("email"):
|
||||
tags = await precision_engine.evaluate(make_event(row))
|
||||
fired.update(tag.rule_id for tag in tags)
|
||||
assert rule_id not in fired, (
|
||||
f"{rule_id} is lifter-bound but fired from the regex engine"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rule_id", _RULE_IDS)
|
||||
@pytest.mark.xfail(strict=True, reason="impl phase E.3.12 (EmailLifter)")
|
||||
def test_email_rule_precision(rule_id: str) -> None:
|
||||
pytest.fail(f"{rule_id}: EmailLifter not yet shipped (E.3.12)")
|
||||
Reference in New Issue
Block a user