Docs / Agent & automation / SQL history

Guide Agent & automation

Query history with SQL

~7 min · SQLite · read-only · Free

The short version. Every request Crusader captures lands in a local SQLite database. crusader sql opens it read-only so you can slice your traffic with plain SELECTs — group by host and status, grep response bodies, find duplicate payloads — without leaving the terminal. crusader sql tables lists the tables, crusader sql schema [table] prints columns, and crusader sql query "<SELECT…>" runs one statement. A guard blocks every write, so the worst a bad query can do is return nothing. The same file opens fine in sqlite3 or any SQLite browser.

01Why query history with SQL

The GUI search bar and crusader history search are fast for "find requests that mention X." But some questions are relational, not full-text: which hosts return the most 500s, grouped by status? which endpoints echo a parameter back verbatim? which responses share a body hash, so I can collapse duplicates? Those are one GROUP BY away in SQL and awkward any other way.

crusader sql exposes the project's history database directly, opened in read-only mode. Every command prints JSON to stdout — {columns, rows, count, limit, truncated} — so it pipes cleanly into jq, a script, or an agent. It is the same data the rest of the app reads; you are just querying it with the full expressiveness of SQLite instead of a search box.

This is plain SELECT-over-your-own-capture data. It is unrelated to the scanner's SQL-injection engine, which attacks targets — different surface entirely. crusader sql never touches a remote host; it only reads the local database file.

02List the tables

Start by seeing what's in the database. tables is the default subcommand, so these are equivalent:

crusader sql
crusader sql tables

That enumerates every table and virtual table in the current project's database — the canonical way to discover what you can query, since the exact set depends on what you've captured and imported (a Burp import, for example, adds extra tables). Use it before assuming a table exists.

03Inspect a schema

Before you write a query, check the columns. schema with no argument dumps every table's definition; pass a table name to scope it:

# columns + types for one table
crusader sql schema exchanges

# every table's schema
crusader sql schema

This is the authoritative source for column names — read it rather than guessing. The exchanges table below lists the columns you'll reach for most often.

04Run a query

Pass a single SELECT as a quoted argument:

crusader sql query "SELECT id, method, host, path, status FROM exchanges ORDER BY id DESC LIMIT 20"

For longer or multi-line statements, read the SQL from a file or stdin instead of fighting shell quoting:

# from a file
crusader sql query --file report.sql

# from stdin (pipe or heredoc)
echo "SELECT host, COUNT(*) AS n FROM exchanges GROUP BY host ORDER BY n DESC" \
  | crusader sql query --stdin

Two flags shape execution:

FlagDefaultWhat it does
--limit N500Caps rows returned. If the result hits the cap, the JSON sets "truncated": true so you know there's more.
--sql-timeout S15Per-query wall-clock budget in seconds — a runaway scan over a huge table fails fast instead of hanging.

The result is JSON: columns (the selected column names in order), rows (arrays of values), count, the effective limit, and truncated. Any BLOB value — for example the raw request/response bytes — is base64-encoded so the output stays valid JSON; decode it on your side if you need the bytes.

05The read-only guard

The connection is opened read-only, and a query guard sits in front of it. It enforces three rules:

  • One statement only. A single query per call — no stacked or chained statements.
  • Reads only. SELECT, WITH (CTEs), and EXPLAIN are allowed, along with a set of safe read-only PRAGMAs (for example schema introspection). Everything that writes or changes structure is rejected: INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, and friends.
  • No side effects. Because the handle itself is read-only, even a query that slipped past the parser couldn't modify the file.

The practical upshot: your capture is safe to hand to a script or an LLM agent for analysis. A malformed or hostile query returns an error or an empty result — it can't corrupt or delete your history.

This guard is about protecting your local data, not about testing a target. Do not confuse crusader sql with the scanner's SQLi modules — they share the letters "SQL" and nothing else.

06The exchanges table

Every captured HTTP exchange — from the proxy, crusader send, Repeater, extensions, and more — is one row in exchanges. These are the columns you'll query most:

