Catalyst / admin/SAynapse-Horizon 14.8 GB / 57.8 GB 40.0 GB free
Help Sign in

admin / SAynapse-Horizon

public

Self Hosted Cyber Threat Intelligence Hub

Code Issues Pull requests Pipelines Packages Security Insights Wiki Settings
SAynapse-Horizon / Synapse-Horizon-v2 / README.md 7182 B · main
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# Synapse-Horizon v2

A modern, dark-mode-first **Cyber Threat Intelligence (CTI) dashboard** for Tier-3 SOC
analysts, threat hunters, and security architects. Synapse-Horizon ingests **live** telemetry
from multiple CTI sources with divergent schemas, **normalizes** each indicator into a single
scannable headline, and surfaces the most critical events on an executive triage radar.

Built with **React 18 + Vite + Tailwind CSS + Lucide** on the frontend and a **Node/Express
ingestion backend**, packaged for a **Docker Compose** environment.

## What's new in v2

- **Animated radar brandmark** — the static header logo is replaced by a live SVG radar
  (rotating sweep + pulsing blue contacts). Pure CSS/SVG, and it respects
  `prefers-reduced-motion` (the sweep freezes for users who opt out of motion).
- **Critical-triage alarm** — a flashing red **"Critical · Triage Required"** pill sits next to
  the Pipeline indicator whenever any Critical Priority Triage event is unacknowledged. Each
  triage row has an **ACK** button (plus an **Acknowledge all**); the alarm stops only once every
  item is acknowledged, and re-arms automatically when a fresh pull brings new critical intel.
- Runs on offset host ports (frontend `8081`, backend `4001`, dev `5191`) so it can run
  alongside v1.

## Live data architecture

Browsers cannot call these CTI APIs directly (CORS blocks them, and several require secret
API keys). So the stack runs two services:

```
┌────────────────────┐     /api      ┌────────────────────────┐     HTTPS      ┌─────────────┐
│  horizon (nginx)   │ ───proxy───▶  │  api (Node ingestion)  │ ───fetch───▶   │ Live CTI    │
│  React SPA :8081   │               │  normalize + cache     │                │ APIs        │
└────────────────────┘  ◀── JSON ──  └────────────────────────┘                └─────────────┘
```

The backend holds the API keys (never the browser), fetches each source on demand, normalizes
every record into one uniform IOC schema, and exposes `GET /api/feeds` + `POST /api/sync`.

---

## Feature overview

| Area | What it does |
|------|--------------|
| **Global key metrics** | Active feeds (`7 / 12`-style), latest feed-pull timestamp (`DD-MMM-YYYY HH:mm:ss UTC`), and total aggregated IOCs held in state. |
| **Feed Control Panel** (left) | Per-source toggles for URLhaus, ThreatFox, Feodo Tracker, PhishTank, CISA KEV, Blocklist.de, AlienVault OTX, and an RSS parser — toggling instantly filters metrics, radar and feed. |
| **Lifecycle controls** | Independent **Auto-Refresh Interval** (display polling: Manual/30s/1m/5m/15m) and **Feed Pull Cadence** (ingestion: On-Demand/1h/6h/24h), plus a prominent **Manual Force Sync**. |
| **Critical Priority Triage** | Max 5 highest-severity events, auto-resorting when a lifecycle completes. |
| **Normalized CTI Feed** | Full-width uniform rows: UTC time · source tag · normalized headline · **View Details**. |
| **Drill-down drawer** | STIX/TAXII attributes, MITRE ATT&CK mappings, raw JSON payload, and a confidence meter. |
| **Loading states** | Skeletons + spinners during force sync / cadence changes. |

## SOC alerting palette

Restrained by design: **Emerald** = stable/active, **Amber** = suspicious/high,
**Rose/Red** = critical / active exploitation, **Sky/Slate** = informational structure.

---

## Live sources & API keys

| Source | Key required | Notes |
|--------|:---:|-------|
| **CISA KEV** | — | Public JSON. Live out of the box. |
| **Blocklist.de** | — | Public IP lists. Live out of the box. |
| **RSS Parser** | — | Public advisory feeds. Live out of the box. |
| **URLhaus** | `ABUSE_CH_AUTH_KEY` | Free [abuse.ch](https://auth.abuse.ch/) account. |
| **ThreatFox** | `ABUSE_CH_AUTH_KEY` | Same abuse.ch key. |
| **Feodo Tracker** | `ABUSE_CH_AUTH_KEY` | Same abuse.ch key. |
| **AlienVault OTX** | `OTX_API_KEY` | From your OTX account. |
| **PhishTank** | `PHISHTANK_APP_KEY` | Registered application key. |

Copy `.env.example``.env` and fill in the keys you have. Sources without a key report as
**"needs API key"** in the sidebar instead of failing. Set `HORIZON_DEMO=1` to serve synthetic
data for any un-keyed source so the dashboard is populated out of the box.

## Running it

### Production (nginx SPA + ingestion backend)

```bash
cp .env.example .env      # add any API keys you have (optional)
docker compose up -d --build
# open http://localhost:8081   (API on http://localhost:4001/api/feeds)
```

### Hot-reloading dev stack

```bash
docker compose --profile dev up
# frontend http://localhost:5191  ·  backend http://localhost:4001
```

### Local (no Docker)

```bash
# terminal 1 — ingestion backend
cd backend && PORT=4001 npm start             # http://localhost:4001

# terminal 2 — frontend (Vite proxies /api → :4001)
npm install && npm run dev                     # http://localhost:5191
npm run build                                  # emits ./dist
```

---

## Architecture

```
backend/                         # Node/Express ingestion service
├── src/server.js                # routes, in-memory cache, background warm-up sync
├── src/normalize.js             # IOC contract + fetch/timeout helpers + severity model
└── src/sources/
    ├── index.js                 # registry + concurrent, failure-isolated orchestration
    ├── cisakev.js  blocklistde.js  rss.js        # no-key live sources
    ├── urlhaus.js  threatfox.js  feodo.js         # abuse.ch (Auth-Key)
    ├── otx.js  phishtank.js                       # OTX / PhishTank (keys)
    └── demo.js                  # synthetic fallback (HORIZON_DEMO=1)

src/                             # React frontend
├── App.jsx                      # layout shell + error banner
├── api/client.js                # GET /api/feeds · POST /api/sync
├── hooks/useFeeds.js            # single source of truth: toggles, store, cadences, lifecycle
├── data/sources.js              # source registry + refresh/cadence option sets
├── utils/{format,theme}.js      # UTC formatting · severity/accent/confidence styling
└── components/
    ├── Header.jsx  Sidebar.jsx  MetricsRow.jsx
    ├── CriticalTriagePanel.jsx  CTIFeed.jsx  DetailsDrawer.jsx
    └── ToggleSwitch.jsx
```

### Data normalization

Each source fetcher pulls its native schema live and compresses every record into one uniform
IOC object (`backend/src/normalize.js → makeIoc`):

```js
{ id, source, detectedAt, ingestedAt, severity, category,
  indicator, indicatorType, target, confidence, mitre[], headline, raw, stix }
```

The UI only ever consumes this normalized shape, so URLhaus URLs, ThreatFox C2 IPs, and CISA
KEV CVEs all render through the same components. Fetch failures are isolated per source — one
API being down never sinks the rest of the pull.