Skip to content

Matching modes and confidence

Matching modes and confidence

Every active indicator in Mimir is checked against your fleet through three independent paths: a real-time watchlist that sweeps every connected agent on a steady cadence, a retroactive historical scan that fires once when the indicator is first added, and hunts an operator launches on demand. This page explains what each path does and why feed-imported indicators come with a confidence score that decays over time.

The three matching paths

Real-time watchlist

A leader-elected worker on the Mimir server compiles every active IOC into osquery SQL and dispatches it to every connected agent once every 15 minutes. The agent runs the query, returns matches, and Mimir creates an alert tagged watchlist_pack for each result.

What the watchlist sees depends on the indicator family:

  • File hashes / filenames / paths. A query against osquery’s file_events + hash join, scoped to events in the last 20 minutes (time > now - 1200s).
  • Registry keys (Windows). Querying the registry table for the indicator’s path. Pure-snapshot, not evented.
  • IPv4 + domain. Two queries per tick — a snapshot against process_open_sockets / current DNS state, plus an evented query against socket_events / dns_lookup_events to catch connections that opened and closed inside the tick window.

The watchlist sees activity going forward: every new file event, DNS lookup, or socket open is checked against the active IOC set once per tick. An indicator added at 09:01 starts being checked on the next tick (within 15 minutes).

Retroactive historical scan

When you add a new indicator, Mimir kicks off a one-shot scan of the last 90 days of stored query results, looking for any host that already saw the indicator. The window is set by MaxHistoricalWindow and capped at 500 distinct hosts per scan to keep the database load bounded.

Any match found is alerted with the source historical_match. The evidence row includes the original timestamp the activity was recorded — so an indicator you added today can produce an alert that says “this host ran the binary 47 days ago.” This is the most-loved feature for incident response: a vendor publishes a fresh hash and you can immediately answer “did any of our hosts ever see this?”

The scan runs asynchronously in a goroutine. For a single hand-added indicator it usually completes within seconds. For a large bulk import the scan can take up to 30 minutes — the budget is matchHistoricalBulkTimeout = 30m precisely because the 90-day window scans far more rows than the older 7-day cutoff did.

Hunts (on-demand)

A hunt is an operator-driven sweep: “don’t wait 15 minutes, ask every host right now.” The compiler takes the same IOC set, builds the same osquery SQL, and dispatches it directly to every targeted agent, then streams the results back into a hunt detail view. Hunt matches are alerted with the source hunt.

Hunts are the only path you control directly. The other two run automatically.

How feed-imported confidence works

Every indicator has a confidence score from 0 to 100. Hand-added indicators default to 100 — you said it’s bad, Mimir trusts you. Feed-imported indicators default to 70 — the source is reputable but not authoritative for your environment, so the score leaves room to deactivate noisy entries automatically.

A leader-elected worker runs once a day to decay feed-imported confidence by 5 points per day. Any feed IOC whose post-decay score drops below 20 is automatically deactivated (kept in the database, not matched against agent activity). New IOCs get a 7-day grace period before the first decay tick.

The two thresholds are pinned constants in internal/server/ioc_decay.go:

const ConfidenceDecayPerDay = 5
const ConfidenceDeactivateBelow = 20

A worked example. A URLhaus indicator imported on day 0 starts at confidence 70. With no further activity it tracks like this:

DayConfidenceActive?
070yes (newly imported)
770yes (still in grace period)
865yes (first decay tick after grace)
1720yes (one decay step from threshold)
1815no — auto-deactivated

A successful re-poll of the source feed that re-emits the same indicator value boosts the confidence back up (and re-activates the IOC if it had been auto-deactivated). So an indicator abuse.ch keeps emitting stays hot indefinitely; one they drop goes inactive on its own about 10 days later.

The Confidence column on the indicators table is colored by band: green over 70, yellow 31–70, red 30 and below. The narrow bar visualizes the current score so you can scan the page for indicators that are about to age out.

Why hand-added indicators don’t decay

Decay only runs against feed-imported IOCs (source_id IS NOT NULL and not the magic manual-source UUID). Hand-added IOCs stay at confidence 100 forever unless you change them. The intuition: an analyst typing a hash into the form is making a deliberate decision that Mimir shouldn’t second-guess. A feed publishing 200,000 hashes is statistical, and statistics deserve decay.

Activating and deactivating manually

Every indicator row has an Active / Inactive button in the last column. Clicking it toggles the row.

  • Active → Inactive. Stops matching immediately. The watchlist’s next tick (within 15 minutes) drops the IOC from its query set. The indicator stays in the database — you can reactivate later, and feed re-polls won’t override your decision (the deactivation is preserved).
  • Inactive → Active. Resumes matching. No retroactive 90-day scan is re-run on reactivation; if you want one, delete and re-create the indicator.

Use Inactive as the right answer for “this feed indicator is producing noise in my environment.” Don’t delete the row — keep it inactive so a future feed poll won’t silently re-import the same value and start matching again.

Source attribution and the filter chip

Every alert carries its source tag (watchlist_pack, historical_match, hunt, or campaign) so you can tell which path produced it. The Alerts page exposes the first three as filter chips — useful for “show me only the late matches from this morning’s bulk import” or “show me only the alerts my last hunt produced.” Campaign-sourced alerts (from offline-host responses arriving after the live hunt closed) are reachable from the campaign detail page rather than via a toolbar chip.

Troubleshooting

An indicator I added is showing confidence 100 but never matching. The watchlist runs every 15 minutes against connected agents only. If the only host that has the artifact hasn’t checked in since you added the indicator, you won’t see a match yet. Open Hosts to confirm the agent is online; if it isn’t, the indicator catches it either on reconnect (via the campaign path, when offline_campaigns_enabled is on — see Campaigns) or when you run a hunt once it’s back.

Feed indicators keep auto-deactivating before I get to investigate them. That means the feed isn’t re-emitting the values on subsequent polls — the upstream source has dropped them, which is itself information (“the campaign aged out”). If you genuinely care about preserving feed indicators past their natural lifetime, copy the value into a manual indicator; hand-added IOCs don’t decay.

Historical scan finished but I don’t see late matches I expected. Two checks: confirm the indicator’s type matches what the host actually has (a SHA256 indicator can’t match a host that only logged the filename — see Indicator types), and confirm the activity was inside the 90-day window (MaxHistoricalWindow). Older activity is not in the scan range.

Where to next