CASE STUDY · RUST SELF-AUDIT

XERJ AUDITED ITS OWN RUST.
IT FOUND A REAL 0-DAY.

XERJ indexed its own engine — 197 Rust files, ~201k lines — as a queryable AST and call graph, then hunted for security bugs on top of it. A call-graph query that grep cannot express surfaced an unauthenticated POST /_sql stack-overflow that aborts the whole process. Reaching it cost 10,533 tokens instead of the 1,805,277 it takes to read the source once. The bug is fixed and the fix is proven by crashing — then not crashing — a live server.

FOR·SECURITY ENGINEERS·AI-AGENT BUILDERS·RUST TEAMS
MEASURED · TOKENS TO REACH THE /_sql FINDING · tiktoken cl100k_base
XERJ-ASSISTED READ EVERY FILE
Tokens to the finding 10,533 1,805,277 171×

READ-ALL = EVERY .rs FILE UNDER engine/ TOKENIZED (tiktoken cl100k_base, an LLM-token proxy) · XERJ-ASSISTED = cycle-finder output + the 4 parser functions actually read + the route lookup · NOT A LATENCY BENCHMARK · SOURCES IN docs/case-studies/xerj-self-audit →

THE CONTEXT-WINDOW WALL

THE XERJ ANSWER

100%
FUNCTION COVERAGE
5,095 / 5,095 functions · 197 files · 0 tree-sitter parse errors
1
UNAUTH CRITICAL, FIXED
/_sql stack-overflow → process abort · proven live · depth-guard shipped
22 / 0
UNSAFE BLOCKS · UNSOUND
every unsafe site enumerated and reviewed; all invariants hold

DETECTION QUALITY, MEASURED.

Before trusting a substrate on unknown bugs, test it on known ones. The day before, a PR fixed six real network-reachable vulnerabilities in this exact engine — a labelled test set. We indexed the pre-fix commit and, for each bug, ran the query an auditor would write for that bug class (never its name or line), against the baseline they would otherwise use: grep.

MEASURED · 6 KNOWN BUGS · PRE-FIX TREE
XERJ SUBSTRATE GREP
Bugs found (recall) 6 / 6 4 / 6
Tokens to triage candidates 84,122 2,715,003 32×

A GREP HIT IS A FILE; AN AST HIT IS A FUNCTION · RECALL 6/6 IS IN-SAMPLE — SEE THE CAVEAT BELOW · FULL PER-BUG TABLE IN DETECTION-QUALITY.md →

01
GREP CANNOT FIND MISSING CODE.
Two of the six bugs are an absent limit check and an absent action cap. You cannot grep for code that is not there — grep max_actions_per_bulk on the pre-fix tree returns 0 lines. "Ingest function that never consults a configured limit" is an AST property, not a string.
02
THE FIRST RUN SCORED 3/6 — WORSE THAN GREP.
Publishing only the tuned number would misrepresent the method. The test set exposed three real extractor defects: argument parsing truncated a.len() * b.len() at the first paren, losing the multiplication; taint provenance did not follow one hop into locals; validator detection was presence-only, scoring decorative validation that runs after the delete as "guarded". Fixing those reached 6/6 — so 6/6 is in-sample.
03
THE SIGNALS GO QUIET WHEN FIXED.
Each signal was checked to discriminate, not merely match: the query-parser cycle reads UNGUARDED pre-fix and GUARDED after; guard_after_destructive_op is true for pre-fix restore_snapshot and false after. A detector that fires on patched code is noise.
04
PRECISION IS THE WEAK AXIS.
The true positive lands at rank 6 of 186, 12 of 13, 18 of 27, 19 of 21. This is a strong filter and a mediocre ranker. It narrows 207k lines to a few dozen functions you can read in full — it does not point at the bug.

The genuinely out-of-sample result is the finding below: not in the test set, found on current main, and proven by aborting a live server.

THE QUERY GREP CANNOT WRITE.

