OpenCSIRT
OpenCSIRT is the governance-tier CSIRT operations platform in the opensecstack ecosystem. It gives a national or sector CSIRT a single working surface for constituency tracking, incident coordination, CSAF 2.0 advisory authoring, and federated peer trust — all with every privileged action attested by CITADEL.
Version v1.0.0 (shipped 2026-05-10) is feature complete across all 11 modules. License: AGPL-3.0-or-later (governance tier).
Overview
NIS2 Article 11 requires Member States to designate CSIRTs with documented incident handling, advisory dissemination, and cross-border cooperation. OpenCSIRT is the working surface for those three jobs. It does not replace IRFlow's per-incident workflow engine and it is not a SIEM — it is the layer that turns IRFlow incidents and ThreatFlow IOCs into advisories, NIS2 Article 23 notifications, and peer-shareable records.
The four core jobs the platform handles:
- Constituency tracking — a directory of organisations served, with sector, NIS2 status, and contact-of-record.
- Incident coordination — abuse-mailbox triage, IRFlow handoffs, and peer-CSIRT escalations with CITADEL-attested timelines.
- Advisory authoring — CSAF 2.0 documents generated by the Python advisory subsystem, reviewed by
csirt_lead, and published with TLP enforcement to constituents and federated peers. - Federated trust — peer CSIRTs exchange advisories and IOC bundles over a JWT-gated handshake protocol; no shared MISP infrastructure required.
Key features
- CSAF 2.0 advisory generation — a dedicated Python subsystem renders and validates CSAF 2.0 JSON, runs IOC enrichment (VirusTotal, AlienVault OTX), and returns the sealed document to the Go API.
- TAXII 2.1 / STIX 2.1 compatible IOC ingest — ThreatFlow IOC bundles are pulled on a configurable schedule and attached to incidents.
- NIS2 Article 23 aggregate reporting — when an advisory is published for a NIS2-scoped constituency, a notification is automatically pushed to NIS2 Compass.
- Six-role RBAC —
viewer,external_peer,analyst,operator,csirt_lead,admin. Publishing advisories and escalating to peers requirescsirt_leador higher. - CITADEL-attested evidence — every incident open, close, advisory publish, and peer escalation is emitted as a signed event into the CITADEL WORM chain via a transactional outbox.
- Peer-CSIRT handshake protocol — JWT-gated federation: peers read advisories at their TLP tier and push back IOC bundles without requiring MISP.
- sinauth SSO — dashboard login uses OAuth 2.0 / OIDC (authorization code + PKCE S256) delegated to sinauth; the API validates RS256 tokens against the sinauth JWKS endpoint.
Architecture
OpenCSIRT is composed of four processes: a stateless Go API (HTTP, chi router), a Python CSAF advisory subsystem, a React + Vite operator dashboard, and PostgreSQL 16. The Go API owns all business logic, mints JWTs, enforces RBAC, and runs an in-process CITADEL outbox watcher goroutine that drains queued evidence events on a configurable tick.
The Python advisory subsystem binds only on loopback (127.0.0.1:8089) and is called exclusively by the Go API using a service JWT. It is intentionally separated so that CSAF tooling, IOC enrichment connectors (VirusTotal, OTX, URLhaus), and YARA bindings live in the Python ecosystem without being embedded in the Go binary.
Every CITADEL event is written into citadel_outbox inside the same database transaction that writes the business row, guaranteeing that outbox rows and business state are always consistent on commit. The watcher retries failed emissions until the retry budget is exhausted, at which point the row is marked failed and recorded in the audit log.
csaf-tools), IOC enrichment SDKs, YARA bindings, and ML triage classifiers — all of which ship Python-first. The boundary is a loopback HTTP call, not a service mesh.Ports & endpoints
| Service | Port | Notes |
|---|---|---|
| Go API | 8088 | OPENCSIRT_HTTP_ADDR |
| Python advisory subsystem | 8089 | OPENCSIRT_ADVISORY_SERVICE_URL; loopback only |
| React dashboard | 3088 | nginx in production, Vite dev server locally |
| PostgreSQL 16 | 5432 | Cluster-internal |
Key API endpoints (full contract in api/openapi.yaml):
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/health | Liveness — returns db, advisory_service, and uptime_seconds |
POST | /api/v1/auth/login | JWT issuance |
GET POST | /api/v1/constituencies | Constituency directory CRUD |
GET POST | /api/v1/incidents | Triage queue and incident creation |
POST | /api/v1/incidents/{id}/close | Close incident and emit CITADEL event |
GET POST | /api/v1/advisories | Draft and list CSAF 2.0 advisories |
POST | /api/v1/advisories/{id}/publish | Publish, enforce TLP, fan out to peers |
POST | /api/v1/integrations/irflow/incident | HMAC-signed IRFlow inbound webhook |
GET | /api/v1/metrics/snapshot | JSON dashboard snapshot |
Integration
OpenCSIRT integrates with four other opensecstack platforms. All cross-platform calls use HMAC-SHA256 signed envelopes with a replay window enforced by CITADEL.
| Platform | Direction | Purpose |
|---|---|---|
| ThreatFlow | Pull (60 s default) | IOC bundles attached to incidents; CSAF advisories pushed on publish |
| IRFlow | Inbound webhook | HMAC-SHA256 verified — IRFlow incidents become OpenCSIRT incidents |
| NIS2 Compass | Push on publish | Article 23 notification when a NIS2-scoped advisory is published |
| VertGuard | Subscriber | CVE advisories pulled from VertGuard, embedded into outbound CSAF |
| CITADEL | Outbox push | opencsirt.incident_opened/closed, advisory_published, escalation_sent |
Minimum required environment variables to bring up OpenCSIRT:
OPENCSIRT_DB_URL=postgres://opencsirt:opencsirt@postgres:5432/opencsirt OPENCSIRT_JWT_SECRET=<32+ random bytes> OPENCSIRT_PASSWORD_PEPPER=<32+ random bytes> OPENCSIRT_USERS=operator:operator:<sha256hex> OPENCSIRT_ADVISORY_SERVICE_URL=http://advisory:8089 OPENCSIRT_CITADEL_API_URL=https://citadel.internal OPENCSIRT_CITADEL_HMAC_SECRETS=<hmac> OPENCSIRT_THREATFLOW_API_URL=https://threatflow.internal OPENCSIRT_IRFLOW_WEBHOOK_SECRET=<hmac>
To run locally in development mode (no CITADEL or ThreatFlow required), copy .env.example as-is — it ships with OPENCSIRT_DEV_MODE=true and a dry-run CITADEL outbox:
git clone https://github.com/opensecstack/opensecstack cd opensecstack/opencsirt cp .env.example .env make dev "cm"># API: http://localhost:8088 "cm"># Dashboard: http://localhost:3088 (login: operator / operator)
Full documentation
The complete OpenCSIRT documentation — configuration reference, operator handbook, peer handshake protocol, CSAF advisory authoring guide, and integration guides — is available in the repository:
github.com/opensecstack/opensecstack/tree/main/opencsirt/docs