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:proxykeeps it,src:goridoes 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
proxywould have invented a fact no capture produced. They show—in the SRC column, and bothsrc:proxyand-src:proxyskip them, the way a Pending flow falls out ofstatus: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
slens is switched on. A filter term is a question, not a mode, soscope:inmeans the same thing either way. (With the lens ON, it is redundant, since the lens already ANDs the same predicate over your query, andscope:outthen 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:outdoes not mean "everything" in that state, which is also why-scope:in, a negated never-match, is not the same asscope:outon a project with no scope rules.ql_explainreportsscope_rules_configured: falseand warns, andgori run history deleterefuses 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 fromgori run sitemap --in-scope, which selects whole HOSTS (a host is kept if any of its traffic is in scope), so ascope:inquery 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.
ANDmay also be written out. ORmatches either side.NOTand a-prefix both negate.- Parentheses group. Precedence is
NOTthenANDthenOR. - 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 historyand MCPql_explainthen 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 itsparams,jsandexportverbs) andgori run proberefuse it, name the nearest real field, and exit non-zero.--lenientsearches the token as text instead (what every surface used to do silently:methd:GETmatched 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~regexscans 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 fordurandrespsize). - A dropped term widens the query. A value gori cannot read (
status:>=foo) is ignored, not refused, but it is named:gori runprints a warning, and the MCP query tools list it in the reply'signored_terms(passstrict:trueto have them refuse the query instead). Check what survived withql_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'