Project Goal
-LangGraph orchestration: agent decides which tools to run based on findings (not a fixed pipeline)
-Connects all prior tools: AutoTriage (identify suspicious evidence) → VolAI (finds suspicious processes or live network connections in files) → IAM Auditor (checks IAM privileges and related identity profiles) → AI PCAP Analyst (looks at any PCAP files and identifies suspicious traffic) → AI Detection Engineer (correlates findings and creates detection rules not identified)
-Generates three report formats: executive brief (CEO), technical findings (analyst), legal evidence (court)
-Generates ATT&CK heatmap: visualize all detected techniques across the incident
The benefit of using an agent is that this can be run on any memory dump in a continuous use of the tools. The decide_next() router looks at the evidence found after every tool and picks what runs next —once verified it takes different paths depending on which evidence is available.
LangGraph
LangGraph’s core revolves around a graph of nodes that all read and write to one shared state object, connected by edges with some of those edges being conditional: instead of “node A always goes to node B”, a function inspects the current state and returns which node should run next – function: add_conditional_edges.
Before LangGraph (and tools like it), people built this by hand with a while loop and a big if/elif chain — LangGraph just formalizes that pattern with a proper API and makes the state transitions inspectable.
Every node in ARIA reads the entire case state — every finding any other tool has produced so far — not just its own functions’ evidence. That’s what lets the Detection Engineer node correlation all the evidence at the end.
Agents vs pipeline
Automation pipeline – directed sequence: step 1, step 2, step 3 call each other like a script
The logic that decides “what happens next” is baked into the code structure itself — you’d need to edit the pipeline to change the order.
An agent makes decisions on what to do from what was found: system observes results and decides the next action based on that observation
ARIA’s agent works like this:
The routing decisions in decide_next() aren’t made by an LLM reasoning over the state in natural language — they’re made by rule-based logic signals (credential_theft, needs_pcap_analysis, etc.) that other tools wrote.
Why? Routing becomes deterministic, auditable, and testable (a defense attorney can ask “why did the system decide to run VolAI here” and you can point to an exact boolean condition).
LLM calls
Here me out…what if we swapped the decide_next router out for an LLM that can freely decide what to call next based on the state.
Right now, decide_next() is a deterministic function: it reads booleans state[“signals”] and returns a tool name. The graph structure doesn’t care how a routing decision gets made, only that something makes it and returns a valid node name.
So what would actually change if you swapped in an LLM?
Prompt would look something like: “here’s what’s run so far, what each tool found, here’s what’s still available — what should run next, or are we done?”
model reads state[“signals”] dict → reasons over the whole picture in natural language
Benefit: LLM can (1) weigh signals in combinations the original engineer never anticipated, (2) handle ambiguous or conflicting evidence the way a human analyst triages a messy case, (3) adjust its own prioritization without anyone needing to touch the code.
Downside: bye bye determinism. The same case can run twice and be routed differently. “Why did the system decide to check IAM before PCAP” now has to be answered by pointing at a model completion instead of a boolean expression. This makes it difficult to justify an investigative decision.
Attack surface increases…if any evidence content (a filename, a log line, a ransom note’s text) flows into the routing prompt unsanitized, there’s now a prompt-injection path where an attacker’s artifacts could manipulate the investigator’s control flow. And every routing decision now costs an API call, with the latency and failure modes that implies, versus a function call that returns in microseconds.
Two different purposes. For the evidence route, the define_next router shows definitive evidence that can map a story. For ambiguous evidence or more complex cases, the LLM route can handle the reasoning. For this project, I chose the deterministic route as it provides enough evidence to make a storyline of an attacker’s whereabouts. As LLMs and agents increase it is important to know when to use which one depending on the purpose and scale of your project.
LangGraph Orchestration
LangGraph agent deployed to ingest disk image + memory dump + logs + PCAP → complete IR report in one command
- Shared state (aria/state.py)
Every node reads from and writes to one Incident State object — a TypedDict with fields like:
findings: list[Finding]
signals: dict[str, Any] #IMP
completed_tools: set[str]
attack_techniques: dict[str, int]
signals is where each tool leaves breadcrumbs like needs_memory_analysis, credential_theft, needs_pcap_analysis. No tools are called directly— tools only write to signals, and the orchestrator reads signals to decide what happens next.
- Tool nodes
Each tool (aria/tools/autotriage.py, volai.py, etc.) has one function: run(state) -> list[Finding].
Ex. In autotriage.py, after scanning the disk manifest:
if signals.get("malware_dropped") or signals.get("ransomware_indicators"):
signals["needs_memory_analysis"] = True
AutoTriage doesn’t call VolAII, just raises a flag. Passes it on to form the evidence for “what happened next.”
graph.py wraps each tool module in a small node function (_make_node) that calls module.run(state) → merges the findings back into state via record_findings()→ logs a tool_runs entry with a timestamp and a reason
- The router: decide_next(state)
After every evidence-gathering node runs, this function looks at three things:
- which tools already ran (state[“completed_tools”])
- which artifacts actually exist (state[“artifacts”])
- what signals are set (state[“signals”])
and builds a priority list:
if NODE_VOLAI not in completed and _has_artifact(state, "memory"):
priority = 3 if signals.get("needs_memory_analysis") else 1
candidates.append((NODE_VOLAI, priority))
Same pattern for IAM Auditor and PCAP Analyst. It sorts candidates by priority and returns the winner. If nothing’s left to gather, it routes to the Detection Engineer. If that’s also done, it returns “END”.
- Wiring it into LangGraph (build_graph())
graph = StateGraph(IncidentState)
for name in _TOOL_MODULES:
graph.add_node(name, _make_node(name))
graph.set_entry_point(NODE_AUTOTRIAGE)
for node in (NODE_AUTOTRIAGE, NODE_VOLAI, NODE_IAM, NODE_PCAP):
graph.add_conditional_edges(node, decide_next, routing_map) #non-linear
graph.add_edge(NODE_DETECTION, END)
AutoTriage, VolAI, IAM Auditor, or PCAP Analyst runs → LangGraph calls decide_next(state) and routes to whatever node name it returns
Demo

