# Spec decisions log This file tracks implementation arbitrations *on top* of the frozen spec (`Projects/Mimic — Spec.md` in the RT-SecondBrain vault). Format: one entry per decision, newest first. --- ## 2026-05-21 — Team kickoff decisions ### D-001 — SOC collaboration hypothesis **Context.** Devils-advocate flagged the sociological assumption that SOC analysts will cote in the live cockpit. **Decision.** Hypothesis accepted as-is. No paper PoC. Risk owned by lead RT. ### D-002 — Mimic deployment location **Context.** Spec §6 NF-network did not pin where Mimic is physically deployed. **Decision.** Mimic runs on RT infrastructure. SOC client connects through the existing RT reverse proxy (Caddy, out of Mimic scope). Mimic → Mythic / Home C2 through outbound VPN. RT R&D (TTP library, stealthy variants) never sits on client premises. ### D-003 — Authentication strategy **Context.** Spec mentions OIDC Keycloak but lab onboarding cost is high. **Decision.** v1 ships **local auth** (username/password, bcrypt, Flask server-side sessions). v2 adds Keycloak OIDC. The RBAC model is **group-based from day one**, so OIDC will map claims to existing groups without touching application code. SOC sessions remain a distinct mechanism (`soc_session.token_opaque` bcrypt hash, clear token out-of-band). ### D-004 — C2 credential storage (T2) **Context.** Engagement.config_json (encrypted JSON column) vs dedicated table. **Decision.** Dedicated table `c2_credential (id, engagement_id, c2_type, config_json_fernet, version, created_at, retired_at)`. Active row per engagement = `retired_at IS NULL`, highest version. Rotation = insert + retire previous. Fernet key in env, never in DB. ### D-005 — Cleanup template variable sources (T3) **Context.** Jinja `{{outputs.X}}` source ambiguity. **Decision.** Two accessors: - `{{outputs.text}}` → `run_step.output_text` (stdout/UTF-8 text). - `{{outputs.blob("")}}` → reads from `output_blob_ref`, hard cap **10 MB** (consistent with F8 evidence limit), UTF-8 decoding with latin-1 fallback, silent refusal + log entry if the blob is non-decodable. `regex_extract` always operates on the resulting string. ### D-006 — SOC session token storage (T4) **Context.** `soc_session.token_opaque` storage form. **Decision.** bcrypt hash. Clear token generated server-side at session creation, returned **once** in the API response, delivered out-of-band to the SOC analyst. Never re-displayable. ### D-007 — Reverse proxy scope **Context.** Mimic exposure to internet for SOC client access. **Decision.** Reverse proxy (Caddy + TLS + IP allowlist) handled by existing RT infrastructure. Mimic ships an HTTP listener on localhost only; the deployment playbook wires it behind the existing proxy. ### D-008 — `ttp_version` table (overrides spec H32) **Context.** Spec H32 reads "snapshot of replayability = `run.snapshot_json` only (no separate `ttp_version` table)". The kickoff backlog (B0.2, team-lead directive) explicitly re-introduces the table. **Decision.** Both coexist: - `ttp_version` (immutable, hash-stamped) tracks every publication / edit of a TTP. Used by importers, audit log, and TTP diffs. - `run.snapshot_json` remains the source of truth for replay independence (each run carries a self-contained snapshot of the resolved TTPs). Net cost: one extra table for clear semantics — TTP lineage and run replay solve different problems. ### D-009 — Hash-chain in `audit_log` from v1 **Context.** Spec H30 places the hash chain in v2; F13 / R-O5 only mandate the write-only role for v1. **Decision.** `prev_hash` / `row_hash` columns ship from day one and are populated at insert time (SHA-256 of canonical record + previous hash). The chain *verifier* lands in v2. Cost is negligible (one SELECT + one SHA-256 per audit insert) and avoids a destructive migration later. ### D-010 — Type-hinting strategy for the ORM **Context.** Flask-SQLAlchemy 3 rejects per-base `type_annotation_map`. **Decision.** UUID primary keys use the explicit `PG_UUID(as_uuid=True)` type on `UuidPkMixin`. Foreign-key UUID columns rely on SQLAlchemy 2's built-in `Uuid` mapping via `Mapped[uuid.UUID]`. No `type_annotation_map` on the declarative base.