Skip to content

Whitelisting — worked examples

Whitelisting explains the mechanics. This page answers the question that comes up in practice: I approved this — will it still match tomorrow?

How to read this page

  • Each case shows one change to tomorrow's window, and what every mode does.
  • Results were measured, not inferred — most twice: once against the matching modules, once by replaying the window through the whole pipeline.
  • Rows measured only at module level say so.
  • Every screenshot is the real console, captured from a lab replay of that case through the whole pipeline, with the strict entry below in force.

Reading the screenshots

  • Green border — the node matched a whitelist entry and was removed from scoring. Edges into it are dashed. Removed nodes only appear with Show excluded nodes switched on, which every screenshot here has.
  • No border — the node was scored. That includes approved nodes the subtree guard held back because something unapproved sits below them.
  • Dark, dimmed node — a phantom: a parent that started before the window opened (explorer.exe here, UNKNOWN on Linux). A picked phantom shows a dashed ring, as in Case 10.
  • Right-hand panel — the whitelist panel as you would use it next. Strict is selected because it is the mode of the entry being tested.
  • Top left — the verdict for the window after whitelisting.

The approved activity

One nightly backup chain, four processes. Every case below is a change to it.

The nightly backup chain in the console, with its four processes picked and Strict selected

The approved activity as the analyst saves it: Pick nodes on, the four processes picked (green), the explorer.exe phantom left out, Strict selected. The window scores attack 85% before the entry exists.

Phantoms are part of the graph you whitelist

  • a's parent (explorer.exe) usually started before the window opened, so the builder adds a phantom placeholder for it.
  • It is the dimmed explorer.exe node in the screenshot. It counts toward the 48-node limit when picked.
  • Phantoms are selectable and matchable. A phantom only ever matches another phantom — never an observed process, and never the other way round.
  • An unidentified phantom cannot be the only actor in an entry; the console refuses to save one. See Phantom nodes.
  • Cases 1–9 leave the phantom out of the selection, so they match whether or not it appears. Case 10 is about the opposite: activity where the phantom has to be included.

The rules used in these examples

Wherever a case shows a rules result, these four are in force — one exact rule on process.command_line per approved node:

Rule text Approves Node
powershell.exe -noprofile -file c:\corp\backup.ps1 the backup launcher a
reg.exe save hklm\sam c:\windows\temp\sam.hive the hive export b
whoami.exe /all the identity check c
net.exe user the account enumeration d
  • All four are enforcing (not observe-only), tenant-wide, with ignore case on — the defaults apart from observe-only.
  • One rule per node is the fairest comparison against approving the same four nodes structurally.
  • Exact vs pattern makes no difference to these cases. The rule column would read identically with pattern equivalents such as powershell\.exe -noprofile -file c:\\corp\\backup\.ps1, because these differences are structural. Where the two kinds genuinely diverge is text, covered in Rules — exact versus pattern.

Reading the rules column

Two numbers appear: removed and withheld.

  • removed — the rule matched and everything below that node is also approved, so it is taken out of scoring.
  • withheld — the rule matched, but something reachable below it is not approved, so the node stays visible to the model.
  • A node no rule matches at all is neither: it is simply scored.

Choosing what to approve

flowchart LR
    S["What you select"] --> G["Nothing selected<br><b>whole graph</b>"]
    S --> P["One node clicked<br><b>subgraph</b>"]
    S --> M["Several nodes picked<br><b>nodes</b>"]
    G --> G2["every component<br>in the window"]
    P --> P2["the whole component<br>containing it"]
    M --> M2["exactly your picks,<br>as an induced subgraph"]
Selection What is stored When to use
whole graph Every component separately Rarely — approves background noise too
subgraph (click one node) The entire connected component, including the phantom it hangs from The activity is the whole chain
nodes (pick several) Only your picks, plus edges between them Pulling one chain out of a large blob

Disconnected picks become separate entries

  • Picking two nodes with no edge between them does not create one entry describing both.
  • The selection is split into connected groups; each becomes its own entry.
  • Picking b and d produced two one-node entries, not one two-node entry.

Case 1 — a node is added

d also spawns curl.exe:

