The whole IOC lifecycle — extract, enrich, hunt, block — as deterministic tools, with an agent on top that never gets the final say on a destructive action.
Six pip-installable layers for indicators of compromise. The boring primitives do the irreversible work the same way every time; a small LangGraph team orchestrates them; and a human stands between the AI and anything that touches production.
Give an agent autonomy over irreversible actions — push a firewall denylist, isolate a host — and a single confidently-wrong indicator becomes a self-inflicted outage.
The fix isn't a smarter model. It's structural: the model proposes and never executes, and no single authority can push a destructive change on its own.
Each layer is a plain, testable function behind its own pip extra — so import iocflow stays a one-dependency install and pulls in nothing you didn't ask for.
Pull IPs, domains, URLs, hashes, CVEs, ATT&CK technique IDs, threat actors and malware families out of unstructured text — with Public Suffix List validation, benign allowlists and re-fanging of evil-domain[.]ru built in.
Look each indicator up against VirusTotal, AbuseIPDB and abuse.ch, and return a single worst-wins verdict.
An LLM turns the enrichment into a structured assessment — and falls back to a deterministic, report-derived summary when no model is configured. It never raises.
Render ready-to-run hunt queries — CrowdStrike CQL, Cortex XQL and Sigma — straight from the indicators, offline and stdlib-only. An LLM can add behavioral hunts; the deterministic ones are always there.
Push malicious indicators to the control points you operate — Palo Alto, Zscaler, CrowdStrike, Abnormal — with dry_run=True as the default everywhere and an authoritative allowlist guard underneath.
The capstone: a small LangGraph team that uses L1–L5 as tools and loops until the case is done.
L1–L5 don't know the agent exists. They're plain functions with stable types — ExtractedEntities → EnrichmentReport → Commentary → HuntPlan → BlockReport — so you can use any one alone. The agent is a consumer of the primitives, not a replacement for them.
Inject any chat model as a plain callable. The core has zero third-party dependencies; the heavy integrations ride on opt-in extras, lazily imported.
from iocflow import investigate # one call: extract → enrich → comment → hunt → propose → (human) → block report = investigate(report_text) # the multi-agent team, end-to-end # or use any layer on its own — they're just typed functions from iocflow import extract, enrich, suggest_hunts iocs = extract(report_text) # deterministic, FP-defended verdict = enrich(iocs) # worst-wins across feeds hunts = suggest_hunts(iocs) # CQL · XQL · Sigma, offline # blocking is dry-run by default, behind an allowlist guard block(verdict.malicious, dry_run=True) # renders the calls; changes nothing
iocflow is the open-source sibling of SOC-in-a-Box — an AVP-sponsored multi-agent SOC where one local LLM played eight analyst roles, read-only against production, with a human approving every containment action.
SOC-in-a-Box proved the pattern against real systems. Two commitments made it trustworthy: the model orchestrates but never does the irreversible work itself, and no single authority can push a destructive action. Those ideas aren't SOC-specific — they're how you make any AI system that touches production safe enough to deploy. So I pulled them out and built a clean, public library around them.