Safety & Guardrails
Agents make mistakes; guardrails make them cheap. This page is the full contract an agent-driven dbcrust run obeys.
The read-only guard
Section titled “The read-only guard”dbcrust <url> --read-only -c "…" # per rundbcrust config set read_only_default true # house policy; override with --read-only=false--read-only rejects, per backend:
| Backend | Blocked |
|---|---|
| PostgreSQL / MySQL / SQLite / ClickHouse / files | anything 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) |
| MongoDB | everything except a read allowlist (find, findOne, aggregate without $out/$merge, count*, distinct, getIndexes, stats, …) — unknown verbs are rejected by default |
| Elasticsearch | anything 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.
Connect-level hardening
Section titled “Connect-level hardening”Where the driver supports it, read-only is also enforced below the statement guard, on every pooled connection:
- SQLite —
PRAGMA query_only=ONat connect time (the server-side backstop that catches anything the textual guard misses) - PostgreSQL —
default_transaction_read_only=onsession option
Honest limits
Section titled “Honest limits”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.
Limits and timeouts
Section titled “Limits and timeouts”dbcrust <url> --timeout 30 --max-rows 500 -c "…"--timeout SECS— per-run query timeout (0 disables; config:query_timeout_seconds).--max-rows N— auto-LIMITappended to top-levelSELECTs (0 disables; config:default_limit, default 100). A result with exactly N rows may have more behind the limit — agents should treatrow_count == max_rowsas “possibly truncated by LIMIT”.- Output guards cap any rendering at 10,000 rows / 32 MB;
-o jsonreports it via"truncated":true.
Never hang
Section titled “Never hang”--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.
The output contract
Section titled “The output contract”- 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 codesquery_error,connection_error,read_only_violation,file_error,usage_error. - Exit codes:
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | a statement failed (batch stops at first failure) |
| 2 | usage / bad arguments |
| 3 | connection or URL-resolution failure |
| 4 | statement blocked by --read-only |
| 130 | interrupted |
The Python CLI (pip install dbcrust) maps its exceptions to the same codes.
Privacy
Section titled “Privacy”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.