- adapter.py: add completed_at field to C2TaskStatus dataclass - mythic.py: implement get_task() (GraphQL task query) and get_task_output() (response query + decode_response_text concat) - fake.py: deterministic state progression via per-instance call counter; get_task_output raises C2Error until completed - mapping.py: apply_task_to_simulation() idempotent output mapper (mapping_applied anchor prevents double-writes) - migration 0007: add mapping_applied BOOLEAN NOT NULL DEFAULT false to c2_task - c2_task model: mapping_applied column added - api/c2.py: GET /api/simulations/<id>/c2/tasks poll-on-read endpoint; refreshes incomplete tasks from C2, fetches output on completion, applies mapping, skips re-polling for completed tasks; best-effort (C2Error on individual task skipped, returns 200 with stale status) - 51 new tests (396 total); pytest/ruff/mypy all green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mimic
Mimic is a Breach and Attack Simulation (BAS) web UI built on the MITRE ATT&CK matrix. It replaces the flat Excel spreadsheets that red-teams and SOC analysts pass around at the end of an engagement, providing a shared workspace for Purple Team handoffs.
Status: Sprint 6 — Engagement export. Admin/redteam can now export an engagement to Markdown, CSV, or PDF in one click from
EngagementDetailPage. The export contains the engagement header and all simulations with both Red Team and SOC fields — closing the "replace the shared Excel" loop. CSV cells are defused against spreadsheet formula injection. SOC has no access to the export.
Quick start
Prerequisites: Docker (or Podman) + GNU Make. Linux/macOS host.
# 1. Configure secrets
cp .env.example .env
# Edit .env and set MIMIC_JWT_SECRET to a strong random value:
# sed -i "s|replace-me-with-a-strong-random-secret|$(openssl rand -hex 32)|" .env
# 2. Build and start the container
make build
make start
# 3. Bootstrap the first admin (run once, the container must be up)
make create-admin USER=alice PASS=changeme8
# 4. Open the UI
xdg-open http://localhost:5000 # Linux
# or visit http://localhost:5000 manually
Log in with the credentials from step 3. The admin can create additional users (redteam / soc) from /admin/users.
To stop or restart:
make stop
make restart # stop + start, preserves the SQLite volume
make logs # tail container logs
To override the host port:
make start PORT=8080
Architecture
Single-container deployment. A multistage Dockerfile builds the Vite frontend, then copies the static assets into the Flask backend image so Flask serves both the API (under /api/*) and the SPA (everything else).
┌───────────────────────────────────────────┐
│ Container: mimic:latest │
│ │
│ Flask (Python 3.12) │
│ ├── /api/* ── blueprints (auth, users, │
│ │ engagements, simulations,│
│ │ mitre) │
│ └── / ── SPA fallback → React build │
│ │
│ SQLAlchemy ── SQLite at /data/mimic.sqlite │
│ (volume: mimic-data)│
└───────────────────────────────────────────┘
- Auth: JWT Bearer tokens (HS256, 60-min TTL). Stateless — no refresh tokens, no server-side session.
- Roles:
admin(super-user — cumulates redteam rights on engagements/simulations),redteam(CRUD engagements + simulations, full field access),soc(read everything, write-only on the SOC half of simulations once the redteam marks themreview_required). - Password hashing: argon2 via
argon2-cffi. - Migrations: Alembic, applied automatically by the container entrypoint (
flask db upgrade && flask run). - MITRE ATT&CK: STIX 2.1 Enterprise bundle committed at
backend/data/mitre/enterprise-attack.jsonand indexed at app boot.make update-mitrere-fetches the latest bundle and (if the container is running) restarts it to reload the index. The endpointGET /api/mitre/techniques?q=powers the autocomplete on simulations. - Simulation workflow: Pending → In progress (auto-transition when redteam saves any non-empty field) → Review required (manual, redteam) → Done (manual, redteam or SOC). The state machine is enforced server-side; the UI surfaces the appropriate transition button per role + current state.
See SPEC.md § "Décisions techniques" for the full architecture rationale and DESIGN.md for the UI design system.
Project layout
mimic/
├── backend/ # Flask app, SQLAlchemy models, Alembic migrations, pytest suite
├── frontend/ # Vite + React + Tailwind + TanStack Query, Vitest suite
├── e2e/ # Playwright acceptance tests (one spec per user story)
├── docker/ # Dockerfile (multistage) + entrypoint.sh
├── tasks/ # Sprint plans (tasks/todo.md) and lessons (tasks/lessons.md)
├── .claude/agents/ # Sub-agent definitions for the team (read-only at runtime)
├── Makefile # all operational entry points
├── SPEC.md # functional + technical spec
├── DESIGN.md # UI design system (palette, typography, components)
└── CHANGELOG.md
Make targets
| Target | What it does |
|---|---|
make build |
Build the mimic:latest container image (multistage: Node → Python). Uses docker if installed, otherwise podman — override with make build CONTAINER_CMD=podman |
make start |
Start the container (port from PORT, default 5000; mounts mimic-data volume) |
make stop |
Stop and remove the container |
make restart |
make stop && make start — preserves the SQLite volume |
make update |
git pull && make build && make restart |
make logs |
docker logs -f mimic |
make create-admin USER=… PASS=… |
Run flask create-admin inside the container |
make update-mitre |
Fetch the latest MITRE STIX 2.1 Enterprise bundle into backend/data/mitre/; auto-restart the container if running. Commit the resulting file change manually. |
make test-backend |
pytest -q inside the container |
make test-frontend |
npm run test -- --run in frontend/ |
make test-e2e |
Playwright acceptance suite (container must be running) |
make clean |
Remove container + volume + Python/Node caches |
make open-pr TITLE="…" BODY=path |
Open a PR on the Gitea repo for the current branch via the REST API. Reads credentials from ~/.git-credentials (same source as git push) — no token in env. Wraps scripts/open-pr.sh. Defaults BASE=main. |
Development (without Docker)
Backend:
cd backend
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export MIMIC_JWT_SECRET=dev-secret
export MIMIC_DB_PATH=./mimic.sqlite
flask --app backend.app:create_app db upgrade
flask --app backend.app:create_app run --port 5000
Frontend:
cd frontend
npm install
npm run dev # http://localhost:5173 with /api proxied to :5000
Tests:
cd backend && pytest -q # 253 tests
cd frontend && npm run test -- --run # 136 tests
cd e2e && npx playwright test # 223 tests (needs container up — use MIMIC_BASE_URL=http://127.0.0.1:5000 if localhost resolves to IPv6)
Documentation
SPEC.md— functional spec, technical decisions, agent teamDESIGN.md— UI design systemCHANGELOG.md— sprint-by-sprint changestasks/todo.md— current sprint plan
License
Internal project — not yet open-sourced.