---
module: aria.ioc-management
title: IOC Management
tab: Claude
order: 40
audience: dev
---

Terse act-without-guessing reference. Owning service: **`hiveops-aria`** (port 8095, DB `hiveiq_aria`). Frontend: `hiveops-aria/frontend/src/components/IocManagement.svelte` via `ariaApi` → base `/api/aria`.

## Endpoints (`ThreatIocController`)

| METHOD | Path | Auth | Notes |
|--------|------|------|-------|
| GET | `/api/aria/ioc` | `hasRole('BCOS_ADMIN')` | params `type` (enum), `activeOnly` (default `true`). |
| GET | `/api/aria/ioc/active` | authenticated (no role) | param `type`; only `active=true`. |
| POST | `/api/aria/ioc` | `hasRole('BCOS_ADMIN')` | body `CreateIocRequest`; `source` forced `MANUAL`. |
| PUT | `/api/aria/ioc/{id}` | `hasRole('BCOS_ADMIN')` | updates value/description/confidence/expiresAt ONLY. 404 if id missing. |
| DELETE | `/api/aria/ioc/{id}` | `hasRole('BCOS_ADMIN')` | soft-deactivate (`active=false`), returns 204. NOT a hard delete. |

`CreateIocRequest`: `iocType`*(NotNull), `value`*(NotBlank), `description`, `sourceRef`, `confidence`, `reportedAt`, `expiresAt`. (* = required)

## Storage

- **Table:** `threat_ioc` (only table for this feature).
- **Columns:** `id, ioc_type, value, description, source, source_ref, confidence, reported_at, added_at, expires_at, active`.
- **Enums:** `ioc_type` = FILENAME, MD5, REGISTRY_KEY, DIRECTORY, SERVICE_NAME, IP, FILE_PATH · `source` = FBI_FLASH, FS_ISAC, MANUAL, OTX, MALWARE_BAZAAR, THREAT_FOX · `confidence` = HIGH, MEDIUM, LOW.
- Flyway V1–V8; `ddl-auto=validate`.

## Kafka (NOT on CRUD path — downstream of IOC match only)

- Produces `hiveops.aria.threats` (`AriaThreatProducer`), `hiveops.aria.sequences` (`AriaSequenceProducer`).
- No consumers in ARIA (no `@KafkaListener`).

## Consumers of the catalog

- `ThreatEventClassifier` — matches ingested events to active IOCs via `findByActiveTrueAndIocTypeAndValueIgnoreCase` (exact value, case-insensitive) for FILENAME/SERVICE_NAME/DIRECTORY/FILE_PATH/REGISTRY_KEY/IP.
- `IocFeedService` — writes feed-sourced IOCs; dedups by active type+value; logs to `ioc_feed_run`.

## Gotchas

- Role is `BCOS_ADMIN` ONLY here — NOT `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`. MSP_ADMIN → 403.
- DELETE = soft deactivate; no hard-delete endpoint exists.
- `ioc_type` immutable after create (UI disables it; backend update ignores it).
- **BUG:** `update()` never reads `request.getSourceRef()` → editing Source Reference is a silent no-op. Create persists it; PUT does not.
- POST always sets `source=MANUAL` regardless of payload.
- `confidence` defaults to `MEDIUM` server-side if null on create (UI sends `HIGH`).
- Matching is exact + case-insensitive; no wildcard/substring. Not retroactive to past events.
- Frontend `getIocs` filters by type client-side; it sends only `activeOnly` to the API.
- DB is on `10.10.10.188` (`hiveiq_aria`), not the services VM. Health: `GET /actuator/health` on 8095.

## Do NOT

- Do NOT expect DELETE to remove a row — it only sets `active=false`.
- Do NOT expect PUT to change `sourceRef` or `iocType`.
- Do NOT assume MSP_ADMIN can access — BCOS_ADMIN required.
- Do NOT add Kafka handling for IOC CRUD — it is synchronous JPA.
- Do NOT hand-write IOC rows into feed sources (`FBI_FLASH`/`FS_ISAC`/`OTX`/`MALWARE_BAZAAR`/`THREAT_FOX`) via this API — POST forces `MANUAL`; feed rows come from `IocFeedService`/seed migrations.
- Do NOT invent other IOC endpoints/tables/topics — the above is the complete surface for this module.