Running case against agent → reports written

ATT&CK techniques found

signals that produce true
Incidence Brief
Case ID: ARIA-20260810-C8FD17
Prepared for: Executive leadership / Board
Prepared by: ARIA (AI Response & Investigation Agent), reviewed by cybersecbella
Case opened: 2026–08–10T17:04:12.791246+00:00
What Happened, in Plain Terms
-
Data on at least one file server was encrypted by ransomware.
-
Employee or service account credentials were stolen from a compromised workstation.
-
A privileged account was used from an unexpected location, indicating account takeover.
-
The attacker maintained an active remote-control channel into the network.
-
A large volume of data was moved internally in a way consistent with staged theft or mass encryption.
-
An email inbox rule was created to silently intercept and delete sensitive messages, consistent with financial fraud (e.g. wire transfer interception).
Key Findings
-
Ransom note recovered.** All your files have been encrypted. Contact darkhollow_support@protonmail.com w
-
LSASS memory access consistent with credential dumping.** Consistent with credential dumping (Mimikatz-style handle access)
-
C2 traffic confirmed: 10.20.4.117 -> 185.220.101.47:443.** TLS to a domain registered 9 days ago; JA3 matches known C2 framework fingerprint
-
C2 traffic confirmed: 10.20.4.117 -> 185.220.101.47:443.** Periodic ~60s beacon interval, low jitter — consistent with C2 heartbeat
-
Correlated attack chain: phishing dropper -> credential theft.** AutoTriage identified a masqueraded binary dropped by a macro-enabled attachment; VolAI independently confirmed LSASS credential access from a process in the same execution chain. High-confidence single incident, not two unrelated events.
## Evidence
ev1



What files were the logs stored in
## Technical Findings + Timeline
ev4


C2 traffic