Muster Quick start

DOCSMuster

Documentation

Muster is a self-hosted web app for security teams: draw incident response playbooks as diagrams, version them, run them step by step during an incident or an exercise, and produce the incident report as a PDF.

Quick start

With Docker, nothing else to install:

git clone <your-fork-url>
cd muster
docker compose up --build

With Node.js 22.13 or later:

npm install
npm run demo

Then open http://localhost:8080 and click one of the demo accounts on the sign-in page.

Demo accounts

AccountPasswordCan
adminmuster-admineverything, plus users and the audit log
editormuster-editoredit and publish playbooks, edit report templates
analystmuster-analystdeclare incidents, run them, write reports
viewermuster-viewerread everything

Demo mode also loads a closed incident with its report (IR-2026-041) and a tabletop exercise in progress, so every screen has content.

Demo data is disposable: when a new version of Muster changes it, a demo-mode server replaces the old demo database on start. A real install (DEMO_MODE=false) is never touched. To start the demo from scratch at any time, run docker compose down -v or delete the data-demo folder.

A short tour

  1. Playbooks lists the ten ready-made playbooks. Open Ransomware: it has three published versions and an unpublished change.
  2. In the editor, select a step and press A to add an action after it, D for a decision, E for an escalation, F for an end. Drag the orange handle under a step onto another step to connect them. Drafts save automatically.
  3. History compares any two versions on the diagram and lists every change.
  4. Incidents → Declare an incident: choose Exercise and a clock speed of ×60 to see SLAs tick in minutes per second. Tick tasks, answer decisions, close the incident.
  5. Create incident report, edit the executive summary and your notes, then Download PDF.
  6. Reports → Templates: upload your logo, change the colour and the report ID pattern, rename, hide or reorder sections. The preview updates as you type.

Open the app in two browsers with two accounts: incident progress, playbook changes and new reports appear live for everyone.

Running it for real

Set DEMO_MODE=false. On first start Muster creates one admin account: set ADMIN_PASSWORD, or read the generated password in the logs. Then add your team in Admin.

# docker-compose.yml
services:
  muster:
    build: .
    ports: ["8080:8080"]
    environment:
      DEMO_MODE: "false"
      ADMIN_PASSWORD: "a-long-random-passphrase"
      COOKIE_SECURE: "true"     # served over HTTPS
      TRUST_PROXY: "true"       # behind a reverse proxy
    volumes:
      - muster-data:/data
    restart: unless-stopped
volumes:
  muster-data:

The ten default playbooks are a starting point. Review owners, SLAs and escalation contacts with your team before relying on them.

Configuration

VariableDefaultMeaning
PORT8080HTTP port
DATA_DIR./data (/data in Docker)where the SQLite database lives. Back this folder up.
DEMO_MODEfalse (true in the compose file)demo accounts and sample data on first start
ADMIN_PASSWORDrandompassword of the first admin when demo mode is off
COOKIE_SECUREfalseset to true when served over HTTPS
TRUST_PROXYfalseset to true behind a reverse proxy, to log real client IPs

Reverse proxy

Put Muster behind a reverse proxy that terminates TLS (nginx, Caddy, Traefik). Live updates use server-sent events on /api/events: disable response buffering there if your proxy buffers.

# nginx
location / {
  proxy_pass http://127.0.0.1:8080;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/events {
  proxy_pass http://127.0.0.1:8080;
  proxy_buffering off;
  proxy_read_timeout 1h;
}
# Caddy: streams SSE without extra settings
muster.example.org {
  reverse_proxy 127.0.0.1:8080
}

Backups

Everything lives in one SQLite database in DATA_DIR. Copy the folder with the container stopped, or take a hot backup:

sqlite3 /data/muster.db ".backup /backups/muster-$(date +%F).db"

Roles

Roles are cumulative: each one can do everything the one before it can. They are checked on every API endpoint, not only in the interface.

ActionViewerAnalystEditorAdmin
Read playbooks, incidents, reports, templatesYesYesYesYes
Declare and run incidents and exercises—YesYesYes
Create and edit reports—YesYesYes
Edit, publish and restore playbooks——YesYes
Edit report templates——YesYes
Archive playbooks, delete reports———Yes
Manage users, read the audit log———Yes

The last active admin can't be demoted or disabled. Changing someone's role or disabling them ends their sessions.

Playbooks

A playbook is a graph of steps. Each step has a type, an owner, an SLA in minutes, a NIST phase (Detection, Containment, Eradication, Recovery, Lessons learned), instructions and a checklist.

StepKeyDrawn asUse it for
ActionAwhite cardsomething to do, with its checklist
DecisionDblack hexagona question; each answer is a labelled connection
EscalationEred cardhanding over: crisis cell, CISO, legal
EndFstadiumclosing the incident

Editor shortcuts: Ctrl Z undo, Ctrl Shift Z or Ctrl Y redo, Ctrl S save, Delete remove the selection, Esc deselect.

Validation

Publishing is blocked while the playbook has errors: no start or more than one, no end, a step that can't be reached from the start, a dead end, a decision with a single answer. Missing owners, SLAs or answer labels are warnings.

Versions

Each playbook has one shared draft and immutable published versions with a message and an author. If two editors save the draft at the same time, the second is told and nothing is overwritten. History compares any two versions on the diagram (added, changed, removed) and lists every change field by field; Restore copies an old version into the draft. Every incident stores a frozen copy of the version it runs on, so later edits never change a past incident.

