Skip to content

Exclusions and mutes

FreeDR has three ways to stop a rule from alerting on activity you know is expected:

Mechanism Scope Lifetime Effect on matching events
Built-in exclusion One rule, part of its definition Versioned with the rule No alert
User exclusion One rule, your local state Until you remove it No alert
Mute One rule, or one rule plus one entity 24 hours or 7 days Alert stored but marked muted: no case, no AI run, no notification

All three apply to one rule only. FreeDR has no global allowlist.

Exclusions and mutes change only alerting. FreeDR still collects and stores the events, and you can still find them in search and Hunt.

How an exclusion is applied

An exclusion is a SQL boolean expression (a predicate) over the columns of the rule's source table. Before each run, the engine adds every built-in exclusion and every active user exclusion to the rule's candidate rows:

… AND ((<predicate>) IS NOT TRUE)

So a row is suppressed only when a predicate is definitely true:

  • A predicate that evaluates to NULL does not suppress the row. For example, a test on a column that is unknown for this event does not hide it.
  • Unknown lineage (a NULL parent_image_path or actor_image_path) therefore fails toward alerting.

Exclusions are applied before the rule's LIMIT, and they apply in rule tests and exclusion previews as well. A predicate may read the same relations as the rule query, each bounded by the rule's timeframe:

  • <source>_context;
  • other typed sources;
  • artifact_path_state.

When FreeDR validates a predicate, it wraps it as SELECT … FROM <source> AS typed WHERE (<predicate>), using the same checks as a rule query. Because suppressed rows are removed from the candidate relation, a subquery on the source name inside the rule cannot see them. Use <source>_context if the rule needs the unfiltered rows.

Built-in exclusions

Built-in exclusions are the builtin_exclusions list in a rule document. Each item has a predicate (at most 512 bytes) and a reason (at most 256 bytes), and a rule may have at most 16.

  • In the shipped pack, 25 rules carry 38 built-in exclusions in total. Most of them exclude stock Windows or Microsoft servicing behaviour.
  • The catalog shows the number per rule. The rule detail view in the console shows the predicates and reasons.
  • Built-in exclusions of shipped rules cannot be changed or removed. They change only with a new rule version.
  • In a custom rule, you edit them in the rule editor (Add built-in exclusion), and they are versioned with the rule.

Example from the shipped pack:

builtin_exclusions:
- predicate: |-
    value_type IN (1, 2) AND regexp_full_match(lower(…value_data…), '(wintrust|mso)\.dll|c:\\windows\\(system32|syswow64)\\(…)\.dll')
  reason: 'Stock provider DLL values rewritten by Windows servicing, regsvr32 wintrust.dll repair scripts and MSI Office (mso.dll); they cannot load attacker code (Elastic and Sigma 5a2b21ee filters).'

User exclusions

User exclusions are your local per-rule exceptions. They work for built-in and custom rules alike, and they are stored separately from the rule definition, so a built-in rule stays unchanged. The console calls them "user exceptions".

Limits

Limit Value
Predicate Non-empty, at most 1,024 bytes. It must validate against the rule's source and timeframe exactly like a built-in exclusion.
Reason Required (not blank), at most 4,096 bytes
Active exclusions per rule 16
Pending (AI-proposed) exclusions per rule 16, counted separately
Duplicates A rule cannot hold the same predicate twice, whether pending or active

Building an exclusion from an alert

The usual way to create an exclusion is from a false-positive alert. Open the alert and choose the exclusion option. FreeDR reads the event that triggered the alert and offers its stable fields as an exclusion predicate (POST /api/v1/detections/alerts/{alert_id}/exclusion-preview):

