How the rule engine works#

A rule is compiled into a plan once, then evaluated against a lazily built view of each entry, which is why sweeps over a full HAR file stay fast.

Web Debugger lets you write a rule once and apply it across every request in a session, past or live, one entry or hundreds of thousands. This page explains how a rule moves from the text you author in the rule editor to the matches you see highlighted on a request. Use it to write rules that scale, and to predict how the engine reports matches back to you.

The rule lifecycle#

After the engine compiles a rule, each evaluation against a request is cheap. That is why bulk runs across a full HAR file work in real time.

How a rule is compiled#

The engine compiles a valid rule into an executable plan before the rule ever sees a request. The plan keeps the original condition tree, and it adds a fast pre-filter for the literal strings the rule cares about: every quoted needle in a contains(...) and every value in an in(...) list. The engine bundles those literals into a multi-pattern matcher. A single pass over a body or a header value then reports that none of these strings appear anywhere, without a rescan of the bytes for each literal.

This matters in practice. A rule with five contains clauses combined with or does not cost five times as much as a rule with one clause. The engine scans the literals together, and evaluates in detail only the conditions that survive the pre-filter.

The engine caches compiled plans and reuses them. An edit to a rule recompiles it. A run of the same plan against ten thousand entries does not.

How a request becomes evaluable#

Before the engine can evaluate a plan against an entry, it must turn the entry into something the rule can ask questions about. GraphDagger does that lazily, only when you run a rule against the entry. The work is the same for an entry from a live recording and for an entry from a HAR file you loaded.

For each entry, the engine builds a working view that includes:

  • The request method, full URL, host, path, and query parameters.
  • All request and response headers, normalised to lower-case keys so Authorization and authorization match the same field.
  • All request and response cookies, also normalised.
  • The request and response bodies. The engine decompresses gzip-, brotli-, and base64-encoded bodies automatically, and exposes them as text whenever they decode cleanly to UTF-8.
  • The total duration of the entry, in milliseconds.
  • The originator information the browser provided (the page, the script, the redirect target) for graph and provenance use.

Decompression and decoding happen here. A contains(res.body, "...") rule against a gzipped JSON response therefore needs no decompression in advance.

How conditions are evaluated#

When the working view is ready, the engine walks the condition tree in the order you wrote it.

A few details matter here.

  • and is short-circuiting. If the first clause is false, the engine does not evaluate the rest. Put the cheapest clause first in an and chain, for example a method check before a body scan. The engine then skips the body scan on every entry that does not match.
  • or collects every match. The engine evaluates all branches. The rule passes if any branch is true, and the engine reports the highlights from every branch that passes, not only the first one.
  • not does not contribute highlights. The engine discards any matches that a sub-condition under not finds. The rule still reports the matches from the rest of the condition.

For body selectors, the engine first tries to read the body as JSON. If the body parses, contains(res.body, "abc") searches every key and value in the parsed structure, case-insensitively, and records the JSON path of the match. A path-qualified selector resolves directly through the structure, and the engine searches only that subtree. If the body is not JSON, the same query falls back to a plain substring scan over the text.

What a match tells you#

A match against an entry gives you more than a yes-or-no answer. It includes the evidence:

  • The entry index in the source, so you can jump to the matching request in the entries list.
  • The request method, URL, and response status for at-a-glance context.
  • A list of matched JSON paths for body matches, so you can see which field tripped the rule.
  • A breakdown of matched fields. For every selector that contributed to the match, it names the field (URL, headers, cookies, query params, request body, response body, status, duration), the path inside that field if there is one, and the value or substring that matched.
  • Counts of content matches and header matches, so you can tell at a glance whether the match came from a body scan, a header check, or both.

A run of several rules at once produces one of these results per matching rule per entry, grouped by rule.

How sweeps stay fast#

The engine compiles the plan once and builds the entry view once per entry. An evaluation of many rules against many entries is therefore mostly two tight loops: entries on the outside, rules on the inside. The literal pre-filter avoids more body scans than necessary. The evaluator never reparses the rule text, and it never rebuilds an entry view it already has.

A few practical consequences:

  • Order conditions cheap-first. Method and status checks cost almost nothing. Body scans cost more.
  • Path-qualified body selectors beat full-body scans. A single value out of the parsed JSON is far cheaper to resolve than a scan of the whole tree.
  • Bulk runs can be cancelled. If you close the rule editor or start a new sweep, the new run replaces the current one.

What to remember when you write rules#

  • Compiled rules are cheap to reuse. One edit and many runs is the supported workflow. The cost is in the edit, not in the run.
  • The engine decodes compressed and base64-encoded bodies for you wherever the bytes decode cleanly.
  • Headers and cookies are case-insensitive in selectors. You do not have to remember whether the wire format used Set-Cookie or set-cookie.
  • The engine explains matches, it does not only flag them. The result includes which field, which path, and which value contributed: enough to act on without a manual re-read of the request.