YAML format

Every playbook exports to YAML (GET /api/playbooks/:id/yaml, or the YAML button in the editor). An excerpt of PB-002:

# PB-002 Phishing email — draft (live: v1)
playbook: "Phishing email"
id: PB-002
category: "Phishing"
schema: muster/v1
steps:
  - id: analyse
    type: action
    title: "Analyse the email"
    phase: "Detection"
    owner: "L1 Analyst"
    sla_minutes: 20
    checklist:
      - "Extract headers and URLs"
      - "Detonate attachments in the sandbox"
      - "Find every other recipient"
    next:
      - to: malicious
  - id: malicious
    type: decision
    title: "Is the email malicious?"
    next:
      - to: purge
        when: "Yes"
      - to: benign
        when: "No"

Incidents

Declare an incident (real time) or an exercise (tabletop, with a faster clock: ×1, ×10, ×60 or ×300). Pick the playbook, the version and the severity. Muster then shows one step at a time:

Everything anyone does is pushed live to everyone else looking at the incident. Stop response ends it early and records that in the timeline.

Reports and templates

When an incident is closed, Create incident report drafts it from the run: executive summary, timeline, SLA performance (met or missed per step), key decisions, analyst notes and sign-off. Edit any text, then download the PDF or export Markdown. The PDF is rendered on the server from the same data and template as the preview.

Templates

Editors manage templates in Reports → Templates: organisation name, logo, accent colour (with a contrast check and a one-click fix), classification label, footer, and the sections: rename, hide, reorder. One template is the default.

Report ID pattern

TokenBecomes
{YYYY}year, e.g. 2026
{YY} {MM} {DD}two-digit year, month, day
{YYMMDD}261024
{SEQ}the template's counter, zero-padded to the width you set
{PB}the playbook code, e.g. PB-001

IR-{YYYY}-{SEQ} gives IR-2026-042; {PB}-REG-{YYMMDD}-{SEQ} gives PB-005-REG-261024-007.

HTTP API

The interface uses a plain JSON API, which you can call too. Sign in with POST /api/auth/login; the session is a cookie. Every write must carry the header X-Muster: 1 (CSRF protection).

curl -c jar -H 'X-Muster: 1' -H 'Content-Type: application/json' \
  -d '{"username":"analyst","password":"muster-analyst"}' \
  http://localhost:8080/api/auth/login
curl -b jar http://localhost:8080/api/incidents
MethodPathRoleWhat it does
GET/api/health—liveness check
POST/api/auth/login · /logout—open or close a session
GET/api/auth/me—the signed-in user, or null
POST/api/auth/passwordviewerchange your password
GET/api/eventsviewerserver-sent events stream of changes
GET/api/playbooks · /:id · /:id/yamlviewerlist, read, export
POST/api/playbookseditorcreate, or duplicate one
PATCH/api/playbooks/:ideditorname, category, severity, description
PUT/api/playbooks/:id/drafteditorsave the draft (with conflict detection)
POST/api/playbooks/:id/publish · /restoreeditorpublish a version, restore one into the draft
DELETE/api/playbooks/:idadminarchive
GET/api/incidents · /:idviewerlist, read
POST/api/incidentsanalystdeclare an incident or start an exercise
POST/api/incidents/:id/advance · /tasks · /notes · /meta · /speed · /stopanalystmove on, tick tasks, notes, name and severity, clock speed, stop
POST/api/incidents/:id/reportanalystcreate the report
GET/api/reports · /:id · /:id/pdf · /:id/markdownviewerlist, read, export
PATCH/api/reports/:idanalystedit the ID, severity, summary, notes or template; refresh from the incident
DELETE/api/reports/:idadmindelete
GET/api/templatesviewerlist templates
POST/api/templates · /:id/defaulteditorcreate, make default
PUT/api/templates/:ideditorupdate
DELETE/api/templates/:ideditordelete
GET/api/users · /api/auditadminaccounts, audit log
POST/api/usersadmincreate an account
PATCH/api/users/:idadminrole, name, disable

Architecture

packages/core   shared TypeScript: types, the 10 playbooks, validation, diffs,
                incident engine, report data
packages/pdf    report → PDF (pdf-lib), shared by the server and the online build
apps/api        Node.js HTTP API: SQLite storage, accounts and roles, live updates
apps/web        React interface
scripts/        build (esbuild) and demo launcher
tests/          API tests (node:test) and a browser walk-through (Playwright)

Development

npm run dev          # rebuilds on change; run `npm start` in another terminal
npm test             # API tests
npm run test:e2e     # browser walk-through, screenshots in tests/screens
npm run typecheck

The browser walk-through needs Chromium once: npx playwright install chromium.

This site

The site lives in site/ and is published by .github/workflows/pages.yml on every push to main. In the repository settings, set Pages → Source to GitHub Actions. The hero film is rendered from code in hero-film/ using real screens of the app.

Online version

npm run build:online produces dist-online/muster.html, a single page that runs the same interface without a server, as a Claude artifact: data lives in the page's shared storage, people sign in with their Claude account, and the share menu sets roles (Editor edits playbooks and templates, Contributor runs incidents and writes reports, Viewer reads). npm run test:online checks it in a browser against a stand-in for that runtime.

Known limits

Solid Geeks: open source the world.