chore(backend): mypy strict clean + ruff format pass

Pre-merge sanity per devops checklist (ruff format --check, mypy --strict).

Type fixes:
- ORM models: `Mapped[dict]` → `Mapped[dict[str, Any]]` (audit, scenario, run,
  report, ttp, detection.artifact_files_json). Equivalent on Pydantic DTOs
  (TtpBase.params_schema_json, ScenarioStepBase.params_override_json).
- Rename `TtpRead.current_version` → `TtpRead.version` to mirror the ORM
  column (which itself was renamed in D-009 cleanup).
- Flask blueprints: add `-> ResponseReturnValue` to every view, plus typed
  UUID params on `_validate_step_consistency`.
- `templating/filters.py`: rewrite the conditional re2 import so mypy can
  narrow the union (`ModuleType | None`); the runtime branch on `_re2 is not
  None` removes the unused-ignore that was triggered by warn_unused_ignores.
- `pyproject.toml`: add `flask_login.*` and `pythonjsonlogger.*` to the
  `[[tool.mypy.overrides]]` `ignore_missing_imports` list (both ship without
  typed marker).
- Misc: drop stale `# type: ignore` comments (`app.py:36`,
  `rbac/decorators.py:35`) flagged by `warn_unused_ignores`. Keep
  `logging.JsonFormatter` ignore because the symbol exists at runtime but is
  not re-exported through the typed surface.

Formatting:
- `ruff format` applied (15 files normalized; line-length unchanged at 100).

Verification on this commit:
- `ruff check`  → All checks passed.
- `ruff format --check` → 68 files already formatted.
- `mypy --strict src` → Success: no issues found in 54 source files.
- `pytest tests/unit` → 49 passed.
This commit is contained in:
knacky
2026-05-22 05:10:51 +02:00
parent 12d131c826
commit adab8a58e7
24 changed files with 203 additions and 165 deletions

View File

