Query DSL
A single syntax drives the Explore editor, Alert Triggers, and the /api/query endpoint. Splunk/Kibana-flavored, single-line, safe to paste into a URL.
Overview
Queries are composed of predicates (field:value) combined with boolean operators (AND, OR, NOT) and parenthesized groups. Whitespace between predicates is an implicit AND.
tag:credential-stuffing AND fleet:core NOT country:US
Grammar
expr ::= or_expr
or_expr ::= and_expr ("OR" and_expr)*
and_expr ::= not_expr (("AND" | implicit) not_expr)*
not_expr ::= "NOT" atom | atom
atom ::= "(" expr ")" | predicate
predicate ::= FIELD (":" | ":" OP) VALUE
OP ::= "=" | "!=" | ">" | ">=" | "<" | "<=" | "~"
VALUE ::= bareword | "(" csv ")" | quoted-stringFields
| Field | Type | Notes |
|---|---|---|
ip | string | IPv4 address |
id | string | Record ID (thr_…) |
asn | string | ASN number, e.g. AS14001 |
asn_name | string | ASN operator name |
country | string | ISO 3166-1 alpha-2 |
tag | string[] | Behavioral tag; matches any element |
fleet | enum | core or mesh |
sensor | string[] | Sensor id that observed this IP; supports wildcards (e.g. sensor:mesh-*) |
confidence | number | 0.0 to 1.0 |
first_seen, last_seen | time | Unix ms or relative (24h, 7d) |
novel | time | Sugar for “first-seen within the last N”. Bareword form, no operator — novel:24hmatches IPs we’d never seen before today. |
ja3, ja4 | string | TLS fingerprints · paywalled |
port | number | Any sighting port |
action | enum | drop, rate_limit, monitor |
Operators
| Op | Meaning | Example |
|---|---|---|
= | Equals (default when omitted) | country:RU |
!= | Not equal | country:!=US |
>, >= | Greater than | confidence:>0.7 |
<, <= | Less than | last_seen:<24h |
~ | Substring / contains | asn_name:~CHOOPA |
* | Glob wildcard inside the value (anchored). Use with = or !=. | tag:tool:sliver* |
Wildcards
A literal * anywhere in the value of an = or != predicate is a glob. The pattern is anchored — tool:sliver* means "starts with tool:sliver", not "contains it anywhere" (that's ~'s job). Every other regex metachar is treated as a literal, so a tag with a .in it doesn't accidentally match through as regex any-char.
tag:tool:sliver* # any sliver variant (starts-with) tag:tool:*-golang # all golang-family tools (ends-with) tag:tool:sliver*golang # middle wildcard tag:*sliver* # contains (same result as tag:~sliver) asn_name:*DIGITAL* # fuzzy ASN lookup tag:(tool:sliver*,tool:chrome*) # comma-list + wildcard works
Booleans & grouping
AND, OR, and NOT are case-insensitive. Parentheses override default precedence. Adjacent predicates without an operator are implicit AND.
(tag:mirai-variant OR tag:iot-botnet) AND NOT country:US tag:credential-stuffing fleet:core # implicit AND
Values
Three value forms are accepted:
- Bareword — anything without whitespace or parens:
country:RU - Quoted — for values with spaces or special chars:
asn_name:"Digital Ocean" - List — match any of several values:
country:(RU,CN,NL)
Relative time
Time fields accept relative shortcuts ending in s, m, h, d, w. They expand to a Unix ms timestamp relative to “now”.
last_seen:<24h # last 24 hours first_seen:>7d # records first seen more than 7 days ago last_seen:>=30m # last 30 minutes or older
Paywalled fields
Examples
tag:ssh-bruteforce AND asn_name:~CHOOPA
confidence:>=0.8 AND last_seen:<1h
(tag:mirai-variant OR tag:iot-botnet) NOT country:(US,CA,GB,DE)
ja4:~t13d0111h2 AND fleet:core
tag:probe-family:elasticsearch AND last_seen:<24h
tag:probe-family:wordpress-scan NOT (tag:asn-abuse AND country:US)
novel:24h AND tag:probe
Probe families
Scan-probe events (synsink captures on our honeypot ports) carry a single probe-family:* tag derived from the raw banner the scanner sent. Facet on it to see what scanners are actually hunting for across the fleet:
probe-family:http HTTP request on a web-like port
probe-family:http-on-nonstd HTTP against a non-web port (signal of a scanner, not a misrouted client)
probe-family:elasticsearch Hit /_cat, /_cluster, /_nodes
probe-family:java-admin-scan Tomcat manager, JBoss jmx-console, WebLogic console
probe-family:solr /solr/* enumeration
probe-family:wordpress-scan wp-login, wp-admin, xmlrpc.php
probe-family:phpmyadmin-scan phpMyAdmin / Adminer hunters
probe-family:secret-hunter .env / .git / .aws / wp-config leak hunters
probe-family:iot-rce-chain HNAP / GponForm / boaform / soap.cgi (Mirai family)
probe-family:log4shell ${jndi:ldap://...} markers anywhere in the request
probe-family:open-proxy-check CONNECT host:443 HTTP/1.1
probe-family:webdav-scan PROPFIND
probe-family:tls-on-nontls TLS ClientHello cipher-suite signature on a non-TLS port
probe-family:ssh-stray SSH-2.0-... banner on a non-SSH port
probe-family:rdp Contains "Cookie: mstshash=" (RDP credential scanner payload, any port)
probe-family:mongodb admin.$cmd + isMaster/buildinfo BSON payload
probe-family:mysql HandshakeResponse w/ mysql_native_password / libmysql markers
probe-family:redis Inline RESP (PING, INFO, CONFIG GET *)
probe-family:memcached stats / version / get (ASCII protocol)
probe-family:smtp EHLO, STARTTLS, MAIL FROM:
probe-family:ftp AUTH TLS, FEAT, SYST
probe-family:pop3 APOP, STLS
probe-family:vnc RFB 003.00x
probe-family:postgres StartupMessage w/ user= field
probe-family:syn-only Client sent zero bytes — pure SYN probe
probe-family:unknown Bytes sent but no matcher hit — worth a hex lookErrors
Parse errors are positional — the response includes the character offset of the offending token so an editor can underline it:
{
"error": "invalid_query",
"message": "Unknown field \"contry\"",
"position": 0
}Paywall errors carry a blocked_fields list so clients can show a targeted upgrade prompt:
{
"error": "invalid_query",
"message": "These fields require a Pro key: ja4",
"blocked_fields": ["ja4"],
"upgrade_url": "/pricing"
}