Alerts and cases¶
An alert is one detection result: a rule matched one stored event, or, for a threshold rule, enough events. A case is the unit you investigate. FreeDR groups related alerts into cases automatically, and you can also build cases by hand. This page explains:
- how alerts are created;
- how you triage alerts;
- how alerts become cases and which alerts are grouped together;
- what a case contains;
- how cases are resolved and closed;
- notifications;
- what FreeDR does and does not delete.
For how rules run, see Rules and detection.
How alerts are created¶
When a rule run succeeds, FreeDR turns each returned row into an alert candidate:
- Event match rules: one alert per returned row.
- Threshold rules: a single alert when at least
min_countrows were returned. It is anchored to the newest returned event and carries the count as{{match_count}}.
| Alert field | Source |
|---|---|
alert_id |
Derived deterministically: SHA-256 over the rule ID, rule version and triggering event ID |
rule_id, rule_name |
The rule |
severity, confidence |
Copied from the rule (see severities) |
match_time |
The triggering event's occurred_at |
description |
The rule's alert_description rendered with the row's values (plain text, at most 4 KiB) |
evidence_event_id |
The triggering event |
muted |
Set if an active mute covered the match when the alert was first stored |
Deduplication¶
An alert's identity is the triple (rule ID, rule version, event ID). This has three consequences:
- The same rule version never alerts twice on the same event. If a window is evaluated again (after a failed commit, a retry or a restart), the identical alert is recognised as an exact duplicate and not stored again.
- A new version of a rule is a new identity. After a rule changes version, it looks back over its
timeframeonce, so it can alert again on recent events that the previous version already alerted on. - FreeDR does not merge alerts by content. Separate events produce separate alerts, even when their descriptions are identical. The alert list can collapse such repeats for display (see Alert list).
The run's alerts, its run-history row and the rule's cursor are committed in one transaction. Alerts are stored in FreeDR's control store, not in the evidence store.
Alert list and grouping¶
The Alerts page and GET /api/v1/detections/alerts list alerts newest first, in pages of 1 to 100. You can filter by:
- rule;
- free text (rule name and description);
- case;
- severity;
- confidence;
- assigned or unassigned to a case;
- triage state and outcome;
- match-time range;
- "urgent unresolved": critical or high, not muted, not resolved.
The grouped view (GET /api/v1/detections/alert-groups) collapses alerts that have the same rule and the same rendered description into one row. Each row shows:
- the count, and how many are unresolved (neither resolved nor muted);
- first and last seen;
- the most severe member.
Grouping covers only the newest 5,000 matching alerts and says so when older ones exist. Expanding a group (GET /api/v1/detections/alert-groups/{group_id}) returns up to 2,000 members for whole-group triage. Grouping is display only; every alert is still stored separately.
For one alert you can open:
- its detail, which shows the case it belongs to;
- a timeline of surrounding events (
GET …/alerts/{alert_id}/timeline?minutes=1|5|15|60, up to 500 events); - the exclusion builder;
- an on-demand AI explanation (if AI is configured). The explanation is advisory text only.
Alert triage¶
Triage is tracked per alert and is separate from case status.
| State | Outcome |
|---|---|
new (default) |
none |
investigating |
none |
resolved |
required: true_positive, expected_activity or false_positive |
Rules for setting triage:
- An outcome is required for
resolved, and not allowed for the other states. - Any state can be set from any other. Every change is appended to the alert's triage history. Setting the state an alert already has writes nothing.
- One alert:
PATCH /api/v1/detections/alerts/{alert_id}/triagewith{"schema_version":1,"csrf_token":"…","state":"resolved","outcome":"false_positive"}. - Many alerts at once:
POST /api/v1/detections/alerts/triagewithalert_ids(up to 2,000) and one state and outcome. This is applied atomically: one unknown ID fails the whole request. The console's bulk bar offers Review, Resolve and Add to case. - After you save a user exclusion, the console offers to resolve the alerts it covers as false positive in one step.
Alert triage changes the "unresolved" counts, the urgent filter on Overview and the tray icon state. Changing an alert's triage does not change its case, and changing a case's status does not change its alerts' triage. The one exception is a resolution by the AI investigator, described in Resolution by the AI investigator.
How alerts become cases¶
Which alerts enter case automation¶
A background worker assigns new alerts to cases:
- It wakes whenever a run commits new alerts, and on service start it first drains anything left over.
- It processes unassigned alerts in match-time order, 16 at a time.
- If it hits an error it retries after 5 seconds. Its status (
processed,backlog,last_error) is reported undercase_automationin detection health.
An alert is not placed in a case automatically if any of these is true:
- its severity is
informational; - it is
muted; - you removed it from a case earlier. Removal marks it as deferred, so automation does not put it back.
Process identity of an alert¶
To decide which case an alert belongs to, FreeDR first resolves the process identity of its triggering event from retained evidence:
-
Find the trigger and its acting process ID. FreeDR finds the triggering event and takes its acting PID:
pidfor process and file events;actor_pidfor registry events;- for Sysmon Event Log events, the payload
ProcessId(SourceProcessIdfor ProcessAccess).
Artifact and USN events have no acting process. If the trigger is no longer retained, or is ambiguous, the alert has no identity and gets a case of its own.
-
Verify the process start. FreeDR looks for the latest real (non-rundown) process start of that PID at or before the event, with no later start and no exit of that PID in between. Only such a verified start counts as the process identity. A bare PID is never treated as an identity, because PIDs are reused.
-
Verify the parent. The parent is the verified start of the parent PID, but only if the parent started no more than 30 minutes before the child.
-
Fall back to a writer known only from the startup snapshot. If the acting process was already running when FreeDR started, it has only a startup snapshot ("rundown") record. If that PID has not exited or restarted since, and there was no later snapshot or OS boot, the process is used as an identity for one purpose only: grouping same-rule alerts within ±60 seconds of each other (for example a burst of scheduled-task writes from one
svchost.exe).
Grouping logic¶
An alert is related to an existing case when the case already contains an alert that:
| Relationship (as recorded) | Condition |
|---|---|
same_retained_event |
has the same triggering event |
same_process_instance |
came from the same verified process start |
verified_parent_child |
came from a direct child of this alert's process, or from this alert's verified parent |
same_rule_writer_burst |
is from the same rule and the same snapshot-only writer, within ±60 seconds |
Anything else does not relate two alerts. That includes:
- the same rule name;
- the same PID;
- the same image name, path or hash;
- a common parent (siblings are not grouped);
- grandparents;
- shared network destinations;
- closeness in time alone.
The decision¶
Joining an existing case. The alert joins the related case or cases when all of these hold:
- there is at least one related case, and at most 64;
- every related case is
openorin_progress, has no outcome recorded, and is not correlation-locked; - no related case has an active response job;
- you have not manually separated any two of the related cases;
- the related cases do not disagree on assessment (at most one assessment other than
unknown).
If several cases qualify, FreeDR merges them into the one with the lowest case number, and the alert joins it.
Reopening a closed malicious case. If exactly one related case is assessed malicious, is resolved or closed, is not locked, and the new activity happened after it was closed, the case is reopened. It returns to open, and its outcome, conclusion and closure reason are cleared. The alert joins it, and a "reopen" finding records why.
Otherwise, a new case. The alert gets a new case:
- title: the rule name (first 100 characters);
- description: "Automatically created from an admitted alert";
- severity: the alert's severity;
- status:
open.
If related cases existed but could not be used, FreeDR keeps a suggestion linking the new case to each of them. The suggestion names the blocker:
- manual separation;
- an active response job;
- more than 64 candidates;
- incompatible assessments;
- a correlation lock;
- a closed, resolved or concluded case.
Every decision is stored with its reason and the evidence it relied on. The decision is singleton, assign, merge or suggestion. You can read the decisions in the case (GET /api/v1/cases/{case_id}/correlations) and in the case audit.
When FreeDR assigns an alert to a case, it also attaches these retained events to the case:
- the triggering event;
- the verified process start, parent start or snapshot-only writer;
- the events behind the relationship.
Manual case work¶
| Action | How |
|---|---|
| Create a case | POST /api/v1/cases with a title (up to 256 bytes), description (up to 4,096 bytes), severity 1 to 4 and optional alert_ids. An alert can belong to only one case; the request fails with the existing case number if an alert is already assigned. |
| Add an alert | POST /api/v1/cases/{case_id}/alerts/{alert_id}. Same one-case rule. |
| Remove an alert | DELETE …/alerts/{alert_id}. Also marks the alert deferred, so automation leaves it unassigned. |
| Attach or remove an event | POST or DELETE …/events/{event_id} |
| Pin an alert or event | POST or DELETE …/alerts/{alert_id}/pin or …/events/{event_id}/pin |
| Merge | POST …/merge with absorbed_case_id. The absorbed case becomes merged and points to the survivor. |
| Split | POST …/split: move selected alerts and events (up to 500 each) into a new case. This also records a manual separation, so automation will not join the two cases again. |
| Correlation lock | PUT …/correlation-lock stops automation from adding alerts to, or merging, this case. |
These mutations need the CSRF token, and most need the case's current expected_updated_at_us, so two concurrent edits cannot overwrite each other. A case with an active response job cannot be merged or split.
Case fields¶
Severity¶
Case severity uses codes 1 (critical) to 4 (low). Informational alerts never reach cases.
- An automatically created case starts with its alert's severity.
- When an alert joins a case, the case takes the more severe of the two.
- If the case is assessed
maliciouswhen an alert joins, it becomes critical. - You can edit the severity by hand while the case is not closed or merged.
Status¶
| Status | Meaning |
|---|---|
open |
New or reopened |
in_progress |
Being worked on. Also set when an AI investigation finishes without resolving the case. |
resolved |
Work finished. The case can still be edited and reopened. |
closed |
Final. Read-only until reopened. |
merged |
Absorbed into another case. Read-only. |
Allowed status changes:
opentoin_progressopentoresolvedin_progresstoresolvedresolvedtoclosed
Use PATCH /api/v1/cases/{case_id} with status. Moving to resolved requires an outcome and conclusion recorded first (PUT /api/v1/cases/{case_id}/conclusion with outcome, conclusion and closure_reason); otherwise the request fails with 400 invalid case request. The console enforces the same order. Reopen (POST …/reopen with a reason) takes a resolved or closed case back to open and clears its outcome, conclusion and closure reason.
Assessment¶
Assessment is separate from status: it records what the activity is.
- Values:
unknown,suspicious,maliciousorbenign. - It is set by you (
PUT …/assessment, with a reason and optional cited events) or by the AI investigator. - It records its provenance:
humanorai:<run_id>. - Assessment affects grouping (see The decision) and severity.
Conclusion¶
PUT /api/v1/cases/{case_id}/conclusion records:
| Field | Values |
|---|---|
outcome |
true_positive, expected_activity, false_positive or inconclusive |
conclusion |
Text, non-empty, at most 4,096 bytes |
closure_reason |
user_activity, admin_qa, benign_software, threat or other |
A conclusion is also written as a finding with human provenance.
The console requires an outcome and a conclusion before it enables Resolve or Close. The API status change does not enforce this.
Findings, notes, activity and audit¶
- Findings: hypothesis, finding, assessment, conclusion and reopen entries. Each cites events and records its provenance.
- Notes: up to 16 KiB each.
- Activity view: a single feed of the case's history.
- Audit: every change is in an append-only audit, including:
- create, update and status changes;
- alerts assigned and removed;
- merge, split and reopen;
- assessment and findings;
- correlation lock;
- automatic correlation;
- response jobs.
Resolving cases¶
Who can resolve a case¶
- You, at any time, through the status change above.
- The AI investigator, in two situations only. See below.
FreeDR does not close or resolve cases on its own based on time.
Automatic AI investigation¶
The AI investigator starts automatically only when AI is configured. A case is investigated automatically if:
- it is
openorin_progress; - its severity is critical, high or medium;
- it contains alerts;
- it has not already been investigated with the same evidence.
Low-severity cases are investigated only when you start a run (POST /api/v1/cases/{case_id}/ai). Never-investigated cases are taken first.
Resolution by the AI investigator¶
When a completed AI run is published, and you have not changed the case while it ran:
| AI verdict | Effect |
|---|---|
benign |
The case becomes resolved with outcome expected_activity and the conclusion "AI investigation found this activity benign: expected_activity. |
suspicious or malicious, with containment |
If AI containment is enabled ("Let AI contain threats it finds", off by default) and every containment action the AI submitted is verified as completed, the case becomes resolved with outcome true_positive and closure reason threat. Every alert in the case not already resolved becomes resolved / true_positive. |
| anything else | An open case becomes in_progress. Alert triage is unchanged, so the alerts stay visible as unresolved. If containment was attempted but not completed, a finding explains why the case was left to you. |
Two protections apply:
- An alert you already resolved keeps your verdict, and so does an alert whose triage changed while the run was going.
- The verdict must cite retained evidence. The conclusion is recorded with the
ai:<run_id>provenance, so AI resolutions can be told apart from yours.
Warning
A benign AI verdict resolves real alerts too. Telemetry crafted to mislead the model (for example, instructions hidden in a command line) could cause a wrong benign verdict. Resolved alerts stay listed under Resolved and in the case history.
The AI investigator never approves exclusions. It can only propose them.
Enrichment and investigation context¶
Automatic enrichment¶
Every case event that comes from an alert trigger is enriched in the same way, whatever the rule. The evidence decides which kinds apply:
| Kind | Applies to |
|---|---|
| File hash | The implicated file. Read locally through FreeDR's artifact collection. |
| File signature | The implicated file's Authenticode signer. Packaged Store/MSIX apps are checked through their package signature. A valid signature does not make a file benign. |
| Hash reputation | The file hash, looked up with the reputation providers you enabled |
| IP reputation | An evidenced public destination address |
| Domain reputation | An evidenced public domain |
A file kind can target up to four files:
- the event's own file;
- the file named by a persisted value: registry Run or service data, a Task Scheduler action or command, or the
ImagePathof a new service; - the image of the acting process;
- in each case only when the path can be resolved safely. Per-user variables, relative paths, UNC paths and similar give no target.
Several events that name the same file share one lookup.
Providers:
- The online reputation providers are CIRCL, MalwareBazaar, ThreatFox, URLhaus, AbuseIPDB, VirusTotal, AlienVault OTX and GreyNoise.
- Each provider is used only after you enable it in Settings. Credentials are write-only and quotas apply.
You can also request enrichment by hand (POST /api/v1/cases/{case_id}/enrichment) and cancel a pending request.
For providers, eligible indicators, quotas, caching, privacy and troubleshooting, see External enrichment (threat intel).
Lineage and case summary¶
-
Case summary. Each case has a deterministic summary (
GET …/summary) built from retained evidence. Per alert group it shows:- the trigger and all its named fields;
- the acting process with its chain, account, lifetime and exit;
- what that process did near the trigger;
- its parent's other children;
- other events that share a value with the trigger;
- rare events nearby;
- what could not be resolved.
The summary also shows enrichment and response state, and the case's pending AI exclusion proposals. - Process tree.
GET /api/v1/processes/tree?pid=&at=shows the process, its ancestors and its children and grandchildren, with hashes, signatures, host prevalence and the alerts each node produced. Ambiguous PID reuse is never guessed. - HTML report.GET …/report.htmldownloads a self-contained report with: - the case record and summary; - alerts and rules; - enrichment; - AI verdicts, labelled as AI; - response jobs, notes, findings and the audit trail.
Notifications¶
Partial
The tray agent and notifications are implemented. Native qualification on Windows 10 and 11 is still pending, including balloon rendering, deep links, session changes and RDP-only or locked sessions.
FreeDR keeps a small tray agent, freedr-tray.exe, running in the signed-in console user's session. The service starts it with that user's own token and restarts it if it exits unexpectedly. The tray menu's Exit stops it until the next sign-in or session change.
- Alert balloons. These are controlled by
GET/PUT /api/v1/settings/alert-notifications(enabled,minimum_severity= critical, high, medium or low). They are off by default; once enabled, the default minimum severity is high.- A balloon is raised when a rule run stores new alerts at or above the minimum severity.
- Muted and informational alerts are never announced.
- At most one balloon is shown per 5 seconds. Alerts that arrive in between are collapsed into an "N new alerts" balloon.
- A balloon shows the severity and the rule name, never the alert description.
- Clicking a single-alert balloon opens the console at that alert, or at its case if it has one.
- Icon state. In priority order:
- isolated;
- possibly isolated;
- unresolved medium, high or critical alerts (low, informational and muted alerts never change the icon);
- degraded component health;
- OK.
- Health balloons. Component degraded or recovered, Event Log channel closed, and host isolation started or released. Each kind is shown at most once per 10 minutes. These are independent of the alert preference.
The tray has no API credential and cannot change anything. Opening the console still requires your password.
Deletion and retention¶
- There is no API or console action that deletes an alert or a case. You resolve, close, merge or split cases instead, and you triage alerts.
- Control records expire with the evidence retention period (
retention_days, 90 days by default):- A case is deleted only when it is
closedormerged, was closed before the cutoff, all its alerts are older than the cutoff, and no retained case refers to it (as a merge target, a related case or a note source). open,in_progressandresolvedcases are never deleted.- An alert is deleted when it is older than the cutoff and does not belong to, and is not referred to by, a retained case.
- A case is deleted only when it is
- When a case expires, its related records go with it: links, notes, findings, audit, AI runs, response jobs and enrichment requests.
- The retained evidence events behind an alert follow the evidence retention policy on their own. A case keeps a reference to an event even after the event itself is no longer retained. Some views then report that the event is no longer available.