Case 1 in the console: curl.exe added under net.exe; only reg.exe is removed

curl.exe appears under net.exe. Only reg.exe is removed (green border). powershell.exe, whoami.exe and net.exe all lead down to curl.exe, so they are held back and curl.exe is scored with its full parent chain — still attack 85%.

Mode Result
shape matches — 1 removed, 3 withheld
strict matches — 1 removed, 3 withheld
exact / pattern rules 1 removed, 3 withheld

Everything matches; the new node holds its parents back

  • Structural matching is monomorphism — the engine looks for your pattern embedded in the window, so the extra node does not stop the match.
  • All four rules still match too — a, b, c and d are unchanged.
  • But nothing approves curl.exe, so the subtree guard engages: a node is removed only if everything reachable from it is also approved. This applies to structural entries and rules identically.
  • d, c and a all reach curl.exe, so all three are withheld — the branch stays visible to the model.
  • Only b is removed, because b reaches nothing uncovered.

Where the new node attaches decides how much is held back

Same numbers for shape, strict and rules.

New node attaches rules that match removed withheld
under d (deepest leaf) all 4 1 (b) 3 (a,c,d)
under a (next to the root) all 4 3 (b,c,d) 1 (a)
in a separate component all 4 4 0

Case 1 variant: curl.exe added directly under powershell.exe; three approved nodes removed

The same entry when curl.exe is added under powershell.exe instead: reg.exe, whoami.exe and net.exe are removed, powershell.exe is kept as curl.exe's parent. The window drops to benign.

  • The deeper the surprise, the more of the branch is kept.
  • An unrelated node elsewhere in the window changes nothing — nothing approved can reach it.

Measurement provenance

Both screenshots above are live-pipeline runs — the new node under d, and under a. Addition in a separate component, and of an ancestor (Case 6), were also replayed live; both matched and removed all four.


Case 2 — a node is removed

d does not run, or its event misses the window:

Case 2 in the console: net.exe missing; nothing is removed and the window is an attack

net.exe never ran. The strict entry no longer matches, nothing has a green border, and all three remaining processes are scored — attack 85%.

Mode Result
shape no match
strict no match
exact / pattern rules 3 removed, 0 withheld
  • A structural entry needs every node it was saved with.
  • Lose one and the entry stops firing entirely — all four nodes go back to full scoring, not just the missing one.
  • No attacker required: a short-lived process that misses the window is enough.
  • This is the most common reason an entry that "worked yesterday" breaks.
  • Rules degrade gracefully — each stands alone.
  • The net.exe user rule simply matches nothing this window; the other three still fire and a, b, c are removed.
  • Nothing is withheld, because no approved node can reach anything unapproved.

If the chain's tail is unreliable, use rules

Structural entries are all-or-nothing on presence. If your approved chain ends in short-lived children the collector sometimes misses, rules hold up where a topology entry will not.


Case 3 — a node is reparented

Same four processes, same command lines — d now hangs off a:

Case 3 in the console: net.exe now hangs off powershell.exe; nothing is removed

Same four processes and command lines, but net.exe now hangs off powershell.exe. A different shape, so the strict entry does not match — nothing removed, attack 85%.

Mode Result
shape no match
strict no match
exact / pattern rules 4 removed, 0 withheld
  • Structural entries encode edges, not just membership.
  • The same nodes in a different arrangement are a different graph.
  • All four rules still match — rules read only the node's own command_line, which did not change — so all four are removed.
  • Rules never look at structure, so reparenting is invisible to them.

Case 4 — a binary is renamed within its category

whoami.exe becomes quser.exe — both are system utilities.

Case 4 in the console: quser.exe in place of whoami.exe; the strict entry does not match

quser.exe in place of whoami.exe. Strict compares the binary, so nothing is removed — attack 85%. A shape entry would still match here.

Mode Result
shape still matches
strict no match
exact rule on command_line 2 removed, 1 withheld
  • This is where shape and strict genuinely differ.
  • Shape labels a process by category, so any system utility fits that position.
  • Strict adds the normalised image and command line, so a different binary is a different node. What the rules do

  • The whoami.exe /all rule stops matching — c now runs quser.exe.

  • Rules still firing: the ones for a, b and d.
  • c is now unapproved, and a reaches c, so a is withheld.
  • b and d reach nothing unapproved, so both are removed.
  • c itself is neither removed nor withheld — no rule matched it, so it is simply scored.

