How Verdix turns one Suricata alert into a verdict by correlating eve.json records and enrichment

A Suricata alert tells you that a rule fired. That matters, but it is only one part of what happened on the network. Depending on the protocol and Suricata configuration, the same flow may also have HTTP, TLS, file, anomaly, or flow records elsewhere in eve.json. The IP addresses and domains involved may also have useful context that does not exist in Suricata at all. Before Verdix makes a verdict on an alert, it brings those pieces together.

An alert is only one event in eve.json

Suricata writes its EVE output as a stream of JSON events. Alerts are mixed with other configured event types such as flow, dns, http, tls, fileinfo, and anomaly. An alert event tells us which rule fired and provides information about the traffic that triggered it. Other EVE records can add more context around the flow.

An HTTP event may contain information about the request. A TLS event can provide details about an encrypted connection. A fileinfo event can describe a file observed in the traffic, while a flow record describes the network flow itself. DNS events can provide useful domain information as well, although a DNS lookup that precedes a separate HTTP or TLS connection is normally its own flow rather than part of the later connection’s flow_id.

Looking only at the alert leaves much of that context unused. Verdix reads the eve.json stream continuously so the surrounding records are already available when an alert reaches triage.

Following the flow_id

Suricata gives events from the same network flow a shared flow_id. Verdix uses that value to connect an alert to the other EVE records generated from the same flow. As eve.json is written, Verdix indexes every event by its flow_id. When an alert reaches the triage pipeline, Verdix can pull the records associated with that flow instead of treating the alert as an isolated event.

This is also why Verdix does not simply scan backward through eve.json when an alert arrives. Useful records are not guaranteed to have already been written. A flow record, for example, may appear after an alert that fired while the connection was still active.

Verdix waits a short time (30 seconds by default) before queuing the alert, so same-flow records have a chance to arrive. It gathers the flow’s records when triage begins. Records that arrive after that are still indexed, but they were not part of the verdict. This happens most with long-lived connections. Suricata writes the flow record when the connection ends, which can be well after the alert fired.

The result is a view of the connection built from the events Suricata already produced.

Adding context that is not in the flow

EVE records describe what Suricata observed, but they cannot answer every question an analyst may have about the systems and indicators involved. Verdix enriches that information before generating a verdict.

For IP addresses, an embedded GeoIP database provides country and ASN information without requiring an internet lookup. For domains, RDAP can provide registration information such as the registrar and registration date, which Verdix can use to determine the domain’s age. If a VirusTotal API key is configured, Verdix can also check the reputation of public IP addresses and domains involved in the alert. Private addresses, and addresses and names in your HOME_NET, are never sent. For internal addresses inside HOME_NET, Verdix attempts a reverse DNS lookup so the analyst can see a hostname when one is available instead of working only from an IP address.

Those sources do not always return an answer. VirusTotal may not be configured. An RDAP request may fail. An internal host may not have a PTR record.

Verdix keeps that distinction visible. Each enrichment source is recorded as having contributed information, failing, or not being configured. That means a missing result is not treated as a negative result. The analyst can see what information was actually available when the verdict was generated.

Building the context for the verdict

By the time the alert reaches the model, Verdix has more than the original Suricata event. It has the alert and rule information, the HTTP, TLS, DNS, and flow records that share the alert’s flow_id, enrichment about the indicators involved, and internal hostnames from reverse DNS.

Verdix assembles those pieces into the context given to the local LLM. The model receives the original alert metadata and rule, correlated EVE events, and enrichment results. The attacker/victim assessment described in the previous post is shown to the analyst but is not yet part of the model’s input. The model is asked to explain what actually happened on the network in concrete terms, then return one of three verdicts: likely false positive, suspicious (investigate), or likely true positive. It also returns its reasoning, the facts that drove the verdict, and a confidence score.

The LLM inference itself runs locally through Ollama, so the assembled alert context is not sent to a hosted model. External enrichment sources such as RDAP and VirusTotal may still make outbound requests when they are enabled.

The verdict is not the only output

A verdict without the evidence behind it is not enough for triage. Verdix stores the context used to make the decision along with the result.

In the investigation view, the analyst can inspect the model’s reasoning and contributing facts, the attacker/victim assessment, the enrichment results, and the status of each enrichment source as recorded when the verdict was made. The view also shows the correlated EVE records for the flow. Those are read when the page loads, so they can include records that arrived after the verdict.

If VirusTotal contributed reputation information, that is visible. If RDAP failed, that is visible too. If Verdix could not confidently determine attacker and victim, the investigation view shows that as ambiguous rather than replacing it with a guess.

The analyst can then accept the Verdix verdict or override it. Verdix does not close, suppress, or escalate the alert on its own.

Correlating a flow is not correlating an incident

There is an important boundary to what Verdix is doing here. The correlation described in this post happens around an individual Suricata alert and its network flow. Verdix uses the alert’s flow_id to find other EVE events associated with that same flow. That is different from taking several Suricata alerts across different connections and deciding that they are all stages of one larger intrusion. Verdix does not currently turn separate alerts into a reconstructed attack chain.

There is one case where Verdix connects separate alerts. When the same rule fires again between the same source and destination within an hour, Verdix reuses the earlier verdict instead of running enrichment and the model again. The investigation view marks the verdict as inherited and links to the original alert. This keeps repeated alerts, such as C2 beacons, from using up time and API quota. It is deduplication, not incident reconstruction.

The unit being triaged is still the individual Suricata alert. The difference is that the alert is no longer evaluated by itself.

From an alert to a verdict

A rule firing is the starting point of triage, not the entire story. Verdix takes the alert, finds the EVE records belonging to the same flow, adds available context about the hosts and indicators involved, and gives that evidence to the local model. The verdict comes at the end of that process. Just as important is the evidence behind it and the analyst’s ability to inspect it.