---
module: aria.threat-events
title: Threat Events
tab: Claude
order: 40
audience: dev
---

Terse act-without-guessing reference. Owner service: **`hiveops-aria`** (`hiveiq_aria` DB, port 8095). All facts below confirmed in `hiveops-aria` source.

## Ownership
- **Read + ingest + classify + store + produce: `hiveops-aria` only.** No other service needed to render this screen.
- No Kafka **consumers** in aria for this feature — pure producer.

## Endpoints (METHOD path — role)
| Method | Path | Auth |
|--------|------|------|
| GET | `/api/aria/events` | `hasRole('BCOS_ADMIN')` |
| GET | `/api/aria/stats` | `hasRole('BCOS_ADMIN')` |
| POST | `/api/aria/ingest/events` | `permitAll` + `X-Service-Secret` header |
| GET | `/api/aria/ioc` | `hasRole('BCOS_ADMIN')` (IOC catalog behind the IOC Match column) |

- `GET /api/aria/events` params: `severity?` (CRITICAL/HIGH/MEDIUM/LOW/INFO), `deviceAgentId?`, `page` (def 0), `size` (def 50). Returns `Page<ThreatEvent>`, sort `detectedAt DESC`.
- Ingest body: `ThreatEventIngestionRequest` (deviceId, deviceAgentId, institutionKey, `events[]` of `ThreatEventItem{signalKey, eventType, rawPayload, detectedAt}`). Returns 202.

## Tables (DB `hiveiq_aria`)
- `threat_event` — the rows this screen lists. Cols: `device_agent_id`, `device_id`, `signal_key`, `event_type`, `severity`, `matched_ioc_id`→`threat_ioc`, `attack_phase`, `sequence_id`, `institution_key`, `raw_payload` (JSONB), `detected_at`.
- `threat_ioc` — IOC catalog; source of the IOC Match badge.
- `precursor_sequence_alert` — sequence alerts (sibling feature).

## Kafka topics
- Produce `hiveops.aria.threats` — key = `deviceAgentId` (else `"unknown"`). **Only CRITICAL/HIGH events published.** Payload `AriaThreatEvent`, `sourceService="hiveops-aria"`.
- Produce `hiveops.aria.sequences` — sequence alerts.
- Consume: **none.**

## Roles
- Read path is **`BCOS_ADMIN` only** — NOT `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`. MSP_ADMIN → 403.
- Ingest gated by `X-Service-Secret` == `ARIA_SERVICE_SECRET`, **only when that env var is non-blank**; blank ⇒ open.

## Gotchas
- MSP_ADMIN gets 403 on this screen — by design.
- `getEvents` checks `severity` first and returns early → **`deviceAgentId` is ignored whenever `severity` is also set.** Filters are effectively mutually exclusive server-side.
- MEDIUM/LOW/INFO are stored but never hit Kafka — downstream services will not see them.
- Classification (severity/IOC/phase) is done once at ingest by `ThreatEventClassifier`; rows are never re-classified.
- Unknown `eventType` on ingest silently falls back to `OS_EVENT`.
- Attack-phase enums: `PHYSICAL_ACCESS`, `MALWARE_STAGING`, `MALWARE_EXECUTION`, `PERSISTENCE`, `CLEANUP` (UI relabels).
- Auto-refresh is client-side (60s), `localStorage` key `aria_threats_autoRefresh`. Staleness ⇒ ingest problem, not UI.
- Blank `ARIA_SERVICE_SECRET` ⇒ ingest accepts anything (no auth).

## Do NOT
- Do NOT tell an MSP_ADMIN they should see this screen — they can't (BCOS_ADMIN gate).
- Do NOT expect a MEDIUM/LOW/INFO event on `hiveops.aria.threats`.
- Do NOT assume filtering by device + severity together works — it doesn't.
- Do NOT look for the data in another service — `hiveops-aria` / `threat_event` owns it exclusively.
- Do NOT re-classify or mutate historical rows to "fix" a label; fix the classifier/IOC catalog and re-ingest.
- Do NOT confuse `/api/aria/ingest/events` (write, service-secret) with `/api/aria/events` (read, BCOS_ADMIN JWT).
