Whitelisting
This guide explains how to use the graph whitelist from the analyst console. Audience: SOC analysts, threat hunters, and the administrators who own the deployment's approved-activity baseline.
Looking for worked examples?
Whitelisting — worked examples answers the question this guide only describes in general terms: I approved this — will it still match tomorrow? It walks every case with console screenshots — a node added, a node removed, a binary renamed, the topology changed — across shape, strict, node-pick, exact and pattern. Every result there was measured on a live pipeline.
Reading the screenshots
The screenshots in this guide come from a live console. The numbered pink outlines mark the controls the text talks about; the numbers match the captions. Click any screenshot to enlarge it.
What whitelisting does
Logster's inference engine flags suspicious activity by analysing a graph of processes, files, registry keys, network connections and scripts from each short time window. Sometimes that graph captures activity that looks suspicious to the detection model but you know is benign — a scheduled backup, a monitoring agent, an admin's own lab command.
A whitelist entry tells the engine: this is approved — take it out before you score. From the moment you save one, any future window containing the approved activity has those nodes removed before the detection model sees the graph. The model cannot flag what it is never shown.
flowchart LR
G["Window graph<br>processes · files · registry<br>network · dns · scripts"] --> M{"Matches an enabled<br>whitelist entry?"}
M -- "yes" --> S["Matched nodes removed<br>from the scoring graph"]
S --> R["Detection model scores<br>what remains"]
M -- "no" --> F["Detection model scores<br>the full graph"]
S -.-> P["Full graph still persisted,<br>removed nodes flagged"]
The removal applies only to scoring. The full graph — including everything the whitelist took out — is still written to the console, with the excluded nodes marked, so an approval is always auditable after the fact.
Two kinds of entry
There are two different ways to approve activity, and choosing the right one matters more than any other decision in this guide.
| Structural entry | Attribute rule | |
|---|---|---|
| What it matches | The shape of a graph — which nodes, connected how | The text of one field on one node |
| How you create it | Select it on a graph you are looking at | Type it into a box; no graph needed |
| Console tab | Add | Regex |
| Match modes | shape, strict |
exact, pattern |
| Scope | Whole tenant | Whole tenant, or one endpoint |
| Observe-only | No | Yes, and it is the default |
| Safety guard | Subtree guard — an approved node with something unapproved below it is kept | Subtree guard — the same |
| Best for | A recurring chain of activity you can point at | A specific command, path or binary you can describe |
Which should I use?
If you can describe the activity in one line of text — "this exact backup command", "anything writing to this log path" — use an attribute rule. It needs no source graph, it can be scoped to a single endpoint, and it can be watched in observe-only mode before you trust it.
Use a structural entry when what makes the activity benign is the arrangement rather than any one value, and you have an example on screen to select.
Before you whitelist
Good candidates
- Recurring admin activity confirmed benign on every endpoint where it appears: scheduled backups, monitoring scripts, telemetry agents.
- Lab or test activity from machines you control.
- Known-noisy third-party software: installer helpers, update services.
Do not whitelist
- Anything you have not fully investigated. A whitelist is trust, not suppression.
- A pattern merely because it is flagged often. Confirm it is benign first, everywhere it occurs.
- A graph containing both benign and malicious activity. Approve only the part you trust — use subgraph scope, or a rule narrow enough to name just it.
- Activity that is benign on one endpoint but not others, unless you scope the rule to that endpoint.
Structural entries
Step 1 — open the graph
On the inference list, click Graph on any row to open the Graph Modal. The window's graph is drawn as nodes and edges. The whitelist panel is on the right, on the Add tab.
(1)* Graph opens the window's graph. (2) Attacks Only* hides benign rows; untick it when the activity you want to approve was scored benign.
The Graph Modal. (1) The Add, Regex and Manage tabs. (2) Pick nodes. (3) The scope line — it always says what will be saved. (4) Shape or Strict. (5) The save button; its label changes with the scope.
Step 2 — choose the scope
- Whole graph (default, nothing selected) — every connected component in the window becomes its own entry.
- Subgraph (click a node first) — only the connected component containing that node is approved. The component is highlighted.
- Nodes (turn on Pick nodes, then click each node) — exactly the nodes you picked, plus the edges between them. Picks with no edge between them become separate entries.
Subgraph scope. (1) Click a node. (2) The panel switches to the whole group containing that node. (3) The button now reads Whitelist this group.
Node scope. (1) Pick nodes on. (2) The panel counts what you picked and what is left unapproved. (3) Strict selected. (4) A reason and your name. (5) Whitelist 4 selected nodes.
Prefer subgraph scope when the activity is the whole chain, and node picks when it is one chain inside a larger blob. Whole-graph scope approves background noise along with the thing you meant to approve. Choosing what to approve compares the three scopes on the same window.
Phantom nodes (drawn dark and dimmed) are part of the graph in every scope. A subgraph includes the phantom its component hangs from, and a phantom can be picked like any other node. See Phantom nodes for when to include one and when to leave it out.
Step 3 — choose shape or strict
| Mode | What it matches | When to use |
|---|---|---|
| Shape | Topology plus node category — "any shell spawning any system utility". A renamed binary in the same category still matches. | Approving a class of activity. Broader, and correspondingly riskier. |
| Strict | Shape plus normalised identifying values: image, command line, file path, domain. | Approving a specific action. Safer; variations need their own entries. |
Shape mode, worked:
Approved: powershell -> mshta -> dns(google.com)
Later run: powershell -> mshta -> dns(github.com) matches
The topology is identical and both destinations are DNS nodes, so the categories line up.
whoami.exe and quser.exe are both system utilities.
Strict mode, worked:
Approved: cmd /c whoami & hostname
Later run: cmd /c whoami & hostname matches
Later run: cmd /c quser & hostname does not match
Process GUIDs, long hex strings and long digit runs are normalised out before hashing, so a strict entry survives a re-run with different PIDs.
Shape and strict are compared side by side, with console screenshots, in Case 4 — a binary is renamed and Case 5 — a command line changes.
Two-node shape patterns are usually too broad
process|SHELL -> process|SHELL — any shell spawning any shell — is
one of the most common patterns on Windows, and approving it can
silence a great deal besides the thing you meant. If a shape entry
matches more often than you expected, switch to strict, or select a
three-node component so the extra node narrows it.
Step 4 — reason, then save
Write a reason your colleagues can act on: "Nightly backup job, confirmed benign on all DC endpoints" beats "benign". Then click the save button — it reads Whitelist this group, Whitelist N selected nodes or Whitelist the entire graph, depending on the scope.
(1) The panel confirms what was saved. The entry takes effect on inferences that run after the engine reloads — within 60 seconds.
The 48-node ceiling
A selection of 48 nodes or fewer is stored with its labelled structure, which lets the engine find it as an embedded subgraph — the approved activity is recognised even when it recurs inside a larger process tree.
Phantom nodes count toward the 48.
Above 48 nodes the entry falls back to matching the whole connected component by hash. In practice such an entry never fires again, because any component large enough to exceed the cap will differ by at least one node on its next occurrence, and a different node set is a different hash. See Case 9 for a measured example, and Case 6 for the embedded match working below the cap.
Do not whitelist large components
If the panel tells you the selection is large, the entry is close to useless. Select a smaller subgraph, or describe the activity with an attribute rule instead.
Attribute rules
The Regex tab creates a rule from a line of text. There is no graph selection and no structure. A rule says: any node of this type, whose named field matches this text.
The Regex tab. (1) Node type and field. (2) Exact or Pattern. (3) The rule text — use the selected node's command_line fills it from the node you clicked. (4) The live measurement: matches in this graph, how many would be held back, and the share of the 300-graph sample. (5) ignore case, only \<endpoint> and observe only. (6) The save button, which says whether the rule will observe or enforce.
Node types and fields
| Node type | Matchable fields | Platform |
|---|---|---|
process |
command_line, image, executable_name |
Windows, Linux |
file |
file_path, path, name |
Windows, Linux |
registry |
target_object, name |
Windows |
network |
network_endpoint, name |
Windows, Linux |
dns |
query_name, name |
Windows |
script |
body, text, name |
Windows (PowerShell script blocks) |
syscall |
syscall_name, name |
Linux (auditd) |
phantom |
command_line, image, executable_name |
Windows, Linux |
The console builds its dropdowns from this list, so a rule can never name a field that does not exist. That matters: an unmatchable rule reads exactly like a working one right up until you need it.
process and phantom are separate types on purpose. A process rule
never matches a phantom, so rules saved before phantoms were matchable
behave exactly as they did. To approve a phantom you have to name the
phantom type — see Rules and phantoms before you
do.
Exact or pattern
| Kind | Meaning |
|---|---|
| Exact | The field must equal the text, case-insensitively by default. |
| Pattern | A regular expression. The whole value must match. |
Rules — exact versus pattern shows where the two diverge on real command lines and paths.
Whole-value matching
Pattern rules are anchored — the engine uses fullmatch, not search.
This is a deliberate safety property, not a limitation.
A substring rule for Test-NetConnection -ComputerName 10.0.0.1 would
also be satisfied by:
An attacker appends your approved text to their own command line and the node is removed before scoring. Requiring the whole value to match closes that door.
To match text appearing anywhere in a value, fence the pattern with .*:
Do this sparingly, and only when the field is one an attacker cannot freely extend. Writing those four characters is meant to be a deliberate act.
A pattern must contain at least 4 literal characters. Below that it is mostly metacharacters and its breadth cannot be predicted. Rule text is capped at 512 characters.
Patterns are tested for catastrophic backtracking
When you save a pattern, the API runs it against adversarial inputs in a sandboxed child process and refuses it if it backtracks catastrophically. At inference time every pattern match runs under a hard timeout; if one is hit, the node is kept and scored normally. A refused pattern is the guard working, not a defect — rewrite it more specifically.
Narrowing a rule
Two checkboxes decide how far a rule reaches:
| Control | Effect |
|---|---|
| ignore case | On by default. Turn it off when case is meaningful. |
| only \<endpoint> | Off by default, so the rule applies tenant-wide. Tick it to confine the rule to the endpoint you are looking at. |
The endpoint checkbox defaults to off on purpose. A rule pinned to one endpoint that then fails to fire elsewhere reads to the next analyst as a broken rule, and they will widen something that was never wrong.
A third narrowing condition, parent image, is available on the API but
not in the console. It makes a rule match only when the node's parent
process image ends with the given text — a cheap structural anchor without
the cost of a structural entry. Set parent_image when creating the rule
through POST /whitelist/rule.
The parent can be a phantom. On Windows a phantom parent usually carries
its real image (svchost.exe, services.exe), so parent_image anchors
through it normally. On Linux most phantom parents carry only the
placeholder UNKNOWN — do not anchor to that. parent_image: UNKNOWN
means "any process whose parent started before the window opened", which
is almost everything on a host.
Observe only
New rules are saved in observe-only mode by default. An observe-only rule is evaluated on every window and reports what it would have removed, but removes nothing. Nothing about scoring changes while it is observing.
This exists because "is it safe to enforce this?" cannot be answered from the one window you happened to be looking at. Leave a rule observing for a few days, check its activity in the Manage list, and flip it to Enforcing there once the numbers look like what you intended. The toggle works in both directions, so a rule that starts misbehaving can be put back under observation without being deleted.
The breadth gate
Before a rule is saved, the API runs it against a sample of 300 recent graphs and measures how much of that sample it touches:
| Share of sampled nodes matched | Result |
|---|---|
| Under 2% | Saved without comment |
| 2% to 10% | Saved, with the number shown to you |
| Over 10% | Refused, unless you explicitly override |
The same text box that writes a tight rule writes one that silences an entire category, and nothing else in the system would notice. The counter in the panel updates as you type, showing how many nodes the rule matches in the graph on screen and across the sample.
A rule the gate refuses. (1) The pattern .*\.exe.*. (2) It matches 77% of the sampled nodes — too broad to save — and offers an override checkbox. (3) The save button stays disabled until you override.
The subtree guard
This is the single most important safety property of the whitelist. It applies to structural entries and attribute rules alike.
A matched node is removed only when every node reachable from it is also approved — by that entry, another entry, or a rule. If an approved process started something nothing approves, the path down to it is kept and scored normally. Those nodes are reported as withheld.
The same thing for a structural entry:
Approved: A -> B
Window A: A -> B A and B removed
Window B: A -> B -> C A and B withheld — C is scored with its parents
Window C: P -> A -> B A and B removed — an addition above does not count
Window D: A -> B, A -> C B removed, A withheld — only the path to C is kept
Additions above or beside an approved chain leave it removed, exactly as before. Only an addition below an approved node holds it back. Both cases are shown in the console in Case 1 — a node is added, and the single-node version in Case 8 — only one node is approved.
Rule approves: backup.exe
Window A: backup.exe -> tar.exe -> /backup/db.tar (all covered)
removed from scoring
Window B: backup.exe -> tar.exe -> /backup/db.tar
-> powershell -c <encoded> (not covered)
withheld — the whole branch is scored
Without this guard an attacker only has to run under an approved parent to
disappear. Worse, the approved parent would be stripped away while the
hostile child survived as an orphan, destroying exactly the context the
model needs to recognise it. Structural entries used to do exactly that: an
approved powershell -> recon found beside a new mshta removed
powershell and left mshta scored with no parent, and in a second case
the model then described a parent the graph did not contain.
The guard looks down the graph only. An unapproved node above a match never withholds it. That includes phantoms: a phantom is always a root — it stands for a parent the window never saw start — so an unapproved phantom parent never stops its approved children being removed. The phantom itself stays in the graph and is scored.
A withheld count is a signal, not a failure
whitelist_withheld_nodes above zero means an approved process did
something your rules do not describe. That is worth looking at. The
engine also logs it at warning level as
inference.whitelist_withheld.
Phantom nodes
When a window contains a child process whose parent started before the
window opened, the graph builder inserts a placeholder for that parent — a
phantom. The console draws it as a dark, dimmed node. On Linux its label
is usually UNKNOWN.
Phantoms participate in whitelisting, as their own node type. They can be picked, saved in an entry, matched and removed like any other node.
This matters because a phantom is often the only thing joining a file or network node to the process tree. Leave it out and that node is stranded as a one-node group with no edges, which is the broadest entry there is. Measured over 300 real Linux windows: excluding phantoms left 10,574 such stranded nodes; including them leaves 540.
How a phantom is identified
A phantom is labelled from whatever the builder managed to record:
| What it carries | How it matches |
|---|---|
Image and command line (usual on Windows — inherited from the child's ParentImage / ParentCommandLine) |
As precisely as a real process node |
| Image only | By category and image |
Nothing — UNKNOWN on Linux, empty on Windows |
One shared token per platform. Every unidentified Linux phantom matches every other; none matches a Windows one |
Three rules follow, and all three are enforced:
- A phantom only matches a phantom. It never matches an observed process, and an observed process never matches it, even with the same image. They are different facts: in one case the sensor saw the process start, in the other it did not.
- An unidentified phantom cannot be the only actor in an entry. Being
unidentified means "started before the window opened", and every
long-running program on the host satisfies that for free. So an entry of
UNKNOWNreading/etc/shadowwould approve that read by any resident process, malware included. The console refuses it. Add a process whose command line you recognise, and the entry is accepted — an attacker would then have to reproduce that process too. - Existing entries are unaffected. Entries saved before phantoms took part keep matching; the engine fingerprints each window both ways.
Worked example: a cron job that reads /etc/shadow
The Debian sysstat cron job, as seen on a Linux host:
The dark UNKNOWN node is the phantom — cron, started before the window opened. It reads shadow and starts sh → debian-sa1. All four are picked, and the panel notes that one of them is a phantom.
The model flags this as credential access (T1003), and its reasoning names
the phantom directly: a phantom process accessing the pinned credential
file /etc/shadow. The phantom is the accusation, so it has to be part
of the approval.
| What you pick | Saved as | Result |
|---|---|---|
| All four nodes, strict | one 4-node entry | Accepted. Matches this activity; does not match UNKNOWN reading /etc/shadow on its own |
UNKNOWN and /etc/shadow only |
one 2-node entry | Refused — the only actor is unidentified |
sh, debian-sa1, /etc/shadow (phantom left out) |
a 2-node entry and a 1-node /etc/shadow entry |
Accepted, but the 1-node entry approves /etc/shadow read by anything — the unsafe outcome the refusal exists to prevent |
The second row of the table: only UNKNOWN and shadow picked. The panel warns before you save, and the save is refused.
Pick all four. The walkthrough with every variant measured is in Whitelisting examples — Case 10.
Rules and phantoms
A rule reaches a phantom only through the phantom node type. Most
Windows phantoms carry an image, so phantom.image works as it does for
processes. An unidentified Linux phantom's image is the literal UNKNOWN.
Never pair a phantom.image = UNKNOWN rule with a rule on the file it reads
Each rule alone is contained: the subtree guard withholds a phantom that
spawned anything unapproved, and a phantom rule on its own removes
nothing else. But together, phantom.image = UNKNOWN and
file.file_path = /etc/shadow remove the whole window when any
resident process reads /etc/shadow — measured: 2 of 2 nodes removed,
nothing left for the model to score. Use a structural entry that
includes the process you recognise instead.
What this costs
- A phantom entry does not match a run where the parent was observed.
If
cronstarts inside the window next time, the graph has a realcronnode where the entry expects a phantom, and it does not match. That is correct — they are different graphs — but it means occasional windows are scored again. For activity whose parent is sometimes in the window and sometimes not, pick the identified core and leave the phantom out, as long as that does not strand a file or network node. - Removal still breaks it. Like any structural entry, a phantom entry
needs every node it was saved with. If the
/etc/shadowread falls outside the window, the entry does not match — the sysstat chain is then scored, and without the shadow read it is ordinary activity. - An extra child holds the phantom back. If the phantom also spawns
something new, the phantom is kept as that child's parent and the rest of
the entry —
/etc/shadow,sh,debian-sa1— is still removed. See The subtree guard.
What you see after a match
On the inference
| Field | Meaning |
|---|---|
whitelist_applied |
True when anything was removed |
whitelist_dropped_components |
How many separate matches fired — one per matched component, one per place an embedded pattern was found, and one per rule that removed anything. A single entry found in three places counts three times. |
whitelist_dropped_nodes |
Total nodes removed before scoring |
whitelist_matched_ids |
The entry IDs that fired |
whitelist_rule_dropped_nodes |
Of the above, how many came from attribute rules |
whitelist_withheld_nodes |
Nodes an entry or rule matched but the subtree guard kept. Recorded even when nothing was removed |
whitelist_shadow_matches |
Matches by observe-only rules, which removed nothing |
whitelist_shadow_ids |
The observe-only entries that matched |
The investigation panel summarises these, and the Whitelist Entries tile opens the Manage list without leaving the inference.
An inference after a match. (1) The Whitelist tile: 1 node excluded, from 1 subgraph; show what was excluded opens the graph on it. (2) Graph opens the full persisted graph. (3) Whitelist — held back: 3 matched nodes were kept because something below them is not approved — here a new curl.exe (see The subtree guard).
On the graph
The persisted graph keeps every node. Nodes the whitelist removed carry
is_whitelisted: true, the document lists them in whitelisted_node_ids,
and the Graph Modal's legend shows a whitelisted (excluded from
scoring) row with the count. Selecting that legend row highlights them.
The same inference's graph. (1) reg.exe has a green ring: it was removed before scoring. powershell.exe, whoami.exe and net.exe were matched too, but are held back as curl.exe's parents. (2) Show excluded nodes spotlights every removed node. (3) The legend row, with its count.
When the whole window is approved
If every node in the window is whitelisted, there is nothing left to
score, and the window is recorded as benign with attack_prob: 0. It is
not skipped: the graph is still persisted with all its nodes flagged,
so the row appears in the console and you can confirm what your entry
removed. An allowlist that silently erased windows would not be auditable,
and the fully-covered case is the one you most want to check.
Managing entries
Switch to the Manage tab in the whitelist panel, or open Whitelist Entries from the investigation view.
| Action | Applies to | Effect |
|---|---|---|
| Enabled / Disabled | All entries | Disabling keeps the entry but stops the engine applying it. Reversible. |
| Observe only / Enforcing | Rules only | Slide to Enforcing when you are ready for a watched rule to start removing nodes. Slide back at any time. |
| Delete | All entries | Permanent. No undo. |
| Preview | All entries | Draws the graph the entry covers. |
Each card carries the properties that silently change what an entry matches, because none of them is visible anywhere else once the entry is saved:
- A badge —
shapeorstrictfor structural entries,exactorpatternfor rules. - What it covers —
graphorsubgraphfor structural entries with a node and edge count;node_type.fieldfor rules, with the rule text printed underneath. - only \<endpoint> or all endpoints, on every entry.
- ignores case or case-sensitive, on rules.
The Manage tab. (1) Download YAML and Restore from file — see Backup and restore. (2) The badge and what the entry covers: STRICT, 4 nodes and 3 edges, all endpoints. (3) Preview, the Enabled toggle and delete (×). (4) The 7-day activity total.
An attribute rule. (1) EXACT, the field, the endpoint scope and case handling. (2) The Observe only / Enforcing toggle. (3) The rule text. (4) What it has removed in the last 7 days.
Changes are not instantaneous. The engine reloads its whitelist set every 60 seconds, so allow up to a minute after enabling, disabling or deleting an entry before testing the result.
Seeing what an entry covers
A saved entry keeps only derived data — a hash, or labels with no raw attributes — so it cannot be read back on its own. Preview solves this by drawing a snapshot of the covered graph that is frozen into the entry when it is created. That is what makes "what did I actually approve?" answerable months later, which is usually the question that decides whether an entry stays.
Preview* of the strict entry above. (1) The mode and the reason it was saved with. (2)* What it covers: 4 of the 5 nodes in its source graph, 3 edges, and where it came from.
Two limits are worth knowing:
- The snapshot is capped at 60 nodes. A larger component is shown truncated, and the preview says so.
- Entries created before snapshots existed have none, and fall back to re-deriving from the source graph. That works only while the graph is still within retention, which defaults to 14 days. After that the entry still matches perfectly well — there is simply nothing left to draw.
Seeing what an entry has actually done
Every enabled entry in the list carries a rolling 7-day total underneath it. An enforcing entry reads "Excluded N nodes in 7 days"; an observe-only rule reads "Would have excluded N nodes in 7 days". An entry with no matches says so too, which is how you find entries that no longer apply to anything.
This is what makes the observe-only toggle meaningful: enforce a rule once
its observed total matches what you intended, and narrow it when it does
not. The API exposes the same figure over a longer window —
GET /whitelist/{id}/activity?days=30, up to 90 days.
Backup and restore
Whitelist entries are the only state in Logster that an analyst authors by hand. Events, graphs and verdicts all regenerate from the log feed, so losing those indices costs history but not work. Losing the whitelist index costs every approval decision ever made, and there is nothing to re-derive them from.
From the console
The Manage tab carries a backup bar:
- Download YAML — every entry for the tenant, including disabled ones, as a single file.
- Restore from file — upload a previously downloaded file. Entries are restored under their original IDs, so importing the same file twice is idempotent rather than duplicating anything.
From the command line
# take a backup
python3 scripts/whitelist_backup.py export -o deploy/whitelist.yaml
# check a file without writing anything
python3 scripts/whitelist_backup.py import deploy/whitelist.yaml --dry-run
# restore after a wipe or a redeploy
python3 scripts/whitelist_backup.py import deploy/whitelist.yaml
Point --api at the API service when it is not on localhost. Pass
--no-snapshots to drop the covered_graph previews, which are most of
the file's size. Restored entries still match exactly as well without
them, but they lose Preview permanently: the snapshot is written once,
at creation, and is never regenerated. A snapshot-less entry can only fall
back to its source graph, and only while that graph is still within
retention.
The file is plain YAML keyed by whitelist_id, so it can live in git
beside the rest of your deployment, be reviewed in a pull request, and be
replayed into a fresh cluster.
Restores survive a wiped Elasticsearch
A restored entry matches on its own stored fingerprint or rule text. It does not need the graph it was created from to still exist, so a full index wipe or a redeployment does not weaken it at all.
Only Preview is affected, because it is the one thing that needs a copy of the original graph. Keep the snapshots in your backup and Preview survives the wipe too.
Troubleshooting
I whitelisted it and the next inference still flagged it
Whitelisting examples shows, case by case, which changes to an approved activity still match and which do not. Find the case that looks like your window first. Otherwise work through this list:
- Refresh delay. Entries take up to 60 seconds to load.
- The rule is observe-only. New rules default to observing, and an observing rule removes nothing. Check the entry in Manage.
- The subtree guard withheld it. Check
whitelist_withheld_nodeson the inference, or the Whitelist — held back row. If it is above zero, an approved process had something below it that no entry or rule covers — that is the guard working, and whatever is below is worth investigating. - You approved a different subgraph. A window often holds several disconnected components. Open Preview on the entry and confirm it covers what you meant.
- The shape differs. A different parent, a different command line, a missing child — any of these change the graph. Strict entries will not match a command line that differs at all; shape entries will not match if a node's category changed.
- The component was too large. Over 48 nodes an entry only matches an identical whole component, which in practice does not recur. Use a smaller selection or a rule.
- The value is over 8192 characters. Attribute values longer than that are skipped by rule matching entirely, so a very long command line never matches any rule.
- The collector missed the child events. If short-lived children did not reach the window, the pattern is incomplete. Check that the Sysmon or auditd rules do not exclude the binaries involved.
- The entry has a phantom, and this time the parent was observed. A phantom only matches a phantom. If the parent process started inside this window, the graph has a real node where the entry expects a phantom. Check the graph: a dark, dimmed node in the source, a normal one now. Save a second entry from this window, or re-save the first without the phantom if that does not strand a file or network node.
- The model is accusing a phantom you left out. If the verdict's
reasoning names a phantom or something only a phantom touched —
typically a sensitive file read by
UNKNOWN— approving the rest of the chain leaves that behind, still scored. Include the phantom.
A rule I want to save is refused
- "matches N of M recent nodes" — the breadth gate. The rule is too broad; narrow the text, pin it to an endpoint, or add a parent anchor. Override only when that breadth is genuinely intended.
- "fewer than 4 literal characters" — the pattern is almost entirely metacharacters, so its breadth cannot be predicted. Add literal text.
- "pattern matches the empty string" — as written it would match a node carrying nothing. Anchor it to something real.
- "caused catastrophic backtracking" — the pattern backtracks exponentially on adversarial input. Rewrite it with fewer nested quantifiers.
- "not matchable on \<type> nodes" — the field does not exist for that node type. Pick one from the dropdown.
A structural entry I want to save is refused
- "carried by an unidentified parent process" — the only process in
your selection is a phantom with no identity (
UNKNOWN). Saved, it would approve the activity for anything that started before the window opened. Add the process whose command line you recognise — usually the child the phantom spawned. The panel shows the same warning before you save. - "above the 48-node limit" — see The 48-node ceiling. Phantoms count toward it.
Whitelisting silenced something I cared about
Disable the entry from Manage; the engine stops applying it within 60
seconds. Then recreate it more narrowly — strict instead of shape, a
smaller subgraph, or a rule scoped to one endpoint. The entry's
created_by field records who saved it.
How retroactive is whitelisting?
Not at all. Entries affect only inferences that run after they are saved. Stored verdicts are frozen.
Quick reference
| I want to | Do this |
|---|---|
| Approve a specific recurring chain | Structural entry, strict mode, subgraph scope |
| Approve a family of similar chains | Structural entry, shape mode, subgraph scope, then spot-check its breadth |
| Approve a chain whose parent is a phantom | Structural entry, strict mode, Pick nodes — include the phantom and the identified process it spawned |
| Approve a specific command or path | Attribute rule, exact kind |
| Approve anything matching a shape of text | Attribute rule, pattern kind, anchored end to end |
| Approve something on one endpoint only | Attribute rule with only this endpoint ticked |
| Watch a rule before trusting it | Leave observe only on, check its activity after a few days |
| Temporarily stop an entry | Disable rather than delete |
| Confirm what an entry covers | Manage, then Preview |
| Protect the approval set | Download YAML and commit it beside your deployment |
| Know whether an entry will still match tomorrow | Whitelisting examples, Cases 1–10 |
FAQ
Does the detection model still see the whole graph? No. Matched nodes are removed before the model runs; it sees only what is left.
Does whitelisting change the persisted graph?
No. The full graph is persisted with every node, and the removed ones are
flagged with is_whitelisted and listed in whitelisted_node_ids. The
console draws them with the whitelisted legend row.
Can I whitelist a subgraph rather than a whole graph? Yes. Click a node before saving and the entry covers only the connected component containing it.
Can I whitelist a phantom node? Yes. Pick it like any other node. It will only ever match another phantom, and the console refuses an entry where an unidentified phantom is the only process. See Phantom nodes.
Why is there a node called UNKNOWN in my graph?
It is a phantom: a parent process that started before the window opened,
so the sensor never saw its start event and could not record what it was.
On Windows a phantom usually still carries its image, taken from the
child's ParentImage.
Can I bulk-whitelist? Not as a single console action. The practical equivalent is one attribute rule that covers the family, which is safer than many structural entries because it carries the breadth gate and can be watched in observe-only mode first. Entries are permanent once saved, whether created singly or restored from a file.
How do I see hits against each entry?
Every enabled entry in Manage reports its rolling activity over the
last 7 days. Disabled entries show none, because they are not being
evaluated. Per-inference detail is on the inference itself, in
whitelist_matched_ids and whitelist_shadow_ids.
Can I add my own patterns without a source graph? Yes — that is exactly what the Regex tab is for.
Will my entry still match if the activity changes slightly? It depends on the change and the mode. Whitelisting examples measures each one — a node added, removed or reparented, a binary renamed, a command line changed — in shape and strict mode, and with rules; the Summary there has the whole table.
How many entries can I have? The engine loads up to 10,000 enabled entries per refresh. Match cost grows with the number of distinct values rather than the node count, because results are memoised per field and value within a window. Audit periodically all the same: an entry nobody can justify is an entry nobody should trust.
What if an analyst approved something I disagree with?
Disable or delete it from Manage. created_by and reason record who
saved it and why.













