CTI graph.

Relationships materialised as edges, so a pivot from one indicator to everything it touches is a traversal rather than a five-table join.

Why a graph at all

The interesting questions in threat intel are relational. Not “is this IP bad” but “what else does the actor behind this IP touch, and do we have any of it?” Answering that from normalised entity tables means an unbounded join; answering it from edges is a walk.

192.168.0.123indicatorAPT-GCC-04threat-actorDridexmalware2 campaignsT1566.002attack-pattern0.910.58depth is bounded on purpose — an unbounded walk on a dense graph returns most of the graph, which is not an answer
Fig. A two-hop pivot. Each edge carries its own confidence, so a weak hop is visibly weak.

The tables

  • graph_nodes — one row per entity participating in a relationship, typed by entity kind.
  • graph_edges — directed, typed relationships, each carrying its own confidence.
  • graph_communities — precomputed clusters, for “what moves together”.

It is a graph in Postgres — no separate graph database in the running system. The same rows that answer a lookup answer a traversal, so a relationship you can see in an API response is a relationship the graph already holds.

Traversal

Exposed as POST /v1/graph/traverse. You give a starting entity and a depth; you get back the reachable subgraph with the edge types that connected it.

the shape of a pivot
POST /v1/graph/traverse
{
  "start": { "type": "indicator", "value": "192.168.0.123" },
  "depth": 2
}

→ nodes: the indicator, 4 actors, 2 campaigns
  edges: attributed-to, communicates-with, indicates
  each edge carries its own confidence

Depth is bounded deliberately. An unbounded walk on a dense graph returns most of the graph, which is not an answer.

How edges get created

During enrichment. When a record is linked, the relationships the upstream asserted become edges — attribution from OTX pulses, malware association from MalwareBazaar, technique mapping from MITRE.

Edges keep their own confidence

An edge is a claim like any other. “This IP is attributed to this actor” can be strongly or weakly supported, and the traversal reports which — so a two-hop pivot through a weak edge is visibly weaker than one through a corroborated edge.

Consumed by SARA

The graph is not only for OmniIntel's own surfaces. It is wrapped as a contract that SARA consumes, alongside the KB chunk push . So the same curated relationships answer questions asked from outside this product.

CTI graph — OmniIntel docs · OmniIntel