A stack overflow in a recursive-descent parser is rarely direct self-recursion — it is a cycle: parse_or_expr → parse_and_expr → parse_condition → parse_or_expr, one turn per (. No single file, function, or regex shows it. The free-function call graph does.

INDEX ONCE · ~5 S · 100% FUNCTION COVERAGE
$ python3 rust_ast_index.py ./engine --out ./ast-out   # 5,095 fns, 87,137 edges, 477 routes
$ python3 ingest.py ./ast-out --url $XERJ              # sent == indexed, asserted
ONE GRAPH QUERY · 186 UNGUARDED CYCLES
$ python3 find_recursion_cycles.py ./ast-out
# strongly-connected components of the call graph,
# flagged where NO member establishes a depth bound.
# ranks the /_sql WHERE-clause parser cycle as UNGUARDED.
TRACE TO THE ROUTE · ONE REQUEST
$ curl -s "$XERJ/rustroutes/_search" -H 'Content-Type: application/json' \
  -d '{"query":{"wildcard":{"path":"*sql*"}},"_source":["method","path","handler"]}'
# POST /_sql -> sql_query (crates/xerj-api/src/router.rs:637) — unauthenticated

PROVEN BY CRASHING IT.

A finding is a claim until you trigger it. One POST /_sql with ~50k nested parens in the WHERE clause aborts the entire server — every tenant, every index — with a stack overflow that no catch_unwind can save.

$ curl -s $XERJ/_sql -d '{"query":"SELECT * FROM t WHERE ((((...50k...))))"}'
# thread 'xerj-rt' has overflowed its stack
# fatal runtime error: stack overflow, aborting
# ... Aborted (core dumped)   — exit 134 (SIGABRT)

The fix mirrors the query_string parser: a bounded recursion depth. After it, the identical payload returns a clean error in ~2 ms and the server stays up.

$ curl -s $XERJ/_sql -d '{"query":"SELECT * FROM t WHERE ((((...50k...))))"}'
# {"error":{"reason":"SQL parse error: WHERE clause nesting exceeds max depth of 64"},"status":400}
# server still answering 200 afterwards.

SECOND PASS: ONE PRINCIPLE.

A deeper pass (24 verified agents) replaced the category checklist with a single test — a function is dangerous when instruction and data share one channel with no separator, and a filter is only real if it strips the instruction power from the data channel. Five stages: flag the dangerous sinks, trace each to user input, map the privilege model, validate every filter for bypasses, and check the state machines for races. Full playbook: METHODOLOGY.md.

8 / 8
CONFIRMED / REFUTED
a 50% cull at the adversarial-verify stage — the refuted half is the point
4 High
CONFIRMED SEVERITY
+ 2 Medium, 1 Low, 1 Info · the 3 app-layer Highs are fixed on the branch, live-proven
19.6×
FEWER TOKENS (FLOOR)
the five grep sweeps touch 153 of 197 files — grepping the stages is reading the codebase

The two sharpest findings are corollary failures, not missing filters. INJ-01: a search-template param was spliced into the query JSON unescaped and re-parsed, so a value could drop the template's own ACL filter — live-proven leaking a private document, now fixed. F-PATH-02: the three filters guarding a snapshot's name are all real and tested; the hole is the un-validated sibling field, the repo location — a snapshot wrote index data outside data_dir, now bounded by a path.repo-style allowlist. Both, plus a session-revocation lost-update, ship fixed with regression tests. Every finding, severity-ranked with its instruction/data analysis: FINDINGS-V2.md.

WHAT THIS DOES NOT SHOW.

01
XERJ DOES NOT FIND BUGS.
The tree-sitter extractor and the call-graph analysis produce the candidates; XERJ stores and queries them at scale. The substrate narrows the search; a human or agent still reads the survivors.
02
THE GRAPH IS NAME-BASED.
Method-call edges are excluded on purpose — resolving .len() by name collapses the graph into one false component. Cycles through trait dispatch or macros are out of reach; the recursion result covers free functions.
03
EVERY CANDIDATE STILL NEEDS A READ.
Blunt category filters over-return (87 for one alloc query); argument-provenance signals sharpen them, and every published finding was read and, where possible, executed. A promising highlight-tag allocation blowup was refuted by testing — the highlighter truncates to a fragment, so the product is bounded — and two unproven traversal leads were not published.
04
ONE PASS, HIGHEST-VALUE LENSES.
This publishes what was proven or fully enumerated — the DoS and the unsafe inventory. The broader auth-flow and business-logic lenses are scoped in the walkthrough, not exhausted here.

READ THE WALKTHROUGH.

The extractor, the ingest script, the recursion-cycle finder, the query cookbook with live hit counts, the ground-truth recall test, and the coverage accounting are all published. Every number reproduces from scratch.

OPEN THE WALKTHROUGH GET THE TOOLING
READY?·REQUEST ACCESS

AUDIT YOUR
OWN RUST.

The same extractor + queries index your crate into XERJ and hunt the same bug shapes — recursion cycles, unbounded allocation, unsafe soundness, path traversal. Want a walkthrough with your team? Leave an email.

We only use this email to send you the binary. Ever. ✓ THANKS. CHECK YOUR INBOX WITHIN 24 HOURS.