Reading a campaign
Reading a campaign
Open any row on the Campaigns page to land on its detail view. The page is built for one question: “what did this campaign actually catch, and what’s still outstanding?” Coverage, pending hosts, late results, and the frozen payload are stacked in that order — fastest path from “is this done?” to “what did it find?” to “what was it looking for?”
The detail view branches on kind: ioc_hunt campaigns show
Late Matches and Frozen IOCs; osquery campaigns show Late
Responses and Frozen SQL. The Coverage card, Pending Hosts card,
Stop button, and header strip are shared.
What you get
A header strip plus four cards.
Header strip
- Back link to the campaigns list.
- Name, kind badge (
ioc_huntblue,osquerygreen), and status (active/stopped/archived), status dot color-coded. - Live hunt: view → link to the originating
hunt, if the campaign is an
ioc_huntand the hunt is still around. Absent forosquerycampaigns (they don’t have a paired hunt row). - Created timestamp; if stopped, the stop timestamp and reason string (if the stopper provided one).
- Stop Campaign button (admin only, active status only).
- Warning badges — when the campaign has tripped a guardrail, an amber or red badge surfaces it inline. See Warning badges and banners below.
Coverage card
Three big numbers in a row:
- Responded — green, count of assignments that have returned results (whether or not they matched anything).
- Pending — blue, count of assignments still waiting. A non-zero pending number means there are hosts the campaign expected to hear from but hasn’t.
- Late results — red. For
ioc_huntthis is Late Matches: matches that came in after the live hunt closed. Forosqueryit’s Late Responses: hosts that answered after the live window closed. Either way it’s the load-bearing number for offline campaigns: every late result is a host the live-only path would have missed.
Beneath the three numbers, a one-line summary. For ioc_hunt:
N assigned hosts · M matched · K query types (the query-type
count reflects how many distinct osquery queries the IOC
compiler produced — large IOC sets can become several types each:
file_hash_sha256, filename, ipv4 snapshot, ipv4 evented, etc.).
For osquery: N assigned hosts · M with rows · 1 query (one
query per osquery campaign — the literal SQL it was launched
with).
Pending hosts card
A table of every host with at least one pending assignment.
Two columns: hostname (linked to the
host detail page) and pending-job
count. When this card is empty (No pending hosts — all assignments complete), the campaign is fully drained and
ready to archive.
Late matches card (ioc_hunt only)
Every match that arrived after the live hunt window closed. One row per match:
- IOC Type — badge showing the matching indicator’s type (SHA256, Filename, IPv4, etc.).
- IOC Value — the matched indicator. Truncated to first 20
- last 8 characters for hashes; click through to the host for full evidence.
- Host — linked first 8 characters of the host UUID.
- Reported At —
agent_reported_atif the agent’s timestamp was preserved, otherwise the server’schecked_at.
Each late-match row also produces a regular alert on the
Alerts page with source='campaign', so you
don’t need to refresh this page to spot them. The toolbar’s source
chips (Watchlist, Historical, Hunt) don’t currently expose a
Campaign chip, but the alerts are visible in unfiltered views and
the late-matches table on this page is the per-campaign roll-up.
Late responses card (osquery only)
Every host that answered after the live window closed. One row per host:
- Host — linked first 8 characters of the host UUID.
- Rows returned — count of osquery rows the host produced.
- Responded at —
agent_reported_atif preserved, otherwise the server’schecked_at. - View rows — opens an inline drawer fetching the host’s rows for this campaign’s query, filtered by host id. The drawer reuses the same row viewer the Queries page uses.
- Rows truncated badge — appears next to the row count when this host produced more rows than the per-host cap allows. The rows you have are good; there were more on that host.
Osquery campaigns don’t produce alerts — there’s no IOC snapshot
to match against. The data lives entirely in this card and the
drill-into-rows drawer, plus the raw query_results rows
queryable from the campaign’s query_id via the standard
Export buttons.
Frozen IOCs card (ioc_hunt only)
The complete IOC set the campaign was launched with, with each indicator’s type, value, and severity. This list is frozen — deactivating an IOC on the Indicators page does not remove it from a campaign that’s already in flight. A campaign launched with a noisy indicator stays noisy until it’s stopped.
If you find yourself looking at a frozen-IOCs list and thinking “these are wrong,” the right move is Stop Campaign and re-launch the hunt with a cleaner indicator set.
Frozen SQL card (osquery only)
The literal osquery statement the campaign was launched with,
shown in a monospace block with a Copy button. If the
campaign was launched from a saved query, the card includes a
From saved query
Like the Frozen IOCs card, this is frozen: editing the saved query later doesn’t retroactively change the SQL a running campaign dispatches. To change the SQL on a stopped or unsatisfactory campaign, stop it and launch a fresh campaign from the Queries page.
Warning badges and banners
Three guardrails surface inline when they fire:
- Per-host truncated badge (
osqueryonly) — next to a host’s row count in the Late Responses card. The host produced more rows than the per-host row cap allows. Rows already collected are kept; nothing on later rows can be recovered without re-launching with a narrower query. - Aggregate-cap auto-stop banner (
osqueryonly) — an amber banner across the top of the detail page reading “Campaign auto-stopped after exceeding the aggregate row cap.” The campaign carried more total rows across the fleet than the configuredMIMIR_CAMPAIGN_AGGREGATE_ROW_CAP(default 10,000,000). The status isstoppedwith reasonauto_stopped_aggregate_row_cap. Rows already collected stay; pending and dispatched assignments are cancelled. Re-launch with a narrower query, or ask your operator to retune the cap. - Corrupt-snapshot warning badge (
ioc_hunt, in practice) — an amber badge near the header strip reading “Snapshot corrupt — matches may be incomplete.” A pathological row caused the ingestion path to fail unmarshalling the frozen IOC snapshot and continue with an empty match set; the badge surfaces what would otherwise be silent log noise. Treat the campaign’s match count as a lower bound and consider stopping + re-launching.
None of these block the campaign from being read or stopped; they’re surfaced so the data on screen is honest about what happened.
Stopping a campaign
Admins see a red Stop Campaign button in the header. Click it to open the stop modal. Two choices:
- Reason (optional) — free-text up to 1000 characters, shown alongside the stopped-at timestamp in the header strip. Use it to explain “stopped because all live responses came in” or “false-positive IOC, re-launched as campaign N.”
- Discard in-flight results — a checkbox. By default (unchecked), Mimir cancels pending assignments but accepts any responses that are already on the wire. Tick the box to also reject responses that arrive after stop — useful when you’ve identified the campaign as outright wrong and don’t want it producing alerts after the stop point.
The stop is a single API call (POST /api/v1/campaigns/<id>/stop),
gated by requireAdmin server-side. Successful stop flips the
status to stopped immediately; the table cell turns gray and
the Stop button disappears.
A stopped campaign keeps its row, its IOC snapshot, its already-collected late matches, and the audit trail. Nothing is deleted. The list page is the right place to come back later and review what the campaign caught before it was stopped.
Reading the numbers in context
A few common shapes of campaign data and what they usually mean:
Responded == Assigned, Late Matches == 0. The campaign is fully drained and clean — every host answered, nothing matched. Safe to leave in place or stop with reason “complete.”
Responded < Assigned, Pending > 0 for weeks. Hosts on the pending list are likely decommissioned but not yet decommissioned in Mimir. Either mark them decommissioned to remove them from the fleet view, or stop the campaign because waiting any longer won’t add signal.
Late Matches > 0 with the live hunt long completed. This is exactly what offline campaigns exist to catch. Investigate each late-match host with the same urgency you’d give a fresh alert — the indicator is real even if the timing is delayed.
Coverage card sums don’t match the hunt’s Coverage strip. Expected. The hunt’s number is point-in-time for its live-dispatch window; the campaign keeps accruing responses forever, so its numbers grow over time.
Troubleshooting
“Campaign not found.” The URL’s id doesn’t exist. Most likely you bookmarked a campaign that’s since been removed (campaigns aren’t permanently deleted, but archival can hide them from the list). Open the Campaigns page directly.
Late matches keep arriving days after I expected the campaign to be done. This is the design. The reconnect dispatcher claims a returning host’s assignment within seconds of reconnect, and there’s no global timeout on a campaign — a laptop that comes back online a week later still runs its assignment. If the trickle isn’t useful, stop the campaign with Discard in-flight results to cut it off.
“Stop Campaign” button is missing. You’re not an admin. Ask your admin to stop, or grant your account the admin role. Non-admin users can read campaigns but can’t end them — the gate is server-side, not just a UI hide.
The frozen IOC list doesn’t include an indicator I added mid-campaign. Expected. The snapshot is taken at launch. Adding an IOC after launch does not retroactively add it to a running campaign. To pick up a newly-added indicator, run a fresh hunt (which spawns a fresh campaign when the flag is on).
Permissions
GET /api/v1/campaigns/<id> is gated by withAnyAuth.
POST /api/v1/campaigns/<id>/stop requires requireAdmin.
The UI hides the Stop button for non-admin users so the affordance
is honest — clicking it as a non-admin would 403, and Mimir
prefers not to show a button that returns 403.
Where to next
- Launching a campaign — how campaigns get created, the two kinds, the feature flag, and when to use them.
- Launch a hunt — the form that
spawns an
ioc_huntcampaign when offline campaigns are on. - Run a query as a campaign —
the toggle on the Queries page that spawns an
osquerycampaign. - Matching modes — the
three live matching paths, and how campaign late matches land
as
campaign-source alerts. - Host detail — drill into a pending host or a late-matched host.