Triggering event Fields offered (selected by default: ✓)
Process start Image path ✓, Parent image path ✓, Command line (✓ when the rule's SQL reads the command line)
Image load Loaded image path ✓, Loading process image ✓
Registry Registry key ✓, Writer image path ✓, Value name
File File path ✓, Writer image path ✓
USN / Artifact File path ✓
Event Log Named fields image, parentimage, sourceimage, targetimage, targetobject, targetfilename, imageloaded ✓; commandline (✓ as above); parentcommandline

How the predicate is built:

  • The trigger's event type (event_type = N or event_type_id = N) is always part of the predicate.
  • Every value is compared exactly. Nothing matches by name pattern.
  • A path is offered only when it is a full path (a directory and a file name). A bare file name is never used.
  • A parent or writer image compares the stamped lineage column (parent_image_path or actor_image_path).
  • Values longer than 160 bytes, or containing control characters, are compared by length and SHA-256: strlen(column) = N AND sha256(column) = '…'.
  • Event Log fields compare the whole stored named-field entry with list_contains(text_list_01, …).
  • You must select at least one field, and the combined predicate must fit in 1,024 bytes. If it does not, deselect a field.
  • If the triggering event is no longer retained, no exclusion can be built from it.

Preview

The Preview button ("preview": true) counts what the exclusion would have suppressed. It has a 10-second budget and writes nothing. It reports:

Field Meaning
window_start_us, window_end_us The last 7 days, over which the rule is evaluated
matches_without, matches_with Rule matches in that window without and with the candidate predicate. Current active exclusions are applied in both.
query_limit, hit_query_limit The 1,000-row rule limit, and whether matches_without reached it
alerts_checked, alerts_checked_limit, alerts_matched How many of the rule's 2,000 most recent stored alerts were checked, and how many have a retained trigger the predicate matches
unresolved_alert_ids The covered alerts that are not yet resolved
timed_out The budget ran out. Counts it did not reach are null.

You can also choose Ask AI to draft (if AI is configured). The AI picks among the same offered fields and returns a draft. Nothing is saved until you preview and save it yourself.

Saving

Saving calls POST /api/v1/detections/rules/{rule_id}/user-exclusions with:

{"schema_version": 1, "csrf_token": "…", "predicate": "…", "reason": "…", "alert_id": "<optional originating alert>"}
  • The exclusion is active immediately and applies from the rule's next run.
  • After saving, the console offers to resolve the covered unresolved alerts as false positive in one bulk triage step.

Other operations:

  • List a rule's exclusions, pending and active, with their state and provenance: GET …/rules/{rule_id}/user-exclusions.
  • Remove an active exclusion: DELETE …/rules/{rule_id}/user-exclusions/{exclusion_id} (body: schema_version, csrf_token).
  • You can also type a predicate by hand in the rule's user exceptions. It is validated the same way.

AI-proposed exclusions (pending approval)

When the AI investigator decides that an alert in a case is a false positive, it can file an exclusion for that alert's rule. Such a proposal is pending:

  • It is validated like any user exclusion.
  • It is stored with the case, the alert and the preview counts it was filed with (matches_without, matches_with, alerts_matched).
  • It suppresses nothing.
  • Detection, rule tests and previews use active exclusions only.

Only you can activate a proposal. The AI has no path to approve one.

Action How
See pending proposals The Overview page (count badge with Approve / Reject and links to the alert and case), the rule's user exceptions (marked "pending (AI)"), and the case summary tab. API: GET /api/v1/detections/pending-exclusions[?case_id=<uuid>], newest first, at most 200.
Approve POST /api/v1/detections/rules/{rule_id}/user-exclusions/{exclusion_id}/approve. The predicate is validated again against the rule as it is installed now. Approval fails if the predicate no longer validates or the rule already has 16 active exclusions.
Reject POST …/user-exclusions/{exclusion_id}/reject. Deletes the proposal.

If you add, by hand, a predicate that the AI already proposed, that proposal is approved.

Mutes

A mute snoozes a rule without hiding its matches:

  • Duration: exactly 24h or 7d. A mute is active until it expires, and no job is needed to end it.
  • Scope: either the whole rule (no predicate), or the rule plus one entity. An entity predicate is built from the alert's trigger fields exactly like an exclusion and is validated the same way.
  • Limit: 16 active mutes per rule.

While a mute is active, matching alerts are still stored with their normal severity and identity, but marked muted. A muted alert:

  • never joins a case, so it never triggers an AI investigation;
  • never produces a tray notification;
  • is not counted as urgent;
  • does not change the tray icon state.

A muted alert is listed with a muted marker. The first time an alert is stored decides whether it is muted. A later re-match of the same alert after the mute starts or ends does not change it.

Action API
Mute POST /api/v1/detections/rules/{rule_id}/mutes with {"schema_version":1,"csrf_token":"…","duration":"24h"\|"7d","predicate":"…optional…","alert_id":"…optional…"}
End early DELETE /api/v1/detections/rules/{rule_id}/mutes/{mute_id}
List active mutes GET /api/v1/detections/mutes[?rule_id=]. Each mute has its expiry. The Alerts page lists active mutes with the time left and an Unmute button.

Mutes are local operator state. They are not a tamper protection.

Audit trail

Every change to a rule's exclusions and mutes writes one row to an append-only tuning audit:

Action Written when
exclusion_added You add a user exclusion
exclusion_removed You remove one, or a custom-rule edit changed the source (detail "rule source changed")
exclusion_proposed The AI files a pending exclusion
exclusion_approved You approve a pending exclusion, or add a predicate the AI had proposed
exclusion_rejected You reject a pending exclusion, or a source change removed it
muted A mute is created (detail 24h or 7d)
unmuted A mute is ended early (detail "ended early"), or removed by a source change

Each row records:

  • the predicate (empty for a whole-rule mute);
  • the reason or detail;
  • the originating alert, when there is one;
  • the time.

To read the audit, open the rule's tuning history in the console or call GET /api/v1/detections/rules/{rule_id}/tuning-audit (the newest 100 entries).