feat(risk): Phase 12 — Risk Intelligence [FASE-12]
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- TechniqueRiskProfile model: per-technique risk scoring (0-100) - 4-factor weighted scoring: detection_gap(35%) + threat_actors(30%) + osint(20%) + test_failures(15%) - Risk levels: critical(≥75) / high(≥50) / medium(≥25) / low(≥10) / info - Detailed scoring_breakdown (JSONB) + actionable recommendations per technique - Router /api/v1/risk: compute-all, compute-one, list, matrix, summary, recommendations, top - Alembic migration b038risk (raw SQL, idempotent) - QA script: 60+ tests across all endpoints Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
69
backend/app/models/risk_intelligence.py
Normal file
69
backend/app/models/risk_intelligence.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""Phase 12: Risk Intelligence model — per-technique risk scoring."""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean, Column, DateTime, Float, ForeignKey,
|
||||
Index, Integer, String, UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class TechniqueRiskProfile(Base):
|
||||
"""
|
||||
Aggregated risk profile for one technique.
|
||||
|
||||
Combines four weighted factors:
|
||||
• detection_gap (35 %) — 0=fully covered → 1=no coverage
|
||||
• threat_actor_rel (30 %) — normalised actor count
|
||||
• osint_signals (20 %) — normalised recent OSINT items (30 d)
|
||||
• test_failure_rate (15 %) — proportion of tests where blue didn't detect
|
||||
|
||||
risk_score = weighted sum × 100 → 0–100
|
||||
risk_level: critical ≥75 | high ≥50 | medium ≥25 | low ≥10 | info
|
||||
"""
|
||||
|
||||
__tablename__ = "technique_risk_profiles"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
technique_id = Column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("techniques.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# ── Computed scores ───────────────────────────────────────────────────────
|
||||
risk_score = Column(Float, nullable=False, default=0.0) # 0–100
|
||||
likelihood = Column(Float, nullable=False, default=0.0) # 0–100
|
||||
impact = Column(Float, nullable=False, default=0.0) # 0–100
|
||||
risk_level = Column(String(16), nullable=False, default="info")
|
||||
|
||||
# ── Raw factor values ─────────────────────────────────────────────────────
|
||||
detection_gap = Column(Float, nullable=False, default=1.0) # 0–1
|
||||
threat_actor_count = Column(Integer, nullable=False, default=0)
|
||||
osint_signal_count = Column(Integer, nullable=False, default=0) # last 30 d
|
||||
test_fail_count = Column(Integer, nullable=False, default=0)
|
||||
test_total_count = Column(Integer, nullable=False, default=0)
|
||||
test_failure_rate = Column(Float, nullable=False, default=0.0) # 0–1
|
||||
confidence_level = Column(Float, nullable=False, default=0.0) # DLC 0–1
|
||||
|
||||
# ── Rich detail ──────────────────────────────────────────────────────────
|
||||
scoring_breakdown = Column(JSONB, nullable=True) # per-factor contributions
|
||||
recommendations = Column(JSONB, nullable=True) # list[str]
|
||||
|
||||
# ── Meta ─────────────────────────────────────────────────────────────────
|
||||
computed_at = Column(DateTime, default=datetime.utcnow)
|
||||
is_stale = Column(Boolean, default=True)
|
||||
|
||||
technique = relationship("Technique", foreign_keys=[technique_id])
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("technique_id", name="uq_risk_profile_technique"),
|
||||
Index("ix_risk_profiles_risk_score", "risk_score"),
|
||||
Index("ix_risk_profiles_risk_level", "risk_level"),
|
||||
Index("ix_risk_profiles_stale", "is_stale"),
|
||||
)
|
||||
Reference in New Issue
Block a user