Reference

gori Query Language

gori has a small query language (QL) for filtering flows. The same syntax works in the TUI filter bars, in gori run (-q/--query, or positionally), and through the MCP tools. The built-in reference is also available as the TUI's Help Query page and the ql_reference MCP tool.

Fields

Match a field with field:value (substring or exact, depending on the field):

Field Matches
host Request host
path Request path and query string, so -path:x also drops ?q=x
url Full URL
method HTTP method
scheme http / https
proto Protocol: http, ws, grpc, sse. Suffix s for the TLS one (https, wss, grpcs, sses), and websocket is an alias of ws
src Where the flow came from (below)
via tui / cli / mcp. Which gori surface sent it (below)
status Response status code
size Total request + response bytes
reqsize / respsize Per-side byte count
dur Response time in milliseconds
header Substring over the head (request + response headers)
body Full-text match over bodies (trigram FTS index)
ws A WebSocket flow whose socket carried a TEXT or BINARY message containing the value, in either direction (below)
rpc A flow whose request body is a JSON-RPC call (MCP included) whose method starts with the value (below)
stub true / false. Flows gori answered itself from a short-circuit rule, with no origin involved
rewritten true / false, or one rule as project/4 / global/3. Flows a Match & Replace rule changed; a rule that matched and changed nothing is not recorded, and a flow captured before gori recorded rules matches neither true nor a rule
static true / false. Images, fonts and audio/video, by response Content-Type (by path extension when there is none). Not SVG, CSS, JS or an image proxy (?url=), and only a successful fetch (2xx or 304). -static:true is the hide-static lens
scope in / out. The project's scope rules (below)
cache hit / miss / dynamic / none: the cache verdict read from the response's cache headers (Age, X-Cache, CF-Cache-Status, Cache-Status, …). none is a real value (no verdict, which includes a pending flow). A cache: query adds a CACHE column to History
host:example.com
method:POST
status:404

One side only: req. / resp.

header: and body: search both the request and the response. Prefix either with req. or resp. to search one side.

Field Meaning
req.body / resp.body That side's body only
req.header / resp.header That side's head only
resp.body:secrettoken                 a token only the response carries
resp.header:set-cookie                responses that set a cookie
-resp.body:abcd                       responses whose body lacks abcd
req.body~(?i)password                 regex over the request body only
NOT (req.body:token OR resp.body:token)

res. is a synonym of resp., and req.size / resp.size are synonyms of reqsize / respsize. Fields that only ever have one side (host, method, status, …) take no prefix.

Source: src:

src: matches flows by who put the request on the wire. src:proxy is traffic a client sent through gori; every other value is a request gori itself made, and src:import is a capture read out of somebody else's file.

Value Matches
proxy The capture proxy relayed a client's request. Ordinary captured traffic
repeater A Repeater send (the TUI, gori run repeater send --record-history, MCP send_request)
fuzzer A fuzz result recorded with --record-history / record_history
discover A crawl fetch (Discover persists by default)
retest One step of an Issue's retest (gori run retest run, MCP run_retest, the TUI's RETEST card). Its own value, not repeater: a request an operator drove by hand and a step of a check gori ran are different facts about the same bytes
refresh One step of a session slot's refresh, by hand or before a send (source_ref names slot NAME step N). Shown as RFRSH
macro One step of a Fuzzer or Miner run's request-time macro, replayed before a candidate (source_ref names macro step N). Shown as MACRO
miner, sequencer, authorize, probe Reserved. Those tools do not record flows yet
import Read in from a HAR, Burp export, --urls, an OpenAPI document
gori Every source gori SENT. The union of the middle rows, and not import
src:proxy                             read History as traffic that really happened
-src:proxy                            everything gori put there
src:gori                              …the same set, stated positively
src:repeater status:200               your own resends that came back OK
src:fuzzer resp.body:root:            fuzz hits, so a payload is not mistaken for a finding

The History SRC column prints a short tag for each of these (PROXY, RPTR, FUZZ, CRAWL, IMPRT, …) and src: accepts those tags too, so src:rptr and src:repeater are the same query. source: is accepted as a long spelling of src:.