ColumnMeaning
idAuto-increment primary key. Monotonic, so ORDER BY id DESC gives newest-first.
timestampCapture time, ISO-8601 UTC.
sourceWhere the row came from — proxy, cli-send, repeater, extension, …
methodHTTP method (GET, POST, …).
urlFull request URL.
host / path / queryParsed URL parts — handy for GROUP BY host or WHERE path LIKE ….
scheme / protocolURL scheme (http/https) and negotiated protocol (e.g. h2/h1).
statusHTTP response status code (integer).
request_headers / response_headersHeaders as JSON text — query with SQLite's json_extract() if needed.
request_body / response_bodyDecoded bodies as text — the columns you LIKE against to grep content.
response_content_typeResponse Content-Type.
size / elapsed_msResponse size in bytes and round-trip time in milliseconds.
body_hashSHA-256 of the body — GROUP BY body_hash to collapse identical responses.
tag / comment / color_tagYour manual annotations from the History view.
request_raw / response_rawRaw on-the-wire bytes (BLOB → base64 in query output).
member_idAttribution in a shared/team workspace (who captured the row).

Full-text search lives in a separate FTS5 virtual table, exchange_fts. That is what the GUI search bar, crusader history search, and the MCP history.search tool query — the GUI does not expose raw SQL. For text matching, FTS is usually faster than a LIKE scan; reach for crusader sql when you need joins, aggregates, or columns FTS doesn't index.

Companion tables

The same database holds more than HTTP exchanges. Expect to see findings (the thin SQLite findings store), endpoint_meta, ws_frames (decoded WebSocket frames), beacon_hits / beacon_sessions / beacon_payloads (out-of-band callbacks), and request_activity. After a Burp import you'll also find burp_import_raw and burp_import_repeater. Always confirm the live set with crusader sql tables — it's the source of truth.

07Example queries

Copy-paste starting points. Each is a single read-only statement.

Newest 20 requests

crusader sql query "SELECT id, method, host, path, status FROM exchanges ORDER BY id DESC LIMIT 20"

Status distribution per host

crusader sql query "SELECT host, status, COUNT(*) AS n FROM exchanges GROUP BY host, status ORDER BY n DESC LIMIT 25"

Server errors that leak the word "error" in the body

crusader sql query "SELECT id, url FROM exchanges WHERE response_body LIKE '%error%' AND status >= 500 LIMIT 50"

Distinct endpoints behind a target host

crusader sql query "SELECT DISTINCT method, path FROM exchanges WHERE host LIKE '%target%' ORDER BY path LIMIT 200"

Duplicate responses by body hash (collapse the noise)

crusader sql query "SELECT body_hash, COUNT(*) AS n, MIN(url) AS sample FROM exchanges WHERE body_hash != '' GROUP BY body_hash HAVING n > 1 ORDER BY n DESC LIMIT 25"

Mind the row cap: results stop at --limit (default 500) and the JSON flags "truncated": true when there's more. Raise --limit deliberately, and lean on aggregates (COUNT, GROUP BY) instead of pulling thousands of raw rows.

08External read-only access

It's a normal SQLite file, so any SQLite client can read it — the sqlite3 CLI, a GUI like DB Browser for SQLite, or your language's SQLite driver. The database lives under your workspace:

ProjectPath
Named project~/.crusader/projects/<slug>/history.db
Default / legacy~/.crusader/history.db

Agent memory is a sibling file (agent_memory.db) in the same directory — a separate database, not part of exchanges. The whole workspace relocates if you set CRUSADER_HOME.

# ad-hoc read-only query with the sqlite3 CLI
sqlite3 ~/.crusader/history.db "SELECT url, status, COUNT(*) FROM exchanges WHERE host LIKE '%target%' GROUP BY url, status ORDER BY 3 DESC LIMIT 20;"

If you open the file with an external tool, treat it as read-only yourself — open it with a read-only flag (e.g. sqlite3 -readonly) and avoid writing while Crusader is running. crusader sql enforces this for you; raw sqlite3 does not.

Want a guide that isn't here yet? Email hello@crusaderproxy.com.