opentide

opentide query

Run a read-only query on one, several or all tenants of a platform and get the rows back, from a query you type or from a rule.

Runs a read-only query on a platform's tenants and prints the rows. Use it to try a detection against real telemetry without leaving the terminal: the query you are writing, or the query a rule deploys.

opentide query --platform sentinel 'SecurityEvent | where EventID == 4688 | take 20'
opentide query --platform splunk --rule <rule-uuid> --tenant all --since 7d
opentide query --platform elastic --language eql --index 'logs-*' 'process where process.name == "cmd.exe"'
opentide --json query --platform defender_for_endpoint --file objects/rules/suspicious-lsass.yaml

opentide validate query --live checks that a query runs; opentide query returns what it finds.

Arguments and options

InputPurpose
[QUERY]The query, in the platform's language. - reads it from stdin
--platformRequired. sentinel, defender_for_endpoint, splunk, sentinel_one, carbon_black_cloud, crowdstrike, harfanglab or elastic
--tenantA configured tenant name (repeatable), or all. Default: the only tenant configured
--ruleRun the query this rule (by UUID) deploys to the platform
--fileRun the query of the rule in this YAML file, saved or not: a path from the current directory or the repository root
--query-fileRead the query from this file
--sinceLook back this far from --to or now: 15m, 24h, 7d, PT1H. Default 24h
--from, --toAn explicit window, ISO-8601. A time without an offset is UTC. --since and --from exclude each other
--limitRows per tenant, 1–5000. Default 100
--languageElastic only: esql (default), eql, lucene or kuery
--indexElastic index pattern, or CrowdStrike repository (repeatable)
--timeoutSeconds to wait for an asynchronous search (SentinelOne, Carbon Black, CrowdStrike). Default 120
--columnsComma-separated columns the table shows
--formattable (default), jsonl or csv. --json prints the envelope instead

Give exactly one query: QUERY, --query-file, --rule or --file.

Tenants

The query runs on the tenants --tenant names, in the order the platform file lists them. With no --tenant, a platform with one tenant uses it; with several, the command exits 2 and lists them, so a query never reaches a production tenant you did not name. --tenant all runs on every tenant. A rule's own tenants list and the tenants' strategies do not restrict a query.

Each tenant runs independently: one that refuses the query or cannot be reached is reported, and the others still return their rows.

What runs on each platform

PlatformAPILanguageWindowLimit
SentinelLog Analytics query_workspaceKQLtimespanappends | take N+1 unless the query ends in take/limit
DefenderGraph runHuntingQueryKQLTimespan interval, at most 30 daysas Sentinel
Splunkone-shot search job (no saved search)SPL; a query not starting with | gets searchearliest_time/latest_timecount
SentinelOnePowerQuery dv/events/pq, polled with pq-pingPowerQueryfromDate/toDateappends | limit N+1
Carbon Blackprocess searchLucenetime_rangerows
CrowdStrikeNext-Gen SIEM query job, stopped on timeoutCQLstart/endappends | head(limit=N+1)
HarfangLabtelemetry APIa source and field=value filters (below)@event_create_datelimit
ElasticES|QL _query, EQL _eql/search, Lucene _search--language@timestamp range filterLIMIT, size
Elastic kuerydetection rule previewKQLthe rule's look-backthe rule's max_signals

One row past --limit is requested, so a result knows when more matched: truncated is true and the table says more matched; raise --limit.

Elastic KQL. Kibana runs KQL only inside detection rules, so a kuery query is previewed as a rule and the rows are the alerts the preview raised, with the alert bookkeeping (kibana.*) removed. Each result carries a warning saying so. ES|QL, EQL and Lucene return events directly. EQL and Lucene search --index, else the rule's index or data view, else the tenant's setup.index.

HarfangLab. The telemetry API filters one source by fields; it has no query language. A query names the source, then filters the API applies as given:

processes process_name=powershell.exe agent.hostname=WS01
dns requested_name=example.org
network 'commandline__icontains=invoke-webrequest'

Sources: processes, binary, network, eventlog, dns, windows_authentications, linux_authentications, macos_authentications. A HarfangLab rule is a Sigma or YARA rule the agents evaluate, so --rule refuses it: pass a telemetry query instead.

CrowdStrike. The API client needs the NGSIEM: Read scope (deploy's correlation-rule scope is not enough). The repository is search-all unless --index names another, such as investigate_view.

Defender. The app registration needs ThreatHunting.Read.All.

Rules

--rule and --file send the query deploy would send:

  • Sentinel and Defender: the query with the exclusions that apply to each tenant, compiled as deploy compiles them; sent shows the result.
  • Splunk: the query with the MDR mapping macro deploy appends.
  • SentinelOne correlation rules: each sub-query, as its own result labelled sub-query 1, sub-query 2… The correlation itself is not applied, and each result says so.
  • Carbon Black: the lines of a multiline query joined with spaces.
  • Elastic: the rule's language (esql, eql, or kuery/lucene for the other types) and its index or data view. For threshold, new_terms and threat_match rules only the event query runs, and a rule's filters are not applied; the result says so in both cases; machine_learning rules have no query.

--language and --index override the rule's own.

Output

Without --json, each tenant prints its name, the row count and a table of up to eight columns; --columns picks others. Warnings (a partial result, a preview) go to stderr. The outcome line counts the rows:

lab  2 rows
 host     count
 ──────────────
 ws-01    12
 ws-02    3

✓ 2 rows from 1 splunk tenant

--format jsonl writes one JSON object per row and --format csv one line per row, each with a _tenant column (and _label for a rule with several queries). Nested values are JSON. Only rows reach stdout; an error goes to stderr.

With --json, for a Splunk tenant (sample values):

{
  "ok": true,
  "status": "completed",
  "message": "2 rows from 1 splunk tenant",
  "platform": "splunk",
  "source": "query",
  "query": "index=main | stats count by host",
  "window": {"from": "2026-10-26T08:00:00.000Z", "to": "2026-10-27T08:00:00.000Z"},
  "limit": 100,
  "tenants": ["lab"],
  "results": [
    {
      "tenant": "lab",
      "status": "ok",
      "columns": [{"name": "host", "type": null}, {"name": "count", "type": null}],
      "rows": [{"host": "ws-01", "count": "12"}, {"host": "ws-02", "count": "3"}],
      "row_count": 2,
      "truncated": false,
      "partial": false,
      "language": "spl",
      "sent": "search index=main | stats count by host",
      "warnings": [],
      "elapsed_ms": 812
    }
  ],
  "row_count": 2
}

A failed tenant has status: "failed", error and, when the platform gave one, detail (the parser's message). sent appears when the query sent differs from the one given. partial is true when the platform stopped early (a Sentinel partial result, an Elastic time-out). For a rule, rule replaces query: {uuid, name, queries}.

Exit codes

CodeWhen
0Every tenant answered, rows or none
1A tenant refused the query or could not be reached; the others' rows are still printed
2The call could not run: no query or two, an unknown rule, tenant or language, a bad window, no tenant configured

Safety

The command reads telemetry and saves no search or rule. Splunk runs a one-shot job. CrowdStrike's job is stopped when the command stops waiting. SentinelOne PowerQuery and Carbon Black process searches have no cancel call: a search that outlives --timeout finishes on the tenant and expires there. An Elastic kuery query writes its preview alerts to .preview.alerts-security.alerts-<space>, as a preview in Kibana does; Kibana cleans that index up itself. Credentials come from the platform file, as for deploy; an unset $VAR refuses the tenant before anything is sent. Rows are telemetry: nothing is written to disk unless you redirect stdout.

On this page