Two things about it are deliberate:

  • An imported flow is not src:gori. gori never put it on a wire; it describes a real endpoint somebody else captured. -src:proxy keeps it, src:gori does not.
  • A flow captured before gori recorded provenance matches NEITHER direction. Those rows hold no source at all: gori was already writing repeater sends, crawls and imports into History before the column existed, so filling them in as proxy would have invented a fact no capture produced. They show — in the SRC column, and both src:proxy and -src:proxy skip them, the way a Pending flow falls out of status: and -status:. Only flows captured after the upgrade carry it.

The two common src: scopings are also views; press v in History to pick History (src:proxy) or History + Repeater, and the list stays narrowed while you type other filters. History + Repeater is what a project opens on. See Views.

Surface: via:

src: says which tool sent a flow; via: says which gori entry point drove it: tui, cli (gori run) or mcp (an agent). MCP send_request records src:repeater, the same as a send from the Repeater tab, so via: is how the two are told apart.

via:mcp                               everything an agent sent or imported
src:repeater via:tui                  only the resends you made by hand
-via:mcp                              everything the agent did not send or import, captures included

A proxy capture has no surface, so it matches no via: value and every negation keeps it. A flow gori sent before it recorded the surface behaves the same way.

Scope: scope:in / scope:out

scope:in matches the flows inside the project's scope (the same include/exclude boundary the TUI's s lens and gori run history --in-scope apply), and scope:out matches the ones outside it. It is an ordinary term, so it negates and it groups:

scope:in status:5xx                   server errors on the target
scope:out -host:cdn                   traffic that leaked out of scope, minus the CDN
(scope:in OR host:staging.example.com) method:POST

Three things about it are deliberate:

  • It ignores whether the s lens is switched on. A filter term is a question, not a mode, so scope:in means the same thing either way. (With the lens ON, it is redundant, since the lens already ANDs the same predicate over your query, and scope:out then matches nothing, since the lens has already dropped every out-of-scope row.)
  • With no scope rules configured, both spellings match nothing. Nothing is in scope, so the question has no answer and is not asked. In particular scope:out does not mean "everything" in that state, which is also why -scope:in, a negated never-match, is not the same as scope:out on a project with no scope rules. ql_explain reports scope_rules_configured: false and warns, and gori run history delete refuses a scope query outright rather than risk deleting a project's history over a term that had nothing to answer.
  • scope: is per-FLOW. On the Sitemap that makes it different from gori run sitemap --in-scope, which selects whole HOSTS (a host is kept if any of its traffic is in scope), so a scope:in query can keep a host and drop some of its endpoints.

Capture is untouched by any of this: gori records everything either way, and this only narrows what a query returns.

Status Classes

status: accepts class shorthands:

status:2xx      status:4xx      status:5xx

Comparisons

Numeric fields (status, size, reqsize, respsize, dur) support comparison operators <, <=, >, >=, =:

status:>=500        server errors
size:>100000        large exchanges
dur:>500            slower than 500 ms
dur:<2s             faster than 2 s (s / ms suffixes allowed)
size:>100k          byte fields take k/kb, m/mb, g/gb suffixes
status:>=4xx        a class shorthand takes a comparison too

Regular Expressions

Use ~ for a regex match on host, path, url, method, scheme, header, body or ws (and the req./resp. sides of header and body). The ~ is its own field/value separator. Do not put a colon before it. Matching is case-sensitive; prefix (?i) for case-insensitive.

path~/admin/
host~^api\.
header~set-cookie
method~^P(OST|UT|ATCH)$                every write verb in one term

A ~ on any other field (status, size, dur, proto, …) has no regex form: the term is dropped and reported, the same way a bad numeric value is, rather than searched as text.

WebSocket Messages

ws:needle matches a flow whose socket carried a message containing needle (case-insensitive); ws~regex matches by regex. Both directions are searched, TEXT and BINARY payloads alike, as the stored bytes: a NUL earlier in a payload does not hide a later hit. Ping, pong and close frames are not messages and never match, and neither do the [gori] notice rows gori writes about a socket. Notices are told apart by that prefix, so a peer message that starts with [gori] is skipped too. -ws:x keeps every flow that carried no such message, including flows that are not sockets at all.

