feat: sprint 3 — multi-technique simulations + MITRE matrix modal #6

Merged
knacky merged 8 commits from sprint/3-mitre-matrix into main 2026-05-27 17:11:22 +00:00
2 changed files with 19 additions and 13 deletions
Showing only changes of commit 4596f09e71 - Show all commits

View File

@@ -26,6 +26,21 @@ _TACTIC_ORDER = [
"impact", "impact",
] ]
_TACTIC_NAMES: dict[str, str] = {
"initial-access": "Initial Access",
"execution": "Execution",
"persistence": "Persistence",
"privilege-escalation": "Privilege Escalation",
"defense-evasion": "Defense Evasion",
"credential-access": "Credential Access",
"discovery": "Discovery",
"lateral-movement": "Lateral Movement",
"collection": "Collection",
"command-and-control": "Command and Control",
"exfiltration": "Exfiltration",
"impact": "Impact",
}
mitre_loaded: bool = False mitre_loaded: bool = False
_index: list[dict[str, Any]] = [] _index: list[dict[str, Any]] = []
_tactics_by_technique: dict[str, list[str]] = {} _tactics_by_technique: dict[str, list[str]] = {}
@@ -87,7 +102,7 @@ def _build_matrix(entries: list[dict[str, Any]]) -> list[dict[str, Any]]:
techs = tactic_techs.get(tactic_id, []) techs = tactic_techs.get(tactic_id, [])
# Sort techniques alphabetically. # Sort techniques alphabetically.
techs_sorted = sorted(techs, key=lambda x: x["name"]) techs_sorted = sorted(techs, key=lambda x: x["name"])
tactic_name = tactic_id.replace("-", " ").title() tactic_name = _TACTIC_NAMES.get(tactic_id, tactic_id.replace("-", " ").title())
matrix.append( matrix.append(
{ {
"tactic_id": tactic_id, "tactic_id": tactic_id,

View File

@@ -8,7 +8,7 @@ import json
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
from sqlalchemy.sql import column, table, text from sqlalchemy.sql import text
revision = "0003" revision = "0003"
@@ -16,15 +16,6 @@ down_revision = "0002"
branch_labels = None branch_labels = None
depends_on = None depends_on = None
# Lightweight table proxies for data migration (no ORM import).
_sims = table(
"simulations",
column("id", sa.Integer),
column("mitre_technique_id", sa.String),
column("mitre_technique_name", sa.String),
column("techniques", sa.Text),
)
def upgrade(): def upgrade():
bind = op.get_bind() bind = op.get_bind()
@@ -47,8 +38,8 @@ def upgrade():
) )
# 3. Make NOT NULL now that every row has a value. # 3. Make NOT NULL now that every row has a value.
# SQLite doesn't support ALTER COLUMN, so we skip the nullable constraint with op.batch_alter_table("simulations") as batch_op:
# change at DDL level — the application model enforces it. batch_op.alter_column("techniques", existing_type=sa.Text(), nullable=False)
# 4. Drop old scalar columns. # 4. Drop old scalar columns.
with op.batch_alter_table("simulations") as batch_op: with op.batch_alter_table("simulations") as batch_op: