Skip to content

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.

The Inference Results table with the Graph button and the Attacks Only filter highlighted

(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 with the whitelist panel on the right, its tabs, Pick nodes toggle, scope text, Shape/Strict choice and save button highlighted

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: a clicked powershell.exe node, with the panel reading whole group containing it and the save button reading Whitelist this group

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: Pick nodes on, four nodes picked, Strict selected, a reason typed and the save button reading Whitelist 4 selected nodes

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.

Approved:    cmd -> whoami + hostname
Later run:   cmd -> quser + hostname                    matches

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.

The panel confirming the save with Whitelisted 1 component

(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 with the node type and field, Exact or Pattern, rule text, live measurement, narrowing checkboxes and save button highlighted

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:

<hostile payload>; Test-NetConnection -ComputerName 10.0.0.1

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 .*:

.*winlogbeat\.yml.*

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 pattern .*.exe.* refused by the breadth gate at 77 percent of sampled nodes, with the save button disabled

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 UNKNOWN reading /etc/shadow would 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 UNKNOWN phantom, shadow, sh and debian-sa1 picked with Strict in the console

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

Only UNKNOWN and shadow picked; the console warns and the save is refused

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 cron starts inside the window next time, the graph has a real cron node 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/shadow read 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.

The Inference Investigation panel showing 1 node excluded, the Graph button, and a Whitelist held back notice for 3 matches

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 graph of the same inference: reg.exe ringed green as excluded, the Show excluded nodes button and the whitelisted legend row highlighted

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 — shape or strict for structural entries, exact or pattern for rules.
  • What it covers — graph or subgraph for structural entries with a node and edge count; node_type.field for 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 with the backup bar, a strict entry card, its Preview, Enabled and delete controls, and its 7-day activity line highlighted

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 card showing EXACT process.command_line, the Enforcing toggle, the rule text and its 7-day activity

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.

The Preview of a strict entry: its reason in the header and a Coverage panel listing nodes covered, edges, endpoint, source inference and creator

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:

  1. Refresh delay. Entries take up to 60 seconds to load.
  2. The rule is observe-only. New rules default to observing, and an observing rule removes nothing. Check the entry in Manage.
  3. The subtree guard withheld it. Check whitelist_withheld_nodes on 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.
  4. You approved a different subgraph. A window often holds several disconnected components. Open Preview on the entry and confirm it covers what you meant.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.