ws:admin                               which socket mentioned admin?
proto:ws ws~(?i)token=[a-f0-9]{32}     a token in any frame

There is no index behind ws:, so it scans the transcript of every socket in the project. Capture stays as fast as before; the cost is paid by the query that uses the term.

To narrow one flow's transcript instead: in History's MESSAGES pane, search with ^F and pick Narrow messages to ^F query from the space menu (again to show all); gori run show <id> --ws-match=QUERY; or MCP get_flow with ws_query. Each takes ws:needle, ws~regex or a bare needle, matches exactly as the QL term does, and reaches messages older than the window those surfaces otherwise load.

JSON-RPC Methods

rpc:method matches a flow whose request body is a JSON-RPC call naming that method. The value is a case-sensitive prefix, so rpc:tools/ is every MCP tool method and rpc:tools/call is just the calls. Like body~, it scans the stored request bytes with no index, so a compressed request body is not read, and a "method" key nested inside params counts too. The body must carry "jsonrpc":"2.0", and a / in the value also matches JSON's escaped \/.

rpc:tools/call                         every MCP tool call
rpc:initialize host:mcp.example        an MCP handshake against one server

The decoded call, paired with its reply, is the RPC pane (named MCP for an MCP server) in History's detail view, a json_rpc key in gori run show --format json and MCP get_flow.

Combining Terms

  • Terms separated by spaces are AND-ed together. AND may also be written out.
  • OR matches either side. NOT and a - prefix both negate.
  • Parentheses group. Precedence is NOT then AND then OR.
  • A bare word (no field:) is free text over method, host, and target.
  • A bare word shaped like a comparison typed without its colon (status>=400, host=api) stays free text, so it usually matches nothing. The History and Sitemap empty states, gori run history and MCP ql_explain then name the colon form (status:>=400). A quoted or negated word is taken as intended.
  • An unknown field: name is read as a typo, not as free text: gori run history, gori run sitemap (and its params, js and export verbs) and gori run probe refuse it, name the nearest real field, and exit non-zero. --lenient searches the token as text instead (what every surface used to do silently: methd:GET matched nothing, which reads as an empty project). The TUI filter bar still accepts a half-typed name as you type it.
host:example.com status:5xx           both must match
host:api AND status:5xx               the same thing, spelled out
method:POST -status:200               POST, but not 200
host:a.com OR host:b.com              either host
(host:a.com OR host:b.com) -path:/js  either host, no /js
NOT (host:cdn OR host:static)         neither host
-(host:cdn OR host:static)            the same: `-(` and `NOT(` negate the group
login                                 free-text search

AND, OR and NOT are recognised uppercase only, so searching for the words "and", "or" or "not" still works. Quote them to force a literal even in caps.

Double quotes keep spaces inside one term:

host:"my host"                        one host value, space and all
"two words"                           free text for the whole phrase
"OR"                                  the literal word, not the operator

Inside quotes, \" is a literal quote: body:"\"role\":\"admin\"" searches for "role":"admin". A body: or header: value holding a control character (a tab inside quotes, a NUL) is dropped and reported, not searched.

A parenthesis inside a value stays literal, so path:/a(b) needs no escaping. A ( only opens a group at the start of a term, and ) only closes one at the end.

Where It Applies

Every filter bar shares the boolean grammar above (AND/OR/NOT, -, parentheses, quoting). The flow bars (History, Sitemap, colour rules, Intercept) also share the comparisons and ~ regex; the other bars compare only severity and cvss (below), and have no ~ at all (a ~ term there is free text). Otherwise what differs is the field set, and only because the surfaces filter different kinds of row.

Surface Fields
History, gori run history, MCP The full table above
Sitemap, gori run sitemap, MCP list_sitemap The same, plus tag: for per-node path memos: a case-insensitive substring of the memo, matching the tagged path, everything under it and the paths leading to it; -tag: drops that subtree, and every tag: term is ANDed with the rest of the query
Colour rules (Colormarker) The same. A colour rule takes the query the History bar takes
Intercept catch condition, extract-rule condition host, path, url, method, scheme, status, proto, header, body. Every other field is refused (see below). proto answers only ws or http here: gRPC and SSE are read off a captured response's Content-Type, so proto:grpc, proto:sse and the TLS spellings never match
Probe severity (sev), status (st), category (cat), host, code
Issues severity (sev), status (st), host, title, cvss
Sub-tab strip / filter (Repeater and the other tool tabs), MCP get_repeater_context filter tag, name, host (target), method (verb), status: the last send's outcome as a code matched by prefix (status:4 is every 4xx), error or unsent
Evidence tab issue, host, method, status, confirmation (confirm), source (src), date

