Threat Intel Arbiter — Architecture

Org-context-aware CVE prioritization · single Go binary · SQLite · MISP + CISA KEV → SSVC v2.1 triage → EDR response
Click any block for details
Ingest
Normalize
Correlate & score
Persist
Respond
Present
data flow
1Ingestpull-all, filter-local — sources normalize into one type

MISP feed

internal/source/misp.go · poller.go
Polls restSearch on an incremental timestamp cursor, paginated. Normalizes attributes into CVEs, IOCs, tags, threat-actor galaxies, and TLP.
incremental cursorpaginatedTLP-aware
details →

CISA KEV

internal/source/kev.go
Known Exploited Vulnerabilities catalog. Flags CVEs as actively exploited in the wild — the strongest single likelihood signal in scoring.
exploitation signalCVE→KEV
details →
2Normalizethe canonical type every component operates on
model.ThreatEvent

Canonical Threat Event

Source-specific detail is erased here. Adding a new source means writing one normalizer — every downstream stage stays unchanged.
Source · Confidence Title · Description CVEs[] IOCs[] · ipv4/ipv6/domain/sha256/md5 Tags[] ThreatActors[] References[] Published
click for details →
3Bufferdecouple ingestion from processing

Event queue

cmd/arbiter/main.go — in-memory channel
A Go channel buffers normalized events and hands them to a single consumer goroutine, so a slow scoring pass never blocks the pollers.
chan ThreatEventsingle consumercursor re-pull on restart
details →
4Correlate & scorematch to the org, then rank by what it means here

Org context (CMDB)

tech stack · CSV import
Apps with version, criticality, internet-facing, hosts, data sensitivity. The load-bearing input — match quality is capped by CMDB hygiene.

Filter

internal/filter
TLP gate (drops tlp:red) and relevance pre-checks before expensive matching.

Match engine

internal/match · match/version
CVE ↔ tech stack with version comparison and a conservative product-only fallback. Pluggable Matcher.

Risk engine

internal/risk/engine.go
Scores each match across four dimensions as a tiebreaker, then runs the SSVC v2.1 decision tree (Exploitation → Exposure → Automatable → Mission Impact) to produce the canonical action (Act/Attend/Track*/Track) — the verdict an analyst acts on.
Four dimensions → one verdict
Likelihood ≤5
KEV · exploited · actor · freshness
Impact ≤5
CVSS · asset criticality · data sensitivity
Exposure ≤3
internet-facing · identity · reachability
Confidence ≤4
source trust · TLP (parallel label)
risk=(L × I × E)/75 severitySSVC action
ActAttendTrack*Track
details →

Alert

internal/risk/alert.go
Wraps the scored match with a dedup identity and a status lifecycle (new → acked → false_pos / resolved), persisted with a rolling TTL.
dedup hashstatus lifecycleTTL 7d
details →
5Persistone embedded file, zero external services

SQLite store

internal/store — modernc pure-Go driver (no cgo)
Single embedded database for the whole system. WAL mode, single writer.
eventsalertstechstackuserssessionscursor
details →
6Respond & presentroute to humans and controls; serve the triage console
Respond

Notification router

internal/notify/router.go
Rules match on severity + confidence, then fan out to the right channels.
Chat webhooksslack · teams
Emailemail.go
Consolestdout
CrowdStrike EDRGUARDRAILS
Present

API + Triage console

internal/api — HTTP server + SPA dashboard
REST endpoints (/api/alerts, /api/techstack, /api/status) and the single-file dashboard where analysts triage by SSVC action, manage the tech stack, and configure routing.
/api/alertsaction-led queuekeyboard triage
reads alerts & tech stack from SQLite ↑
×Cross-cuttingspans every layer

Authentication

Argon2id passwords · hashed sessions · login rate-limit · X-Arbiter-Key · dashboard escaping

Configuration

Env vars + YAML risk weights. Thresholds and routing rules are tunable without a rebuild.

Runtime

One static binary. No external services beyond the SQLite file — deploy by copying and setting a few env vars.

Health & status

Public /health liveness; authenticated /api/status exposes source connectivity and counts.