Hunt Workbench and Query Language¶
The Hunt Workbench runs structured searches inside the open case. It is for questions that are awkward or impossible to answer with the dashboard's text filter: failed logons for one account, rare destination addresses, the busiest source hosts, or all high-severity activity in a time window.
Choose the dataset explicitly before every run:
| Dataset | Contains | AI boundary |
|---|---|---|
| Forensic timeline | Graded, in-scope investigative events | Results can be attached to findings or added to the notebook |
| Super-timeline | The complete raw imported record, including Info telemetry | Results remain analyst-only; promote individual rows before they can support a finding or enter synthesis context |
Queries read SQLite indexes in bounded cursor pages. The workbench does not load a complete timeline into browser or server memory. Conditions that can use the time, host, source, severity, event-ID, IOC, or MITRE indexes are pushed down first; the explanation shown before and after a run says which indexes are used. Other conditions are checked as each bounded page arrives.
First queries¶
Failed authentication for an account during the last two hours, counted by source address:
event.category=authentication
AND user.name="jdoe"
AND event.outcome=failed
AND timestamp during "last 2h"
| group by source.ip
| count
| sort count desc
High or Critical activity on either of two hosts:
Rare destinations in the selected dataset:
Safe regular-expression search:
Parameterized hunt template:
Enter its values in Parameters as account=jdoe, window=last 24h. Parameter values are stored
with a saved hunt and each execution-history record. They are values, not query fragments, so they
cannot change the grammar.
Grammar¶
filter := expression ( "|" stage )*
expression := NOT expression
| "(" expression ")"
| expression AND expression
| expression OR expression
| predicate
predicate := field operator value
operator := = | != | > | >= | < | <=
| contains | matches | exists | between | during
value := quoted string | number | boolean | null
| bare keyword | $parameter | /safe-regex/flags
stage := group by field
| count
| stats function(field) [by field]
| rare field [limit N]
| sort field [asc|desc]
| limit N
NOT binds more tightly than AND, and AND binds more tightly than OR. Parentheses override
that order. Equality and ordering are typed: severity uses
Info < Low < Medium < High < Critical, numbers compare numerically, and timestamps compare as
time. contains is case-insensitive. Equality is exact.
Time windows¶
Use during with last or past, followed by a number and unit:
timestamp during "last 30m"timestamp during "past 2h"timestamp during "last 7d"
Units are ms, s, m, h, d, and w. An absolute window uses two parseable timestamps:
The first page fixes the relative-time anchor. Following its Next page cursor uses the same anchor, so a long review cannot shift the result window between pages.
Pipeline stages¶
| Stage | Result |
|---|---|
group by host.name \| count |
One row per host with a count column |
count |
One total matched-row count |
stats count(), min(destination.port), max(destination.port) by source.ip |
Typed statistics per source address |
rare destination.ip limit 10 |
Ten least-common non-empty values |
sort count desc |
Descending order by the named output column |
limit 50 |
At most 50 returned rows |
Statistics name their output columns predictably: count, min_destination.port,
max_destination.port, sum_count, and so on. sum and avg are useful for numeric fields.
Typed field catalogue¶
The autocomplete menu is generated from the same catalogue the server validates. Aliases are accepted, but explanations and saved plans use the canonical name.
| Field(s) | Type | Indexed | Meaning / aliases |
|---|---|---|---|
id |
keyword | yes | Stable event ID; alias event.id |
timestamp, original_timestamp |
timestamp | timestamp |
Normalized time and original pre-alignment time; alias @timestamp |
description, message |
string | no | Summary and retained full source message |
severity |
keyword | yes | Critical, High, Medium, Low, or Info |
host.name |
keyword | yes | Affected host; alias asset |
event.source |
keyword | yes | Artifact and source tools; aliases source, artifact |
event.category, event.type, event.action, event.outcome |
keyword | no | Canonical event classification; outcome is an alias |
user.name, user.id, user.domain |
keyword | no | Canonical account; account.* aliases are accepted |
source.ip, destination.ip |
keyword | yes | Canonical or legacy network addresses |
source.port, destination.port |
number | no | Network ports |
source.hostname, destination.hostname, network.protocol |
keyword | no | Network endpoint names and protocol |
process.name, process.parent.name |
keyword | no | Subject and parent process names |
process.pid, process.parent.pid |
number | no | Subject and parent process IDs |
process.executable, process.parent.executable, process.command_line |
string | no | Process paths and command line; process.commandline is accepted |
file.path |
string | yes | File path; alias path |
file.name |
keyword | no | File name |
file.sha256, file.md5 |
keyword | yes | File hashes; aliases sha256, md5 |
registry.key, registry.value_name, registry.value_data |
string / keyword | no | Registry activity |
service.name, service.display_name, service.executable |
keyword / string | no | Service activity |
task.name, task.command |
keyword / string | no | Scheduled-task activity |
authentication.session_id, authentication.protocol |
keyword | no | Authentication context |
authentication.logon_type |
number | no | Windows logon type |
session.id, session.interactive |
keyword / boolean | no | Session identity and interactivity |
cloud.provider, cloud.principal_id, cloud.tenant, cloud.region |
keyword | no | Cloud identity and location |
cloud.resource |
string | no | Cloud resource |
mailbox.sender, mailbox.recipient |
keyword | no | Email participants |
mailbox.subject |
string | no | Email subject |
actor.*, subject.*, object.*, target.* |
keyword / number | no | Canonical entity kind, id, name, domain, address, and numeric port |
mitre.technique |
keyword | yes | MITRE ATT&CK technique; alias mitre |
related.finding_id |
keyword | no | Finding IDs linked to the event |
evidence.screenshot |
string | no | Source screenshot paths |
count |
number | no | Collapsed occurrence count; defaults to 1 |
ioc |
keyword | yes | Any event IP, hash, or path represented in the IOC index |
Canonical fields may be absent on an older or less-structured record. Use field exists when a
missing value matters. Legacy event rows are upgraded on read where a deterministic mapping is
available; no prose is invented to fill a field.
Errors and resource limits¶
Errors identify the line and column and use stable codes:
| Code | Meaning |
|---|---|
empty_query / invalid_query |
No usable expression, or the request was not query text |
unknown_field |
Field is outside the typed catalogue; nearest valid names are suggested |
expected_token, expected_operator, expected_value |
Grammar is incomplete at the reported location |
invalid_operator |
The operator does not apply there, such as during on a non-time field |
unsafe_regex |
Pattern is too long or uses lookarounds, backreferences, nested quantifiers, or repeated wildcards |
missing_parameter |
A $parameter was not supplied |
invalid_time_window |
A relative or absolute time window could not be parsed |
invalid_cursor |
Cursor is malformed or belongs to different query text, parameters, or dataset |
resource_limit |
Execution exceeded its bounded time, scan, regex, group, or materialized-row budget |
cancelled |
The analyst pressed Cancel or closed the request |
Default server limits are 100,000 scanned rows, 5 seconds, 50,000 regex evaluations, 5,000 groups, 10,000 materialized rows for a raw global sort, and 250 rows per storage page. A single response is limited to 1,000 rows. These limits make an expensive query fail visibly instead of degrading the case server. Regex patterns are limited to 256 characters and deliberately reject constructs with poor worst-case behavior.
Saved hunts and result actions¶
Save hunt stores the name, exact query, selected dataset, author, and parameters with the case. Each executed run adds its time, analyst, status, parameters, match/scan counts, and duration. The history keeps the newest 50 runs. Saved hunts travel with investigation snapshots and survive synthesis.
Select a saved hunt to see its Execution history under the saved-hunt list. Each row shows one run, newest first: time (UTC), analyst, status (completed, cancelled, limited, or failed), match count, and duration. Hover the match count to see how many rows the run scanned. Open Parameters under a row to see the parameter values that run used, and the error text of a run that did not complete. A hunt that has never run shows "Not run yet". The history refreshes when a Run finishes, and the saved hunt stays selected. A request that the server refuses before the query starts (for example, a case with no indexed store) is not a run, so it adds no row.
Results can be:
- viewed as a table, chronological timeline, or chart;
- cursor-paged without changing the query's relative-time anchor;
- exported as formula-safe CSV;
- added to the analyst notebook with selected event links;
- attached as evidence to an existing finding; or
- opened from one-click pivot buttons on event, IOC, finding, and asset rows.
Notebook and finding-evidence actions are disabled for super-timeline results. Select and promote the individual raw rows first; the promoted forensic copies can then be queried and used normally.
Sigma coverage¶
The Sigma coverage card at the bottom of the Hunt Workbench shows which of the case's ingested sources the import-time Sigma rules grade. Use it to find blind spots before you trust a quiet result.
The headline reads "N of M ingested sources have Sigma rules running". The table has one row for each origin in the case's super-timeline:
- Origin and Events — the source label and the number of rows the super-timeline holds for it.
- Coverage — one of four states:
- Sigma rules running — at least one rule is loaded for this source.
- No loaded rules — the source is graded, but every rule for it is switched off, refused or filtered by status. This is a real blind spot.
- Not graded by Sigma — no Sigma log source reads this origin (for example Windows event logs or Microsoft 365 audit logs).
- Sigma grading is switched off —
DFIR_SIGMA_RULES=off, so nothing is graded.
- Sigma sources and rules — for each Sigma log source that grades the origin, the rules loaded, switched off, refused, and filtered by status in the installed release.
- Show in super-timeline — opens the Super-Timeline filtered to that origin only.
Limits of the card:
- Sigma grades rows at import only. Rows imported before a source gained rules, or while Sigma was switched off, were not checked.
- Web Access Log rows are graded by the web server rules or the web proxy rules. The importer picks one per import from the log's shape.
- Entra ID also carries service-principal sign-ins. Sigma does not grade them.
The card is for the analyst only. The AI never reads it. It refreshes when a case opens, when an import adds rows, and when you click Refresh coverage.