Case 5 — a command line changes

reg.exe saves a different hive — same binary, different arguments.

Case 5 in the console: reg.exe saves a different hive; the strict entry does not match

The graph looks identical to the approved one — the change is in reg.exe's command line (save hklm\security …), which is only visible on hover. Strict compares command lines, so nothing is removed — attack 85%.

Mode Result
shape still matches
strict no match
exact rule 2 removed, 1 withheld
  • Shape does not read command lines at all.
  • Strict does, but normalises volatile tokens first: GUIDs, long hex strings, and digit runs of four or more are masked.
  • So re-runs differing only in PIDs, handles or timestamps still match.
  • A genuinely different argument does not.

What the rules do

  • The reg.exe save hklm\sam … rule stops matching — the hive path changed.
  • Rules still firing: the ones for a, c and d.
  • a reaches the now-unapproved b, so a is withheld; c and d are removed.
  • Note the symmetry with Case 4: whichever node falls out of coverage, its ancestors are what get held back.

Narrow entries are less brittle

Had you approved only the c → d chain instead of all four nodes, this change would not matter — b is not part of that entry. The less an entry covers, the less can invalidate it.


Case 6 — the activity appears inside a larger tree

The approved chain runs under an outer powershell.exe that also spawns something else:

Case 6 in the console: the approved chain inside an outer powershell that also runs curl

The approved chain (green) sits under an outer powershell.exe that also starts curl.exe. All four approved nodes are removed; the outer powershell.exe and curl.exe are scored — benign.

Mode Result
shape matches
strict matches
exact rules 4 removed, 0 withheld
  • Entries of 48 nodes or fewer are matched as embedded subgraphs.
  • The approved activity is recognised wherever it recurs — including inside a process tree that did not exist when you approved it.
  • The outer nodes are scored normally.
  • All four rules still match, and all four nodes are removed.
  • The unapproved nodes (p, q) sit above the approved ones, and the guard looks downward only — at what an approved process spawned, not at what spawned it.
  • This is deliberate: you cannot control what launches your approved job, but you can be held responsible for what it starts.

Case 7 — the activity happens twice

Case 7 in the console: two copies of the chain under one phantom, all eight nodes removed

Two copies of the chain in one window. One entry matches both — all eight processes removed, benign.

Mode Result
shape 2 matches
strict 2 matches
exact rules 8 removed
  • One entry removes every occurrence in the window.
  • whitelist_dropped_components counts matches, not entries — one entry found in three places reports three.
  • The same four rules cover both copies: a rule is a statement about a value, so it matches every node carrying that value, however many there are.

Case 8 — only one node is approved

Scenario shape strict
identical re-run matches matches
any node added matches matches
any node removed still matches still matches
reparented still matches still matches
binary renamed matches matches
inside a bigger tree 2 matches matches
  • A one-node entry has no edges, so no topology change can break the match.
  • That sounds convenient and is usually a mistake.

What it removes depends on what is below it

Approving node a alone, as a one-node entry or as one rule:

one-node shape entry one exact rule
Matches any shell, anywhere only that command line
Removed 0 0
Withheld 1 (a) 1 (a)
  • a reaches b, c and d, and nothing approves them.
  • So the guard withholds a and removes nothing at all — for the entry and the rule alike. Approving a parent achieves nothing until you also approve what it starts.
  • A one-node entry on a leaf — b, d — is removed wherever it matches.

A one-node shape entry is the broadest thing you can create

  • It matches any node of that category, anywhere.
  • Approving one powershell.exe in shape mode approves every shell in every future window that has nothing unapproved below it.
  • On Windows most shells start something, so the guard often holds it back — but a shell that ran alone is removed, whatever it ran.
  • In the bigger-tree case it matched twice — the approved node and an unrelated outer powershell.exe.
  • To approve one specific thing: use strict, or better an attribute rule on image or command_line.

Case 9 — the selection is larger than 48 nodes

