Skip to content

SQL Safety Reference

dbward processes every SQL statement through three safety layers before execution:

  1. Classification — determines the operation type
  2. Review — checks for risky patterns
  3. Risk scoring — calculates overall risk level for auto-approve decisions

Every statement is classified into one of three categories:

  • SELECT
  • SHOW
  • EXPLAIN (without ANALYZE)
  • EXPLAIN ANALYZE on SELECT statements
  • SET (safe session variables only)
  • INSERT, UPDATE, DELETE, MERGE
  • COPY
  • CALL (stored procedures)
  • CREATE TABLE, CREATE VIEW, CREATE INDEX
  • ALTER TABLE
  • SELECT with writable CTE (WITH ... INSERT/UPDATE/DELETE)
  • SELECT with dangerous functions (24 known functions)
  • EXPLAIN ANALYZE on DML statements (actually executes the inner statement)
  • SELECT INTO

DestructiveDdl (subcategory of ExecuteDml)

Section titled “DestructiveDdl (subcategory of ExecuteDml)”

These statements are classified as ExecuteDml but flagged as DestructiveDdl. They pass through the classifier and are controlled by sql_review rules:

  • DROP TABLE ¹
  • DROP VIEW ¹
  • DROP INDEX ¹
  • DROP SEQUENCE ¹
  • TRUNCATE ¹
  • CREATE SEQUENCE ¹

¹ Requires Permission::RequestDdl (request.ddl). Controlled by sql_review rules (default: block for drop_table/truncate, warn for others). --allow-ddl bypasses sql_review blocks. See Break-Glass.

  • DROP SCHEMA/DATABASE/FUNCTION/ROLE
  • CREATE FUNCTION/PROCEDURE/TRIGGER/ROLE/DATABASE
  • GRANT, REVOKE
  • BEGIN, COMMIT, ROLLBACK, SAVEPOINT
  • LOCK TABLE
  • LOAD DATA
  • SET (unsafe variables)
ConditionResult
Parse failureClassified as ExecuteDml (fail-closed: requires approval)
Unknown statement typeClassified as ExecuteDml
Input > 1 MBRejected
> 100 statementsRejected
Multiple SELECT statementsRejected (use single SELECT)
SET + SELECT comboAllowed
NULL bytes in inputRejected

Each rule has a configurable severity: warn, block, or off.

  • block (default for no_where_delete, no_where_update, drop_table, truncate) — rejects the request regardless of workflow (DDL rules ² can be bypassed with --allow-ddl)
  • warn (default for drop_column, not_null_without_default, create_index_not_concurrently, alter_column_type, mixed_ddl_dml, large_in_list, drop_index, drop_view, drop_sequence) — adds a finding to the risk assessment
  • off (default for create_sequence) — rule is disabled
[[sql_review]]
database = "*"
environment = "development"
no_where_delete = "warn"
no_where_update = "warn"
drop_table = "warn"
drop_column = "warn"
not_null_without_default = "warn"
create_index_not_concurrently = "warn"
alter_column_type = "warn"
truncate = "warn"
mixed_ddl_dml = "warn"
large_in_list = "warn"
drop_index = "warn"
drop_view = "warn"
drop_sequence = "warn"
create_sequence = "off"
RuleFires whenRisk
no_where_deleteDELETE without WHERE clauseEntire table deletion
no_where_updateUPDATE without WHERE clauseEntire table overwrite
drop_tableDROP TABLE detectedPermanent data loss
drop_columnALTER TABLE DROP COLUMNColumn data loss
not_null_without_defaultALTER TABLE ADD COLUMN NOT NULL without DEFAULTFails on existing rows
create_index_not_concurrentlyCREATE INDEX without CONCURRENTLY (PostgreSQL)Table lock during build
alter_column_typeALTER COLUMN ... TYPETable rewrite, potential data loss
truncateTRUNCATE TABLEAll data removed
mixed_ddl_dmlDDL and DML in same requestComplex rollback
large_in_listIN (...) with > 100 valuesPerformance concern
drop_indexDROP INDEX detectedIndex removal, query performance impact
drop_viewDROP VIEW detectedView removal, dependent queries break
drop_sequenceDROP SEQUENCE detectedSequence removal, ID generation impact
create_sequenceCREATE SEQUENCE detectedNew sequence creation

² DDL rules (drop_table, drop_column, truncate, create_index_not_concurrently, alter_column_type, not_null_without_default, drop_index, drop_view, drop_sequence, create_sequence) can be bypassed with --allow-ddl. DML safety rules (no_where_delete, no_where_update, large_in_list) and mixed_ddl_dml are never bypassable.


Risk scoring determines whether a request qualifies for auto-approve.

LevelNumericMeaning
Low1Safe (SELECT, safe DDL, small tables)
Medium2Moderate concern
High3Significant risk
Critical4Reserved
Unknown5Cannot assess (schema not synced)
Unavailable6Parse failure
ConditionLevel
SELECT + allow_read_onlyLow
Safe DDL (CREATE TABLE/VIEW/INDEX) + allow_safe_ddlLow
Schema not syncedUnknown
Multi-statement DML (>1 DML)High
DROP or TRUNCATE detectedHigh
≥ 3 review warningsHigh
CASCADE FK + large table (> max_estimated_rows)High
CASCADE FK + small tableMedium
Large table without cascadeMedium
1-2 review warningsMedium
None of the aboveLow

These CREATE/ALTER statements are considered safe regardless of table size:

  • CREATE TABLE (new table, no existing data)
  • CREATE VIEW
  • CREATE INDEX CONCURRENTLY (PostgreSQL only)
  • ALTER TABLE ... ADD COLUMN (PostgreSQL only, no lock on existing rows)

Functions that promote a SELECT to ExecuteDml:

dblink, dblink_exec, dblink_connect, lo_export, lo_import, lo_unlink, pg_read_file, pg_read_binary_file, pg_ls_dir, pg_execute_server_program, copy_to, copy_from, set_config, pg_cancel_backend, pg_terminate_backend, pg_sleep, pg_advisory_lock, pg_advisory_xact_lock, pg_notify, sys_exec, sys_eval, load_file, sleep, benchmark