rsigma hunt run
rsigma hunt run
Convert detection rules with the PostgreSQL backend, execute the query read-only against a log archive, and stream the matching rows back as exemplar-shaped NDJSON events.
Synopsis
rsigma hunt run [OPTIONS] --target postgres --rules <PATH>...
Description
hunt run closes the loop between conversion and authoring: backend convert -t postgres emits the SQL, and this command runs it against your PostgreSQL/TimescaleDB archive and returns the matching rows as one raw JSON event object per line, ready to feed rule draft, rule tune, rule test exemplars, or a rule backtest corpus.
rsigma is a read-only client of a store it already generates queries for; it does not store or search logs itself. The session opens with SET default_transaction_read_only = on and a server-side statement timeout before any hunt SQL runs, so read-only is enforced rather than promised. Rows stream without OFFSET paging, so memory is bounded by one row regardless of result size.
The generated per-rule SELECT is wrapped, never edited:
SELECT * FROM (<generated>) AS __hunt
WHERE <timestamp_field> >= '<since>' AND <timestamp_field> < '<until>'
ORDER BY <timestamp_field>
LIMIT <n>
Only the WHERE/ORDER BY/LIMIT wrapper is hunt-owned SQL. Note that ORDER BY <timestamp_field> is not deterministic on timestamp ties; for exemplar harvesting that is acceptable and no synthetic tiebreaker is invented. A rule’s fields: list is cleared before conversion: hunts want whole rows, and a declared projection would both starve the exemplar contract in flat mode and replace the raw json_field column with extractions in JSONB mode.
Row-to-event reshaping
- JSONB mode (
-O json_field=data) is the high-fidelity path: the stored column is the original event, so it is emitted verbatim, merging the timestamp column under its column name when the event body lacks one (the body’s value wins on conflict, with a one-time note on stderr). - Flat-column mode reconstructs the event from the row: each non-NULL column becomes a JSON key named by the column.
timestamptz/timestampbecome RFC 3339 strings,inetbecomes a string, integer and float types become numbers,numericbecomes a number when the value round-trips through a double exactly and the exact decimal string otherwise,boolstays boolean,jsonbis inlined as its JSON value. NULL columns are dropped rather than emitted asnull, because the reference schema is a wide single-landing-table where most category-specific columns are NULL for any given event. Columns of a type with no decoder (e.g.tsvector) are skipped with a one-time stderr warning naming the column and type.
The columns are post-pipeline names (the pipeline mapped the rule’s fields to the archive’s columns before conversion), so hunt output events carry the archive’s native field names. Consume them downstream without re-applying the mapping pipeline.
Scope
v1 is detection-rule hunts only. Correlation rules are rejected with a pointed error: correlation SQL returns aggregate rows (group keys plus counts), not events. --target postgres is the only accepted value; every other target stays convert-only (delegated sigma-cli targets by definition, since rsigma cannot execute a Splunk query), and the error points at backend convert.
Flags
Required
| Flag | Description |
|---|---|
-r, --rules <PATH>... |
Sigma rule file(s) or director(ies) to hunt with. Repeatable. |
-t, --target <TARGET> |
Hunt target. Only postgres (aliases postgresql, pg) is executable. |
Connection
| Flag | Default | Description |
|---|---|---|
--dsn <DSN> |
$RSIGMA_HUNT_DSN |
PostgreSQL connection string (URL or keyword form). Required for --emit events. The password is never rendered in logs or errors; messages show a DSN rebuilt from the non-secret fields. sslmode is honored (disable, prefer with plaintext fallback, require); TLS uses rustls with the system root store. A dedicated read-only role is recommended but not relied upon. |
--timeout <DURATION> |
60s |
Server-side statement_timeout for the hunt session, rendered as an integer millisecond literal. Must be between 1ms and 2147483647ms (a sub-millisecond value would truncate to 0, which disables the server timeout). Validated on every path, including --emit sql. |
Query shaping
| Flag | Default | Description |
|---|---|---|
-p, --pipeline <PIPELINE> |
none | Processing pipeline(s) (repeatable). Builtin names or YAML file paths, same as backend convert. |
-O, --option <KEY=VALUE> |
none | Backend options (repeatable): table, schema, json_field, timestamp_field. See the PostgreSQL backend reference. |
--since <WHEN> |
none | Window start: an RFC 3339 instant or a duration relative to now (30m, 12h, 7d). |
--until <WHEN> |
none | Window end (exclusive). Same syntax as --since; a duration is relative to now, so --until 1h means “up to one hour ago”. |
--limit <N> |
1000 |
Maximum rows per rule. 0 means unbounded. Hitting the limit is reported on stderr so a truncated hunt is never mistaken for a complete one. |
Output
| Flag | Default | Description |
|---|---|---|
--emit <MODE> |
events |
events streams NDJSON; sql prints the wrapped queries with attribution headers and exits without connecting (works in every build, including binaries without the hunt-postgres feature). |
-o, --output <PATH> |
stdout | Write events (or SQL) to a file instead of stdout. In events mode the file opens on the first event (or, for a successful hunt with no matches, at completion), so a hunt that fails before producing output never clobbers an existing file. |
Events are always NDJSON: one raw JSON event object per line, nothing else on stdout. Per-rule row counts and the final summary (rows, elapsed, truncation) go to stderr and honor --quiet / --no-stats.
Examples
Hunt the last 30 days into an exemplar file
rsigma hunt run -r rules/suspicious_curl.yml -t postgres \
--dsn postgres://hunter@archive.example/siem \
--since 30d -o hunted.ndjson
Review the SQL without connecting
rsigma hunt run -r rules/ -t postgres -O table=okta_events -O json_field=data \
--since 2026-07-01T00:00:00Z --emit sql
-- timestamp_field: time
-- json_field: data
-- rule: Suspicious Curl (id: 00000000-0000-0000-0000-000000000201)
SELECT * FROM (SELECT * FROM okta_events WHERE data->>'Image' = '/usr/bin/curl') AS __hunt WHERE time >= '2026-07-01T00:00:00+00:00'::timestamptz ORDER BY time LIMIT 1000;
Feed a draft
rsigma hunt run -r hunts/okta_group_add.yml -t postgres --since 7d -o /tmp/tp.ndjson
rsigma rule draft -e @/tmp/tp.ndjson
Exit codes
| Code | Meaning |
|---|---|
0 |
The hunt ran; events (or SQL) were emitted. |
2 |
A rule failed to load or convert, a correlation rule was passed, or a rule produced non-SELECT output. |
3 |
Bad flags or options (unknown target, empty window, invalid identifier or time bound), missing DSN, a binary built without hunt-postgres, or a connection/execution failure against the archive. |
See also
- Hunting in the archive for the hunt-to-exemplar workflow end to end.
- PostgreSQL backend reference for the
-Ooptions and JSONB mode. backend convertfor convert-only targets.- Feature flags for the
hunt-postgresgate; Environment variables forRSIGMA_HUNT_DSN.