How-to: Build Cross-Platform Investigations
Use gnat.investigations to collect, normalise, correlate, and persist
evidence from multiple connected platforms into a unified GNAT workspace.
Overview
InvestigationBuilder runs a five-step pipeline:
| Step | What happens |
|---|---|
| 1. Seed expansion | Queries each platform for indicators, incidents, cases, and events matching the seed values |
| 2. Incident expansion | Fetches constituent evidence (alerts, tasks, observables, timeline) for every discovered incident |
| 3. Normalisation | Translates every raw platform record into a common EvidenceNode |
| 4. Correlation | Adds cross-platform edges (same IOC, same host, same user, same campaign, same ticket) between nodes from different platforms |
| 5. Materialisation | Writes nodes + edges into a GNAT workspace as STIX objects and Relationship SROs |
Basic usage
from gnat.investigations import InvestigationBuilder, Seed, SeedType, materialize
# Provide any subset of your connected platform clients
builder = InvestigationBuilder({
"xsoar": xsoar_client,
"greymatter": gm_client,
"threatq": tq_client,
})
# Build the evidence graph from one or more seeds
graph = builder.build(
seeds=[
Seed("185.220.101.5", SeedType.IP),
Seed("INC-4892", SeedType.CASE_ID, hint_platform="xsoar"),
],
title = "Ransomware triage – 2026-04-05",
)
# Print a summary
print(graph.summary())
# Persist into a GNAT workspace
ws = materialize(graph, workspace_manager, workspace_name="ransomware-apr-2026")
Seed types
SeedType |
Typical value | Platforms queried |
|---|---|---|
IP |
"185.220.101.5" |
All platforms with indicator search |
DOMAIN |
"evil-corp.com" |
All platforms with indicator search |
HASH |
"d41d8cd98f00b204e9800998ecf8427e" |
All platforms with indicator search |
EMAIL |
"phish@bad.actor" |
TIPs, SOAR, email security platforms |
URL |
"https://evil.example/payload" |
All platforms with indicator search |
HOSTNAME |
"ws-12.corp.example.com" |
SOAR, EDR, SIEM, asset management |
USERNAME |
"jsmith@corp.example.com" |
SOAR, EDR, identity platforms |
ALERT_ID |
"alert-001" |
SOAR, SIEM platforms |
CASE_ID |
"INC-4892" |
SOAR platforms (XSOAR, TheHive, ServiceNow) |
TICKET_REF |
"JIRA-1234" |
Jira, ServiceNow |
Use hint_platform to direct a seed exclusively at one connector:
Seed("INC-4892", SeedType.CASE_ID, hint_platform="xsoar")
Working with the evidence graph
from gnat.investigations import EvidenceGraph, EvidenceNode, EvidenceEdge, NodeType
# Iterate nodes
for node_id, node in graph.nodes.items():
print(f"{node.node_type.value} [{node.platform}] {node.title or node.value}")
# Iterate edges
for edge in graph.edges:
print(f"{edge.source_id} --{edge.edge_type}--> {edge.target_id}")
# Filter by node type
incidents = [n for n in graph.nodes.values() if n.node_type == NodeType.INCIDENT]
observables = [n for n in graph.nodes.values() if n.node_type == NodeType.OBSERVABLE]
# Summary counts
print(graph.summary())
# "EvidenceGraph: 47 nodes across 3 platforms, 31 edges"
Node types: INCIDENT, OBSERVABLE, ASSET, IDENTITY, FINDING,
TASK, DECISION, ARTIFACT, TIMELINE_EVENT.
Materialising into a workspace
Once the graph is built, materialize() writes every node to the workspace
as a STIX object and creates Relationship SROs for the edges:
from gnat.investigations import materialize
ws = materialize(
graph = graph,
workspace_manager = workspace_manager,
workspace_name = "ransomware-apr-2026",
)
# The workspace now contains STIX objects sourced from all platforms
for stix_obj in ws.list_all():
print(stix_obj.stix_type, stix_obj.id)
Multi-platform investigation example
builder = InvestigationBuilder({
"xsoar": xsoar_client,
"greymatter": gm_client,
"threatq": tq_client,
"thehive": hive_client,
"servicenow_secops": sn_client,
"cortex_xdr": xdr_client,
})
graph = builder.build(
seeds=[
Seed("blackcat-ransomware", SeedType.IOC_VALUE),
Seed("185.220.101.5", SeedType.IP),
Seed("evil-corp.com", SeedType.DOMAIN),
Seed("INC-4892", SeedType.CASE_ID, hint_platform="xsoar"),
],
title = "BLACKCAT Ransomware Campaign — Apr 2026",
)
print(graph.summary())
ws = materialize(graph, workspace_manager, "blackcat-apr-2026")
Combining with the analysis layer
After building the evidence graph you can run correlation, gap detection,
and timeline reconstruction from gnat.analysis:
from gnat.analysis.graph import GraphQuery
from gnat.analysis.timeline import TimelineBuilder
from gnat.analysis.copilot.gap_detector import GapDetector
# Graph pivoting
gq = GraphQuery(graph)
context = gq.pivot("xsoar::incident::INC-4892", hops=2)
print(f"{context.node_count} related nodes")
# Timeline
builder = TimelineBuilder()
events = builder.from_evidence_graph(graph)
for e in events:
print(f"{e.timestamp.isoformat()} {e.title}")
# Gap detection (after linking graph findings to an Investigation)
from gnat.analysis.investigations import InvestigationService
service = InvestigationService(store)
inv = service.create(title="BLACKCAT", created_by="analyst@example.com")
service.add_hypothesis(inv.id, "BLACKCAT used spear-phishing for initial access.",
created_by="analyst@example.com")
detector = GapDetector()
gaps = detector.detect_all(service.get(inv.id))
for gap in gaps:
print(f"[{gap.severity}] {gap.description}")
See Also
- How-to: Use the Analysis Layer
- How-to: Create Intelligence Reports
- How-to: Use Workspaces
- Explanation: Analysis Layer Architecture
Licensed under the Apache License, Version 2.0