Above 48 nodes an entry cannot carry a pattern, so only an identical whole component can match, by hash.

Scenario Result
identical re-run, different PIDs and GUIDs, different endpoint matches — 61 nodes removed
one leaf added no match — 0 removed, verdict attack
  • Measured on a 61-node component.
  • The re-run matched across entirely different ProcessGuids — identity is normalised out.
  • Adding one leaf did not just fail to match the new node. It sent all 62 nodes back to full scoring, and the window was flagged as an attack — even though 61 of those nodes were explicitly approved.

Rules have no size cap at all

  • A rule is evaluated per node, so a 61-node component is no different from a 4-node one.
  • The four rules above would keep working on either, and adding a leaf would withhold its ancestors rather than un-match everything.

Do not whitelist large components

  • Below the cap, addition is free.
  • Above it, one extra node turns an approved window into a false positive.
  • If the panel warns the selection is large: pick a smaller subgraph, or describe the activity with an attribute rule.

Case 10 — the parent is a phantom

A different activity, from a real Linux host: the Debian sysstat cron job. cron started long before the window opened, so it appears as a phantom labelled UNKNOWN — and that phantom both reads /etc/shadow and launches the job.

Case 10 in the console: the UNKNOWN phantom, shadow, sh and debian-sa1 picked with Strict

The real window from agent-linux-01 (inference 56169bbece…, 39 nodes, zoomed to the four that matter). The dashed UNKNOWN node is the phantom cron: it reads shadow and starts sh → debian-sa1. All four are picked — Selection A below — and the panel notes that one of them is a phantom.

What the model said about it, before any whitelisting:

A phantom process (PID_113180) accessing the pinned credential file /etc/shadow … the preceding access to the shadow file by a phantom process indicates the activity is part of an unauthorized attempt to access credentials.

attack, probability 0.85, technique T1003. The accusation is the phantom reading /etc/shadow, so an approval that leaves either out approves nothing the model cares about.

Three ways to select it

Selection What is saved
A — all four, p f s d One 4-node entry
B — phantom left out, f s d Two entries: s → d, and f on its own. With p gone, nothing joins f to the rest
C — p f only Refused: the only actor is an unidentified phantom

What each one matches tomorrow

Same result in strict and shape mode for every row.

Tomorrow's window A B: s → d B: f alone
Identical — cron still a phantom match match match
cron starts inside the window (a real node, not a phantom) no match match match
The /etc/shadow read falls outside the window no match match no match
The phantom also spawns curl http://evil match — f s d removed, p kept as curl's parent match match
Attacker: UNKNOWN reads /etc/shadow, nothing else no match no match match — the read is removed

Selection A is the safe one

  • It approves the shadow read only together with the sysstat chain. An attacker who reads /etc/shadow from a resident process gets nothing; they would have to reproduce sh -c command -v debian-sa1 … and debian-sa1 1 1 as well.
  • It costs coverage when the window boundary moves: if cron itself lands in the window, or the shadow read falls outside it, the entry does not match and those windows are scored again.

Selection B is the dangerous one, and looks harmless

  • The 1-node f entry has no edges, so it matches /etc/shadow in any window, read by anything — the last row.
  • Nothing about saving it looks wrong: the panel shows three nodes, two edges. The split only shows as a second entry in Manage.

Selection C is refused, with this message in the panel and from the API: "This selection is carried by an unidentified parent process (shown as a phantom), and nothing else in it identifies who acted."

Case 10 Selection C: only UNKNOWN and shadow picked; the console warns and the save is refused

Selection C in the console. The panel warns before you save (lower red box), and the save itself is refused (upper red box). Nothing is created.

An extra child keeps its phantom parent

  • In the curl row, Selection A still matches. curl is not approved, so the subtree guard keeps the phantom above it; /etc/shadow, sh and debian-sa1 are removed.
  • The model sees UNKNOWN -> curl http://evil — the new activity with its parent — not a curl on its own.
  • Same behaviour as Case 1.

The same thing with rules

Rules, from least to most permissive. removed / withheld / left for the model — the identical window first, then the attacker's.

