Skip to content

Safety & Guardrails

Agents make mistakes; guardrails make them cheap. This page is the full contract an agent-driven dbcrust run obeys.

Terminal window
dbcrust <url> --read-only -c "…" # per run
dbcrust config set read_only_default true # house policy; override with --read-only=false

--read-only rejects, per backend:

BackendBlocked
PostgreSQL / MySQL / SQLite / ClickHouse / filesanything that isn’t a single read-only statement: DML/DDL, data-modifying CTEs, EXPLAIN ANALYZE on writes, multi-statement smuggling (SELECT 1; DROP …), SELECT … INTO, INTO OUTFILE, sequence bumps (nextval/setval), advisory/named locks, PRAGMA name = value assignments (read PRAGMAs stay allowed)
MongoDBeverything except a read allowlist (find, findOne, aggregate without $out/$merge, count*, distinct, getIndexes, stats, …) — unknown verbs are rejected by default
Elasticsearchanything that isn’t SELECT/SHOW/DESCRIBE/EXPLAIN (its SQL interface is read-only by design)

A blocked statement exits with code 4 and (under -o json) a read_only_violation error on stderr.

Where the driver supports it, read-only is also enforced below the statement guard, on every pooled connection:

  • SQLitePRAGMA query_only=ON at connect time (the server-side backstop that catches anything the textual guard misses)
  • PostgreSQLdefault_transaction_read_only=on session option

The statement guard is textual and best-effort. A SELECT can still call a user-defined function with side effects, and MySQL/ClickHouse/MongoDB have no connect-level backstop here. For hard guarantees, do what DBAs have always done:

  • connect with a read-only database role, or
  • point the agent at a replica.

--read-only then remains useful as fast, local, zero-config defense in depth.

Terminal window
dbcrust <url> --timeout 30 --max-rows 500 -c "…"
  • --timeout SECS — per-run query timeout (0 disables; config: query_timeout_seconds).
  • --max-rows N — auto-LIMIT appended to top-level SELECTs (0 disables; config: default_limit, default 100). A result with exactly N rows may have more behind the limit — agents should treat row_count == max_rows as “possibly truncated by LIMIT”.
  • Output guards cap any rendering at 10,000 rows / 32 MB; -o json reports it via "truncated":true.

--no-input (implied whenever stdin is not a terminal) turns every would-be interactive prompt — password entry, session:///recent:///file:///Vault pickers — into an immediate, hint-bearing error instead of a hung process. The pager is disabled in one-shot mode for the same reason.

  • stdout: results only. One rendering per statement; statements without result sets print nothing.
  • stderr: status (“Connecting to saved session…”), warnings, and errors. Under -o json|jsonl, SQL/connection errors are a single JSON line: {"error":{"code":"…","message":"…"}} with codes query_error, connection_error, read_only_violation, file_error, usage_error.
  • Exit codes:
CodeMeaning
0success
1a statement failed (batch stops at first failure)
2usage / bad arguments
3connection or URL-resolution failure
4statement blocked by --read-only
130interrupted

The Python CLI (pip install dbcrust) maps its exceptions to the same codes.

Agent-driven DBCrust talks only to your database. It performs no telemetry and no AI calls. DBCrust’s own AI assistant (??, \ai) is a separate, disabled-by-default feature — see its privacy notes if you enable it.