30 rules. None uses ML.
All Vericto standard rules and how to configure them.
How rules work
When a query reaches the Vericto proxy, the parser builds the complete AST (Abstract Syntax Tree) of the query. The engine traverses the tree in depth searching for nodes that violate the conditions defined in each active rule.
The process is always the same:
- The SQL query reaches the proxy as plain text
- The engine selects the right parser for the dialect (
pg_queryfor Postgres,sqlparser-rsfor MySQL, Oracle and SQL Server). PostgreSQL and MySQL can be evaluated by the TCP proxy; Oracle and SQL Server, by the HTTP API. - The complete AST is built in memory
- The engine evaluates each active rule against the tree
- If any rule is violated, the query is blocked before reaching the database
Each rule defines exactly five properties:
- Code: unique identifier in the form
VERICTO-XXXorCUSTOM-XXX - AST condition: the node and property evaluated (e.g.
DeleteStmt > WhereClause = NULL) - Dialect(s): one or more SQL dialects the rule applies to
- Severity: one of the 5 CVSS levels — CRITICAL, HIGH, MEDIUM, LOW or INFORMATIONAL
- Suggested safe query: the corrected version of the query shown in the block log
AST parsing adds on average less than 0.4ms to the response time. The total proxy p99 (parsing + rule evaluation + network overhead) is under 2ms.
Severity levels
Vericto uses the 5-level CVSS taxonomy. The default action can be customised per workspace with an EnforcementPolicy.
| Severity | Default action | Behavior | Alerts |
|---|---|---|---|
| CRITICAL | BLOCK | Immediate block of the query. SQLSTATE 42501 to the client. | Real-time alert via webhook and dashboard |
| HIGH | BLOCK | Query blocked, fully recorded in the audit trail. | Real-time alert |
| MEDIUM | FLAG | The query is forwarded upstream. Logged and alerted on, but not blocked. | Alert (FLAG) in dashboard and webhook |
| LOW | MONITOR | The query is forwarded. Recorded in telemetry only, no alert. | Telemetry / audit trail only |
| INFORMATIONAL | MONITOR | The query is forwarded. Informational logging, no action. | Metrics only |
On every plan, the workspace owner and admins can customize the EnforcementPolicy (change the action per severity or turn on observe mode). This is useful during adoption to observe traffic before enabling blocks.
CRITICAL rules
CRITICAL rules detect operations that can cause irreversible loss of massive amounts of data. They are active across all supported dialects unless otherwise indicated.
| Code | Name | AST Condition | Dialects | Suggested safe query |
|---|---|---|---|---|
VERICTO-001 |
DELETE without WHERE | DeleteStmt > WhereClause = NULL |
All | DELETE FROM {tabla} WHERE id = $1 |
VERICTO-003 |
DELETE with always-true WHERE | DeleteStmt > WhereClause = ALWAYS_TRUE (1=1, true) |
All | DELETE FROM {tabla} WHERE id = $1 |
VERICTO-010 |
DROP TABLE / DROP DATABASE | DropStmt (excludes DROP INDEX) |
All | Use versioned migrations (Flyway, Prisma Migrate, Alembic) |
VERICTO-011 |
TRUNCATE TABLE | TruncateStmt (nodo presente) |
All | DELETE FROM {tabla} WHERE created_at < NOW() - INTERVAL '90 days' |
VERICTO-012 |
DROP SCHEMA | DropStmt > ObjectType = SCHEMA |
All | Use versioned migrations |
VERICTO-030 |
UPDATE without WHERE (main tables) | UpdateStmt > WhereClause != Present |
All | UPDATE {tabla} SET {col} = $1 WHERE id = $2 |
VERICTO-042 |
UPDATE without WHERE clause | UpdateStmt > WhereClause = NULL (includes trivial WHERE) |
All | UPDATE {tabla} SET {col} = $1 WHERE id = $2 |
VERICTO-080 |
COPY … TO/FROM PROGRAM | CopyStmt > program = true |
Postgres | Use COPY … TO/FROM STDIN or controlled file paths; never run shell commands from SQL |
VERICTO-081 |
DO anonymous code block | DoStmt (DO $$ … $$) |
Postgres | Move the logic to a reviewed function/migration instead of an anonymous block opaque to the parser |
VERICTO-086 |
SQL text that MySQL and the engine would read differently | Text that cannot be resolved with certainty to a single statement because of how MySQL reads its comments and string escapes | MySQL | Always enforced: it cannot be disabled or downgraded. Send SQL without ambiguous comments or escapes |
HIGH rules
HIGH rules detect high-risk operations that can cause partial data loss or irreversible structural changes.
| Code | Name | AST Condition | Dialects | Suggested safe query |
|---|---|---|---|---|
VERICTO-002 |
DELETE with LIMIT 0 | DeleteStmt > LimitCount = 0 |
MySQL | DELETE FROM {tabla} WHERE id = $1 LIMIT 1 |
VERICTO-013 |
DROP INDEX without IF EXISTS | DropStmt > ObjectType = INDEX & IF_EXISTS = false |
All | DROP INDEX IF EXISTS {index_name} |
VERICTO-015 |
ALTER TABLE DROP COLUMN | AlterTableStmt > AlterTableCmd.subtype = DROP_COLUMN |
All | Use a soft delete with a deleted_at column, or a versioned migration |
VERICTO-016 |
ALTER TABLE RENAME | AlterTableStmt > Rename |
All | Use a migration tool and update all references first |
VERICTO-017 |
ALTER TABLE DROP CONSTRAINT | AlterTableStmt > DropConstraint (FK/PK/CHECK, DROP PRIMARY KEY) |
All | Drop the constraint only in a reviewed migration, after confirming no data or code depends on it |
VERICTO-018 |
ALTER TABLE ALTER COLUMN TYPE | AlterTableStmt > AlterColumnType |
All | Perform the type change in a phased migration (new column + backfill) to avoid a blocking rewrite or lossy cast |
VERICTO-019 |
ALTER TABLE DISABLE TRIGGER / RLS | AlterTableStmt > DisableTrigger | DisableRowSecurity |
All | Do not disable triggers or Row Level Security in production; if unavoidable, do it in a controlled window and re-enable them immediately |
VERICTO-031 |
UPDATE without WHERE nested in CTE | WithClause > UpdateStmt > WhereClause = NULL |
Postgres | Add a WHERE with a specific predicate to the UPDATE inside the CTE |
VERICTO-033 |
DELETE without WHERE in subquery/CTE | WithClause/SubLink > DeleteStmt > WhereClause = NULL |
All | Rewrite as DELETE with explicit JOIN and WHERE |
VERICTO-040 |
INSERT INTO … SELECT without filter | InsertStmt > source = SelectStmt |
All | INSERT INTO {tabla} SELECT ... WHERE <condición> LIMIT N |
VERICTO-070 |
Use of SLEEP() or PG_SLEEP() | FunctionCall > name = sleep|pg_sleep |
All | Remove the SLEEP call from the pipeline |
VERICTO-082 |
GRANT / REVOKE | GrantStmt (GRANT or REVOKE) |
All | Manage privileges through reviewed permission migrations, not from application queries |
VERICTO-083 |
MERGE | MergeStmt |
All | Constrain the MERGE with specific conditions; it can mass-mutate like an UPDATE/DELETE with no effective WHERE |
VERICTO-084 |
CREATE TABLE AS SELECT | CreateTableAsStmt / SELECT … INTO |
All | Add a WHERE/LIMIT to the source to avoid duplicating an entire table |
VERICTO-085 |
Read of a sensitive column | A projection derived from a tagged column (SELECT, RETURNING, COPY … TO) |
All | Decided by each database's sensitive columns: list the columns instead of SELECT * and do not project the tagged ones |
MEDIUM rules
MEDIUM rules detect query patterns that may indicate an AI agent operating without adequate context, or that could degrade database performance.
| Code | Name | AST Condition | Dialects | Suggested safe query |
|---|---|---|---|---|
VERICTO-050 |
SELECT without LIMIT | SelectStmt > LimitCount = NULL |
All | SELECT ... WHERE <condición> LIMIT 1000 |
VERICTO-051 |
SELECT * without WHERE | SelectStmt > TargetEntry = STAR & WhereClause = NULL |
All | SELECT col1, col2 FROM ... WHERE <condición> |
VERICTO-060 |
INSERT without explicit columns | InsertStmt > Cols = NULL |
All | INSERT INTO {tabla} (col1, col2) VALUES ($1, $2) |
VERICTO-061 |
INSERT batch with more than 10k rows | InsertStmt > ValuesList count > 10000 |
All | Split into batches of ≤1,000 rows with multiple INSERT or COPY |
SQL Injection rules
Deterministic detection of SQL injection patterns in LLM-generated queries. Unlike traditional WAFs based on text patterns, Vericto analyzes the AST, making detection immune to obfuscations that preserve semantics.
| Code | Name | AST Condition | Dialects | Why it avoids false positives |
|---|---|---|---|---|
VERICTO-090 |
OR tautology in WHERE | WhereClause > BoolExpr(OR) > always_true_branch |
All | An LLM generating legitimate queries never has a reason to include OR 1=1. They only exist in authentication-bypass attempts. |
VERICTO-090 coverage: applies to WHERE clauses in SELECT, DELETE, and UPDATE. Detects patterns like OR 1=1, OR 'a'='a', OR true, and nested tautologies like AND (condition OR 1=1).
Snowflake rules
Seven rules for Snowflake (vericto-engine 4.0.0). They read only what the Snowflake parser records, so they never fire on another dialect. Snowflake has no TCP proxy: these rules apply in the HTTP API, the CLI in CI/CD and MCP.
| Code | Name | AST Condition | Dialects | Suggested safe query |
|---|---|---|---|---|
VERICTO-091 |
Stage unload or file transfer | COPY INTO @stage | 's3://…' | 'azure://…' | 'gcs://…', GET, PUT |
Snowflake | Load from the stage (COPY INTO table FROM @stage); data unloads go through a reviewed process outside the application |
VERICTO-092 |
External stage or integration | CREATE|ALTER STAGE … URL = | CREDENTIALS, CREATE STORAGE | EXTERNAL ACCESS | API INTEGRATION, CREATE EXTERNAL FUNCTION |
Snowflake | Use an internal stage; integrations and external stages are created in reviewed migrations |
VERICTO-093 |
Share to another account | GRANT … TO SHARE, CREATE SHARE, ALTER SHARE … ADD | SET ACCOUNTS, CREATE LISTING |
Snowflake | Sharing data is an account decision: make it outside application SQL |
VERICTO-094 |
Recovery reduced | DATA_RETENTION_TIME_IN_DAYS = 0, CREATE OR REPLACE TABLE | SCHEMA | DATABASE, INSERT OVERWRITE, UNDROP, ALTER ACCOUNT |
Snowflake | CREATE TABLE IF NOT EXISTS and a retention above 0; in CI it is flagged like a DROP TABLE |
VERICTO-095 |
Warehouse cost | CREATE|ALTER WAREHOUSE … WAREHOUSE_SIZE | MAX_CLUSTER_COUNT | AUTO_SUSPEND = 0, ALTER RESOURCE MONITOR |
Snowflake | Create the warehouse with the defaults or an AUTO_SUSPEND above 0; size changes go in a reviewed migration |
VERICTO-096 |
Code that runs later | CREATE TASK | STREAM | PIPE | PROCEDURE | FUNCTION | ALERT, ALTER TASK … RESUME | MODIFY AS, EXECUTE TASK |
Snowflake | Create tasks and procedures in reviewed migrations; from the application, call them with CALL |
VERICTO-097 |
Role switch | USE ROLE, USE SECONDARY ROLES ALL | <roles> |
Snowflake | Connect with the role the application needs; USE SECONDARY ROLES NONE is not reported |
Custom rules (YAML)
Builder plans and above allow defining custom rules in YAML. You can create rules that block specific operations on particular tables, without touching your application code.
Custom rules are evaluated after built-in rules with the same latency. The YAML has node_type at the top level and the predicates inside condition:
rule: block-update-on-payments
node_type: UpdateStmt
condition:
relation: payments
There are eight condition predicates: relation, where_clause, where_always_true, target_list, has_limit, func_name, object_type and alter_kind. Name, severity and dialect do not belong in the YAML: they are form and API fields.
The full reference — the eight node types, the eight predicates and copy-ready examples — is in the custom rules documentation.
Custom rules are hot-loaded without restarting the proxy. The propagation time is under 5 seconds from when you save the file in the dashboard.
Custom rules are managed from the workspace dashboard. From the CLI you can inspect the effective catalogue with vericto rules list and view a rule's detail with vericto rules show <CODE>.
Code index
| Code | Description | Severity | Default action |
|---|---|---|---|
VERICTO-001 | DELETE without WHERE clause | CRITICAL | BLOCK |
VERICTO-002 | DELETE with LIMIT 0 (MySQL) | HIGH | BLOCK |
VERICTO-003 | DELETE with always-true WHERE (1=1, true) | CRITICAL | BLOCK |
VERICTO-010 | DROP TABLE or DROP DATABASE | CRITICAL | BLOCK |
VERICTO-011 | TRUNCATE TABLE | CRITICAL | BLOCK |
VERICTO-012 | DROP SCHEMA | CRITICAL | BLOCK |
VERICTO-013 | DROP INDEX without IF EXISTS | HIGH | BLOCK |
VERICTO-015 | ALTER TABLE DROP COLUMN | HIGH | BLOCK |
VERICTO-016 | ALTER TABLE RENAME (table or column) | HIGH | BLOCK |
VERICTO-017 | ALTER TABLE DROP CONSTRAINT (FK/PK/CHECK) | HIGH | BLOCK |
VERICTO-018 | ALTER TABLE ALTER COLUMN TYPE | HIGH | BLOCK |
VERICTO-019 | ALTER TABLE DISABLE TRIGGER / RLS | HIGH | BLOCK |
VERICTO-030 | UPDATE without WHERE on main table | CRITICAL | BLOCK |
VERICTO-031 | UPDATE without WHERE inside CTE | HIGH | BLOCK |
VERICTO-033 | DELETE without WHERE in subquery or CTE | HIGH | BLOCK |
VERICTO-040 | INSERT INTO … SELECT without filter | HIGH | BLOCK |
VERICTO-042 | UPDATE without WHERE clause | CRITICAL | BLOCK |
VERICTO-050 | SELECT without LIMIT | MEDIUM | FLAG |
VERICTO-051 | SELECT * without WHERE clause | MEDIUM | FLAG |
VERICTO-060 | INSERT without explicit columns | LOW | MONITOR |
VERICTO-061 | INSERT batch with more than 10k rows | MEDIUM | FLAG |
VERICTO-070 | Use of SLEEP() or PG_SLEEP() | HIGH | BLOCK |
VERICTO-080 | COPY … TO/FROM PROGRAM (shell execution) | CRITICAL | BLOCK |
VERICTO-081 | DO anonymous code block | CRITICAL | BLOCK |
VERICTO-082 | GRANT / REVOKE | HIGH | BLOCK |
VERICTO-083 | MERGE | HIGH | BLOCK |
VERICTO-084 | CREATE TABLE AS SELECT | HIGH | BLOCK |
VERICTO-085 | Read of a sensitive column | HIGH | PER TAG |
VERICTO-086 | SQL text that MySQL and the engine would read differently (MySQL) | CRITICAL | BLOCK |
VERICTO-090 | SQL injection: OR tautology in WHERE (OR 1=1) | CRITICAL | BLOCK |
VERICTO-091 | Stage unload or file transfer (Snowflake) | CRITICAL | BLOCK |
VERICTO-092 | External stage or integration (Snowflake) | CRITICAL | BLOCK |
VERICTO-093 | Share to another account (Snowflake) | CRITICAL | BLOCK |
VERICTO-094 | Recovery reduced (Snowflake) | HIGH | BLOCK |
VERICTO-095 | Warehouse cost (Snowflake) | HIGH | BLOCK |
VERICTO-096 | Code that runs later (Snowflake) | HIGH | BLOCK |
VERICTO-097 | Role switch (Snowflake) | HIGH | BLOCK |