Rules in force Identical window Attacker: UNKNOWN reads /etc/shadow
process.command_line exact on s and d 2 / 0 / 2 — p and f still scored 0 / 0 / 2
… plus file.file_path exact /etc/shadow 3 / 0 / 1 1 / 0 / 1 — the read is removed
… plus phantom.image exact UNKNOWN 4 / 0 / 0 2 / 0 / 0 — the whole window is removed
  • Two rules on the chain do not fix the false positive. They remove s and d, and leave exactly the two nodes the model was accusing.
  • Adding the file rule removes the attacker's shadow read too, because a rule on a leaf has nothing below it for the guard to check.
  • Adding the phantom rule makes an attacker's read disappear completely. The guard does not help: UNKNOWN spawned nothing unapproved.
  • With the extra curl child, the full set removes 3 and withholds p, because p reaches curl. The guard catches that case — just not the attacker who spawns nothing.

For a phantom reading a sensitive file, use Selection A

Rules match values, and UNKNOWN is not a value anyone owns. Any rule set that removes the /etc/shadow read here also removes it for any resident process. Selection A is the only approval above that requires the recognised sysstat processes to be present.

Measurement provenance

Selection A was saved through the API from the real 39-node window and matched it — one embedding, exactly the four nodes. Selection C was refused by the live API. The other rows were measured against the matching modules on that same real window.


Rules — exact versus pattern

Rules ignore structure entirely. The differences are about text.

Rule Value seen later Result
exact whoami.exe /all, ignore case on WHOAMI.EXE /ALL matches
exact whoami.exe /all, ignore case off WHOAMI.EXE /ALL no match
exact whoami.exe /all whoami.exe /all > c:\out.txt no match
pattern whoami\.exe /all whoami.exe /all > c:\out.txt no match
pattern .*whoami\.exe /all.* whoami.exe /all > c:\out.txt matches
pattern .*whoami\.exe /all.* evil.exe -enc AAAA; whoami.exe /all matches
  • Patterns are anchored by default — the whole value must match.
  • .*-fencing turns a rule into a substring match.
  • The last row is why that matters: an attacker appends your approved text to their own command and the node is removed before scoring.
  • Fence deliberately, and only on fields an attacker cannot freely extend.

Path matters more than name

Rule Value seen later Result
image exact, C:\Windows\System32\whoami.exe same path matches
image exact, C:\Windows\System32\whoami.exe C:\Users\bob\AppData\Local\Temp\whoami.exe no match
executable_name exact, whoami.exe C:\Users\…\Temp\whoami.exe matches
  • executable_name is the basename only.
  • It approves the binary from any directory — including one an attacker dropped.
  • Prefer image with the full path.

What only exact can do

exact pattern
Value shorter than 4 characters yes refused — needs 4 literal characters
Value containing \, (, +, . unescaped yes refused — invalid regex
Cost per window (2000 nodes, 100 rules) 16 ms 2.8 s
  • Windows paths are full of backslashes, so the escaping burden is real.
  • Default to exact; reach for pattern only when the value genuinely varies.

Summary

Change in the window shape strict rules
Identical match match all removed
Node added below an approved node branch withheld branch withheld branch withheld
Node removed no match no match rest still removed
Reparented no match no match unaffected
Binary renamed (same category) match no match that node withheld
Command line changed match no match that node withheld
Inside a larger tree match match all removed
Happens twice 2 matches 2 matches all removed
Over 48 nodes, one node added no match no match unaffected — no size cap
Entry includes a phantom; parent observed next time no match no match unaffected
Phantom left out, leaving a file with no edges that file matches anywhere that file matches anywhere —

Two things to remember:

  • Structural entries tolerate addition and break on removal. They describe a shape, and the shape must be present in full. An addition below an approved node is tolerated as a match, but holds that branch back, the same as for rules.
  • Rules tolerate removal and hold back on addition. They describe values, and the subtree guard keeps an unapproved child's whole branch visible.
  • A rule never un-matches because of structure. In every case above, the rules that stopped firing did so because a value changed (Cases 4 and 5) or the node was absent (Case 2) — never because the graph was rearranged.
  • A phantom only matches a phantom. Include it when it is what the model is accusing, or when leaving it out would strand a file or network node (Case 10).