feat(phase-25): add detection rule associations, checklist UI and evaluation workflow (T-215, T-216)

This commit is contained in:
2026-02-09 16:44:35 +01:00
parent cd124b655b
commit f4de12d8ab
9 changed files with 970 additions and 0 deletions

View File

@@ -11,6 +11,8 @@ from app.models.data_source import DataSource
from app.models.detection_rule import DetectionRule
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping
from app.models.test_template_detection_rule import TestTemplateDetectionRule
from app.models.test_detection_result import TestDetectionResult
from app.models.enums import TechniqueStatus, TestState, TestResult, TeamSide
__all__ = [
@@ -18,5 +20,6 @@ __all__ = [
"IntelItem", "AuditLog", "Notification", "DataSource",
"DetectionRule", "ThreatActor", "ThreatActorTechnique",
"DefensiveTechnique", "DefensiveTechniqueMapping",
"TestTemplateDetectionRule", "TestDetectionResult",
"TechniqueStatus", "TestState", "TestResult", "TeamSide",
]

View File

@@ -0,0 +1,55 @@
"""TestDetectionResult — tracks which detection rules triggered during a test.
When the Blue Team evaluates a test, they mark each associated detection
rule as triggered / not triggered / not applicable, along with notes.
"""
import uuid
from datetime import datetime
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, Index
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
class TestDetectionResult(Base):
"""
Per-test, per-rule evaluation result.
- ``triggered`` = True: rule detected the attack
- ``triggered`` = False: rule did NOT detect the attack
- ``triggered`` = None: not yet evaluated
"""
__tablename__ = "test_detection_results"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
test_id = Column(
UUID(as_uuid=True),
ForeignKey("tests.id", ondelete="CASCADE"),
nullable=False,
)
detection_rule_id = Column(
UUID(as_uuid=True),
ForeignKey("detection_rules.id", ondelete="CASCADE"),
nullable=False,
)
triggered = Column(Boolean, nullable=True) # None = not evaluated
notes = Column(Text, nullable=True)
evaluated_by = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
)
evaluated_at = Column(DateTime, nullable=True)
# Relationships
test = relationship("Test")
detection_rule = relationship("DetectionRule")
evaluator = relationship("User", foreign_keys=[evaluated_by])
__table_args__ = (
Index('ix_tdr_test', 'test_id'),
Index('ix_tdr_rule', 'detection_rule_id'),
)

View File

@@ -0,0 +1,50 @@
"""TestTemplateDetectionRule — links test templates to detection rules.
Enables the Blue Team to see which detection rules should fire
for a given test template / attack procedure.
"""
import uuid
from datetime import datetime
from sqlalchemy import Column, Boolean, ForeignKey, Index, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
class TestTemplateDetectionRule(Base):
"""
Association between a test template and a detection rule.
Auto-generated by matching mitre_technique_id, or manually curated.
``is_primary`` marks rules with severity >= high as primary detections.
"""
__tablename__ = "test_template_detection_rules"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
test_template_id = Column(
UUID(as_uuid=True),
ForeignKey("test_templates.id", ondelete="CASCADE"),
nullable=True,
)
detection_rule_id = Column(
UUID(as_uuid=True),
ForeignKey("detection_rules.id", ondelete="CASCADE"),
nullable=False,
)
is_primary = Column(Boolean, default=False)
# Relationships
test_template = relationship("TestTemplate")
detection_rule = relationship("DetectionRule")
__table_args__ = (
Index('ix_ttdr_template', 'test_template_id'),
Index('ix_ttdr_rule', 'detection_rule_id'),
UniqueConstraint(
'test_template_id', 'detection_rule_id',
name='uq_template_detection_rule',
),
)