---
module: aria.ioc-feeds
title: IOC Feeds
tab: Claude
order: 40
audience: dev
---

Terse agent reference. Everything below is confirmed in `hiveops-aria` source.

## Ownership
- **Owner service:** `hiveops-aria` (port **8095**, `spring.application.name=hiveops-aria`). Self-contained — no other service involved for this feature.
- **DB:** `hiveiq_aria` (PostgreSQL, Flyway `ddl-auto=validate`).

## Endpoints (all `@PreAuthorize("hasRole('BCOS_ADMIN')")`)
| Method | Path | Returns |
|--------|------|---------|
| GET | `/api/aria/feeds/status` | `List<Map>` one per feed: `feedName`, `configured`, `enabled`(always true), `lastRunAt`, `lastRunStatus`, `lastIocsAdded`, `lastIocsUpdated` |
| GET | `/api/aria/feeds/runs?page=&size=` | Spring `Page<IocFeedRun>` (UI size=20) |
| POST | `/api/aria/feeds/refresh` | `202 {"status":"refresh_started"}` — all feeds, background thread |
| POST | `/api/aria/feeds/refresh/{feedName}` | `202 {"status":"refresh_started","feed":<UPPER>}`; `400` if feedName unknown |

## Feed names (exact, case-sensitive in service map)
- `OTX`, `MALWARE_BAZAAR`, `THREAT_FOX`
- `refresh/{feedName}` upper-cases input before validating.

## Tables
- `ioc_feed_run` — run history (`feed_name`, `started_at`, `completed_at`, `status`, `iocs_added`, `iocs_updated`, `error_message`). Added in Flyway **V6**.
- `threat_ioc` — IOC catalog; feeds upsert here. Match key = active + `ioc_type` + case-insensitive `value`.

## Status values
- `RUNNING` (default on insert), `SUCCESS`, `ERROR`. Frontend also renders "Never run" when no row exists.

## Config / env
- API keys: `ARIA_FEED_OTX_API_KEY`, `ARIA_FEED_MALWARE_BAZAAR_API_KEY`, `ARIA_FEED_THREAT_FOX_API_KEY`. Blank key ⇒ `configured=false`, run skips, SUCCESS with 0/0.
- Cron: `ARIA_FEED_REFRESH_CRON` (default `0 0 3 * * *`).

## Kafka
- **None** for this feature — no producer, no consumer. (ARIA topics `hiveops.aria.threats` / `hiveops.aria.sequences` are unrelated paths.)

## Gotchas
- Refresh is **async fire-and-forget** on a raw `Thread`; `202` ≠ success. Read `/status` or `/runs` for the real result.
- Role is **`BCOS_ADMIN` only** — NOT `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')`. `MSP_ADMIN` ⇒ 403.
- `enabled` in `/status` is **hardcoded true**; `ARIA_FEED_ENABLED` env is declared but **unused** — does not gate anything.
- `countRunning` concurrency guard is soft: single-feed refresh is `@Transactional` (RUNNING row uncommitted until done) → double-click can double-run; `refreshAll`/cron reach `refreshFeed` via self-invocation → **`@Transactional` bypassed**.
- Feed clients swallow per-request errors (`log.warn`); a run only goes ERROR if `fetchFromFeed`/`upsertIocs` throws.
- Both abuse.ch clients hard-filter for ATM relevance ⇒ 0 results is normal, not a bug.

## Do NOT
- Do NOT treat a `202` as "feed succeeded."
- Do NOT expect `MSP_ADMIN` to work — feeds are BCOS_ADMIN-gated.
- Do NOT set `ARIA_FEED_ENABLED=false` to stop auto-runs (no effect); change the cron or remove keys.
- Do NOT look for a Kafka topic between feed and catalog — the upsert is a direct DB write.
- Do NOT invent other feed names — only `OTX`, `MALWARE_BAZAAR`, `THREAT_FOX` exist.
- Do NOT expect any other service (incident/journal/mgmt/devices) to serve this data.