A hold gate and an extract-rule condition evaluate one live message, before it is captured, so they refuse every History field that message cannot answer rather than guess: scope: (a project's scope rules are not part of a message), size:, reqsize:, respsize: and dur: (the exchange has not finished), stub:, rewritten:, static:, src:, via:, cache:, rpc: and ws: (a gate holds one message, not a transcript; its body: already means that message), and the one-sided req./resp. spellings. A refused field is never searched as free text instead. The condition rows say so where you type them, an extract rule carrying one will not save, and MCP intercept_set_filter and gori run intercept filter refuse the condition.

Probe and Issues take severity names (info, low, medium/med, high, critical/crit) and triage states (open, confirmed/conf, false-positive/fp, resolved/done, plus closed for any non-open state). Severity supports comparisons, so sev:>=high works. Issues also accepts cvss: with numeric comparison operators (cvss:>=7.0, cvss:<4.0), exact scores (cvss:7.5), or vector substrings (cvss:3.1).

sev:>=high -status:fp                 Issues: high and critical, no false positives
cvss:>=7.0 status:open                Issues: high or critical CVSS findings still open
cat:cors sev:medium                   Probe: medium CORS findings
host:api.example.com method:POST      Intercept: hold POSTs to one host
body:secret AND -host:cdn             Colour rule: paint leaks, ignore the CDN

Both the Intercept and colour-rule bars Tab-complete field names and known values as you type.

Matching Request and Response Content

header: and body: search the bytes of a message, so where they work is decided by which bytes exist at the moment the filter is asked:

  • History, Sitemap and colour rules search a captured flow, so both fields always work, on both sides of the exchange.
  • Intercept and extract-rule conditions search the message in flight. header: works at the HTTP request and response gates and in an extract-rule condition, but not for a held WebSocket message, which has no head of its own. body: works for a held WebSocket message and for an extract-rule condition, where the payload is in hand, but not at an HTTP hold gate, because that gate is what decides whether the body gets buffered in the first place.

One deliberate difference between the two, worth knowing before you write a rule:

  • In a query, body: reads a trigram index: fast, but bounded to the first 8 KiB of each side and skipping binary and compressed bodies. body~regex scans the stored bytes instead, with no bound.
  • In a colour rule, body: always scans, and reads the first 64 KiB of each side. Indexing happens after capture and a rule has to paint the row that just arrived, so scanning is the only way to be right; the 64 KiB bound is what keeps a screenful of large bodies from stalling the list. A colour rule therefore paints rows the identical query does not list, but a match past 64 KiB is not painted.

Every body: term, on every surface, reads the bytes as they went over the wire, so none of them finds a string inside a gzipped body. That includes an extract rule's condition, which is evaluated before the response is decoded; only the extraction that follows sees decompressed text. To match on compressed content, match something outside it: a header, the path, or the response size.

Caveats

In a few cases a query result looks complete even though some rows were never checked:

  • A pending flow has no status, duration or response size, so it drops out of both status: and -status: (and the same for dur and respsize).
  • A dropped term widens the query. A value gori cannot read (status:>=foo) is ignored, not refused, but it is named: gori run prints a warning, and the MCP query tools list it in the reply's ignored_terms (pass strict:true to have them refuse the query instead). Check what survived with ql_explain.
  • A bad regex is an error, never silently dropped: body~[ fails the whole query.
  • -body: on a big body keeps a hit past the 8 KiB index bound, because the index never saw it.

Examples

# Errors on one host
gori run history -q 'host:api.example.com status:5xx'

# Slow POSTs mentioning a token
gori run history -q 'method:POST dur:>1s body:token'

# Admin paths, excluding static assets
gori run history -q 'path~/admin/ -path~\.(css|js|png)$'

# Scope a passive scan
gori run probe -q 'host:example.com'