@@ -13,6 +13,7 @@ Revision ID: 202605210001
Revises:
Create Date: 2026-05-21
"""
from __future__ import annotations
import sqlalchemy as sa
@@ -30,9 +31,7 @@ depends_on: str | None = None
# ---------------------------------------------------------------------------
USER_TYPE = sa.Enum("rt_operator", "rt_lead", "soc_analyst", name="user_type")
ENGAGEMENT_STATUS = sa.Enum(
"draft", "active", "closed", "archived", name="engagement_status"
)
ENGAGEMENT_STATUS = sa.Enum("draft", "active", "closed", "archived", name="engagement_status")
C2_TYPE = sa.Enum("mythic", "home", name="c2_type")
HOST_STATUS = sa.Enum("unknown", "alive", "dead", name="host_status")
PAYLOAD_TYPE = sa.Enum(
@@ -64,18 +63,10 @@ RUN_STEP_STATUS = sa.Enum(
"cleanup_failed",
name="run_step_status",
)
CLEANUP_STATUS = sa.Enum(
"pending", "success", "failed", "partial", name="cleanup_status"
)
DETECTION_LEVEL = sa.Enum(
"detected", "partial", "not_detected", name="detection_level"
)
DETECTION_SOURCE = sa.Enum(
"ndr", "edr", "siem", "manual", "other", name="detection_source"
)
EVIDENCE_STATUS = sa.Enum(
"success", "failure", "partial", name="evidence_status"
)
CLEANUP_STATUS = sa.Enum("pending", "success", "failed", "partial", name="cleanup_status")
DETECTION_LEVEL = sa.Enum("detected", "partial", "not_detected", name="detection_level")
DETECTION_SOURCE = sa.Enum("ndr", "edr", "siem", "manual", "other", name="detection_source")
EVIDENCE_STATUS = sa.Enum("success", "failure", "partial", name="evidence_status")
def upgrade() -> None:
@@ -107,8 +98,12 @@ def upgrade() -> None:
sa.Column("local_password_hash", sa.String(255)),
sa.Column("disabled_at", sa.DateTime(timezone=True)),
sa.Column("last_login_at", sa.DateTime(timezone=True)),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.UniqueConstraint("email", name="uq_user_email"),
)
@@ -126,8 +121,12 @@ def upgrade() -> None:
sa.Column("id", UUID(as_uuid=True), primary_key=True),
sa.Column("name", sa.String(80), nullable=False),
sa.Column("description", sa.String(255)),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.UniqueConstraint("name", name="uq_group_name"),
)
@@ -136,13 +135,19 @@ def upgrade() -> None:
sa.Column(
"group_id",
UUID(as_uuid=True),
sa.ForeignKey("group.id", ondelete="CASCADE", name="fk_group_permission_group_id_group"),
sa.ForeignKey(
"group.id", ondelete="CASCADE", name="fk_group_permission_group_id_group"
),
nullable=False,
),
sa.Column(
"permission_id",
UUID(as_uuid=True),
sa.ForeignKey("permission.id", ondelete="CASCADE", name="fk_group_permission_permission_id_permission"),
sa.ForeignKey(
"permission.id",
ondelete="CASCADE",
name="fk_group_permission_permission_id_permission",
),
nullable=False,
),
sa.PrimaryKeyConstraint("group_id", "permission_id", name="pk_group_permission"),
@@ -158,9 +163,15 @@ def upgrade() -> None:
sa.Column("start_date", sa.Date),
sa.Column("end_date", sa.Date),
sa.Column("c2_type", C2_TYPE, nullable=False, server_default="mythic"),
sa.Column("created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
op.create_table(
@@ -217,8 +228,12 @@ def upgrade() -> None:
sa.Column("config_fernet", sa.LargeBinary, nullable=False),
sa.Column("version", sa.Integer, nullable=False, server_default="1"),
sa.Column("retired_at", sa.DateTime(timezone=True)),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
op.create_index(
"ix_c2_credential_engagement_active",
@@ -244,8 +259,12 @@ def upgrade() -> None:
sa.Column("c2_type", C2_TYPE, nullable=False),
sa.Column("status", HOST_STATUS, nullable=False, server_default="unknown"),
sa.Column("last_seen", sa.DateTime(timezone=True)),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
op.create_index("ix_host_engagement_id", "host", ["engagement_id"])
@@ -267,9 +286,15 @@ def upgrade() -> None:
sa.Column("tags", JSONB, nullable=False, server_default="[]"),
sa.Column("version", sa.Integer, nullable=False, server_default="1"),
sa.Column("is_published", sa.Boolean, nullable=False, server_default=sa.false()),
sa.Column("created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
# No `ttp_version` table — H32 / D-009: snapshot lives on run.snapshot_json.
@@ -287,9 +312,15 @@ def upgrade() -> None:
sa.Column("description", sa.Text),
sa.Column("version", sa.Integer, nullable=False, server_default="1"),
sa.Column("c2_type", C2_TYPE, nullable=False),
sa.Column("created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
op.create_table(
@@ -316,8 +347,12 @@ def upgrade() -> None:
),
sa.Column("params_override_json", JSONB, nullable=False, server_default="{}"),
sa.Column("delay_after_ms", sa.Integer, nullable=False, server_default="0"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.UniqueConstraint("scenario_id", "order_idx", name="uq_scenario_step_order_idx"),
)
@@ -334,10 +369,16 @@ def upgrade() -> None:
sa.Column("status", RUN_STATUS, nullable=False, server_default="queued"),
sa.Column("started_at", sa.DateTime(timezone=True)),
sa.Column("ended_at", sa.DateTime(timezone=True)),
sa.Column("started_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
sa.Column(
"started_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
),
sa.Column("snapshot_json", JSONB, nullable=False, server_default="{}"),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
op.create_table(
@@ -363,8 +404,12 @@ def upgrade() -> None:
sa.Column("output_blob_ref", sa.String(512)),
sa.Column("exit_code", sa.Integer),
sa.Column("resolved_payload_text", sa.Text),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
op.create_table(
@@ -382,9 +427,15 @@ def upgrade() -> None:
sa.Column("ended_at", sa.DateTime(timezone=True)),
sa.Column("resolved_command_text", sa.Text),
sa.Column("output", sa.Text),
sa.Column("executed_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"executed_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
# ----------------------------------------------------- detection / evidence
@@ -408,8 +459,12 @@ def upgrade() -> None:
sa.Column("latency_ms", sa.Integer),
sa.Column("comment", sa.Text),
sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
op.create_table(
@@ -432,8 +487,12 @@ def upgrade() -> None:
sa.Column("artifact_files_json", JSONB, nullable=False, server_default="[]"),
sa.Column("comment", sa.Text),
sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
# ----------------------------------------------------------------- report
@@ -452,9 +511,15 @@ def upgrade() -> None:
sa.Column("pdf_path", sa.String(512)),
sa.Column("md_path", sa.String(512)),
sa.Column("generated_at", sa.DateTime(timezone=True), nullable=False),
sa.Column("generated_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"generated_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
# ------------------------------------------------------------- soc_session
@@ -479,8 +544,12 @@ def upgrade() -> None:
sa.Column("last_ip", sa.String(64)),
sa.Column("last_user_agent", sa.String(512)),
sa.Column("last_used_at", sa.DateTime(timezone=True)),
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
sa.Column(
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
sa.Column(
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
),
)
# ------------------------------------------------------------ audit_log

View File

@@ -10,6 +10,7 @@ WORM enforcement without a migration; sprint 0 fills the columns at insert.
from __future__ import annotations
from datetime import datetime
from typing import Any
from uuid import UUID
from sqlalchemy import JSON, DateTime, ForeignKey, String, Text, func
@@ -26,13 +27,11 @@ class AuditLog(UuidPkMixin, Base):
nullable=False,
server_default=func.now(),
)
actor_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL")
)
actor_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
action: Mapped[str] = mapped_column(String(80), nullable=False)
resource_type: Mapped[str] = mapped_column(String(80), nullable=False)
resource_id: Mapped[str | None] = mapped_column(String(128))
metadata_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict)
metadata_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
prev_hash: Mapped[str | None] = mapped_column(String(64))
row_hash: Mapped[str] = mapped_column(String(64), nullable=False)

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from uuid import UUID
from sqlalchemy import (
@@ -45,9 +45,7 @@ class Detection(UuidPkMixin, TimestampsMixin, Base):
)
latency_ms: Mapped[int | None] = mapped_column(Integer)
comment: Mapped[str | None] = mapped_column(Text)
recorded_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
run_step: Mapped[RunStep] = relationship()
soc_user: Mapped[User] = relationship()
@@ -69,14 +67,12 @@ class Evidence(UuidPkMixin, TimestampsMixin, Base):
nullable=False,
)
artifacts_text: Mapped[str | None] = mapped_column(Text)
artifact_files_json: Mapped[list[dict]] = mapped_column(
artifact_files_json: Mapped[list[dict[str, Any]]] = mapped_column(
JSON, default=list, nullable=False
)
# Each entry: {"name": str, "ref": str, "sha256": str, "size_bytes": int}
comment: Mapped[str | None] = mapped_column(Text)
recorded_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
run_step: Mapped[RunStep] = relationship()
rt_user: Mapped[User] = relationship()

View File

@@ -37,9 +37,7 @@ class Engagement(UuidPkMixin, TimestampsMixin, Base):
nullable=False,
)
created_by_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL")
)
created_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
hosts: Mapped[list[Host]] = relationship(
back_populates="engagement",

View File

@@ -64,14 +64,10 @@ class GroupPermission(Base):
class UserGroup(Base):
__tablename__ = "user_group"
__table_args__ = (
PrimaryKeyConstraint(
"user_id", "group_id", "engagement_id", name="pk_user_group"
),
PrimaryKeyConstraint("user_id", "group_id", "engagement_id", name="pk_user_group"),
)
user_id: Mapped[UUID] = mapped_column(
ForeignKey("user.id", ondelete="CASCADE"), nullable=False
)
user_id: Mapped[UUID] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"), nullable=False)
group_id: Mapped[UUID] = mapped_column(
ForeignKey("group.id", ondelete="CASCADE"), nullable=False
)

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from uuid import UUID
from sqlalchemy import (
@@ -30,7 +30,7 @@ class Report(UuidPkMixin, TimestampsMixin, Base):
)
version: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
content_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict)
content_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
content_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
# SHA-256 of canonical JSON. Identical hash referenced in PDF footer, JSON
# export and Markdown export (spec H19 / F9 / F14).
@@ -38,11 +38,7 @@ class Report(UuidPkMixin, TimestampsMixin, Base):
pdf_path: Mapped[str | None] = mapped_column(String(512))
md_path: Mapped[str | None] = mapped_column(String(512))
generated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
generated_by_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL")
)
generated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
generated_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
engagement: Mapped[Engagement] = relationship()

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from uuid import UUID
from sqlalchemy import (
@@ -39,11 +39,9 @@ class Run(UuidPkMixin, TimestampsMixin, Base):
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
started_by_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL")
)
started_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
snapshot_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict)
snapshot_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
# Full self-contained snapshot of scenario + steps + resolved TTPs.
# Source of truth for replay (spec H32).
@@ -111,8 +109,6 @@ class RunStepCleanup(UuidPkMixin, TimestampsMixin, Base):
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
resolved_command_text: Mapped[str | None] = mapped_column(Text)
output: Mapped[str | None] = mapped_column(Text)
executed_by_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL")
)
executed_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
run_step: Mapped[RunStep] = relationship(back_populates="cleanup")

View File

@@ -6,7 +6,7 @@ run start that every referenced host matches.
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from uuid import UUID
from sqlalchemy import (
@@ -45,9 +45,7 @@ class Scenario(UuidPkMixin, TimestampsMixin, Base):
Enum(C2Type, name="c2_type", create_type=False),
nullable=False,
)
created_by_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL")
)
created_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
engagement: Mapped[Engagement] = relationship(back_populates="scenarios")
steps: Mapped[list[ScenarioStep]] = relationship(
@@ -78,7 +76,7 @@ class ScenarioStep(UuidPkMixin, TimestampsMixin, Base):
ForeignKey("host.id", ondelete="RESTRICT"),
nullable=False,
)
params_override_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False)
params_override_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
delay_after_ms: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
scenario: Mapped[Scenario] = relationship(back_populates="steps")

View File

@@ -35,9 +35,7 @@ class SocSession(UuidPkMixin, TimestampsMixin, Base):
token_hash: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
# bcrypt hash. Plain token returned once at creation.
expires_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False
)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
last_ip: Mapped[str | None] = mapped_column(String(64))

View File

@@ -8,6 +8,7 @@ counter (§8) — bumped on edit, never referenced for replay.
from __future__ import annotations
from typing import Any
from uuid import UUID
from sqlalchemy import (
@@ -39,7 +40,7 @@ class Ttp(UuidPkMixin, TimestampsMixin, Base):
nullable=False,
)
payload_template: Mapped[str] = mapped_column(Text, nullable=False, default="")
params_schema_json: Mapped[dict | None] = mapped_column(JSON)
params_schema_json: Mapped[dict[str, Any] | None] = mapped_column(JSON)
opsec_notes: Mapped[str | None] = mapped_column(Text)
cleanup_command: Mapped[str | None] = mapped_column(Text)
@@ -57,6 +58,4 @@ class Ttp(UuidPkMixin, TimestampsMixin, Base):
is_published: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# Promoted to the library (lead RT only — F11).
created_by_id: Mapped[UUID | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL")
)
created_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))