The problem with allowlists
When engineering teams think about protecting their databases from LLM agents, the first solution that comes up is an allowlist: define which operations are allowed (SELECT) and block the rest (DELETE, DROP, UPDATE).
The problem is that this approach blocks on the first token of the query, not on its semantics. And AI agents generate patterns a simple allowlist cannot detect.
An allowlist does not know the difference between
UPDATE users SET name = $1 WHERE id = $2andUPDATE users SET role = 'admin'. To an allowlist, both are "UPDATE": either both pass or both are blocked.
If you block every UPDATE, you break your application. If you allow every UPDATE, you are one ambiguous prompt away from a production disaster.
What an allowlist cannot detect
These are four patterns that get past an operation allowlist without trouble:
1. UPDATE without WHERE
-- Un agente responde a "actualiza el precio del producto"
-- con contexto ambiguo y genera esto:
UPDATE products SET price = 0;
An allowlist that allows UPDATE does not catch this. The query modifies every row of the table: in a catalogue table with 50,000 products, the damage is immediate and, without a backup, irreversible.
2. DELETE with always-true WHERE
-- El LLM fue instruido para "limpiar registros viejos" pero
-- generó una condición trivialmente verdadera:
DELETE FROM sessions WHERE 1=1;
It has a WHERE: it passes any filter that only checks whether a WHERE is present. But WHERE 1=1 is semantically identical to having no WHERE. The engine recognizes predicates whose operands are all literals, like 1=1, which cannot filter any row, and treats them as absent (VERICTO-003).
3. TRUNCATE hidden behind another statement
-- Un agente que encadena pasos en un solo string:
SELECT count(*) FROM orders; TRUNCATE TABLE orders;
The first token is SELECT. An allowlist that only looks at the start of the string lets it through. The parser splits every statement in the string and evaluates all of them: it finds the TruncateStmt and blocks the query (VERICTO-011).
4. DROP TABLE inside a DO block
-- El DROP queda dentro de un bloque PL/pgSQL:
DO $$
BEGIN
DROP TABLE IF EXISTS users;
END $$;
The first token is DO, not DROP. An allowlist that only checks leading tokens lets it through. The body of a DO block is PL/pgSQL, opaque to the SQL parser, so Vericto does not try to guess what is inside: it blocks every DO block with VERICTO-081, because it can hide any DML or DDL.
How AST parsing works against these cases
AST (Abstract Syntax Tree) parsing builds a complete representation of the query's syntax tree. Instead of reading the text left to right, the parser turns the query into a hierarchical data structure where each node has a type, properties and child nodes.
For the query UPDATE products SET price = 0, the AST looks like this:
UpdateStmt
├── relation: "products"
├── targetList:
│ └── ResTarget
│ ├── name: "price"
│ └── val: Integer(0)
└── whereClause: NULL ← el nodo que activa VERICTO-042
Rule VERICTO-042 evaluates one condition: the UpdateStmt has no effective WHERE (missing or always true). If it holds, the query is blocked, whatever table, columns or values it modifies.
For the WHERE 1=1 case, the evaluation goes one step further:
DeleteStmt
├── relation: "sessions"
└── whereClause:
└── A_Expr
├── kind: AEXPR_OP
├── name: "="
├── lexpr: Integer(1)
└── rexpr: Integer(1) ← ambos operandos son literales iguales
→ evaluable estáticamente → siempre true
Vericto's engine recognizes predicates that do not depend on any row because all their operands are literals: 1=1, 'a'='a', WHERE TRUE, NOT FALSE, 1 IN (1, 2). It treats them as a missing WHERE. A predicate that references a column, such as id IS NOT NULL, does filter (it excludes NULLs), so it is not reported.
Aren't database permissions enough?
It is the most common objection: "we just give the agent a database user with SELECT-only permissions".
The problem with this strategy is that it is too restrictive in practice. AI agents that deliver real value need to write: insert events, update statuses, record metrics. A SELECT-only user can do none of that.
And once you grant write permissions for the legitimate operations, you are back to the original problem: the agent can generate an UPDATE or DELETE without a WHERE clause, and database permissions do not distinguish between UPDATE users SET name = $1 WHERE id = $2 and UPDATE users SET role = 'admin'.
Database permissions and AST parsing are complementary layers of defense, not alternatives. Vericto does not replace least privilege in your database: it complements it with a semantic analysis permissions cannot do. And if you want to limit which tables and columns each agent touches, Vericto's agent access allowlists (VERICTO-087) also work on the AST: they count every reference in the query, not just the first token.
Why determinism matters
The alternative to AST parsing for detecting dangerous queries is another ML model that "understands" whether a query is dangerous. This approach has three fundamental problems that make it unsuitable for the security path:
- False negatives: a model trained on historical queries may not know the specific patterns your LLM generates in your domain
- False positives: blocking legitimate queries in production is as damaging to the team as letting destructive queries through
- Not auditable: you cannot put "a model with 99.2% accuracy protects our data" in a SOC 2 report, but you can put "the AST parser deterministically blocks any UpdateStmt with whereClause = NULL"
Determinism is not just a technical property: it is a requirement for the control to be auditable in SOC 2 or ISO 27001 reviews. The same query with the same ruleset produces the same verdict on every run, under any system load. There is no stochastic variance, no model drift, no inference temperature.
How to implement it in <5 minutes
If you have an LLM-to-SQL pipeline on Postgres or MySQL, integrating Vericto is a one-line change in your connection string:
- DATABASE_URL=postgres://user:pass@prod-db.host:5432/mydb
+ DATABASE_URL=postgres://user:pass@localhost:5433/mydb
Your ORM (Prisma, SQLAlchemy, Drizzle, ActiveRecord) does not know there is a proxy in the middle. Vericto intercepts every query, analyzes the AST and, if it is safe, forwards it to your real database; if it is destructive, it blocks it with a native protocol error. The design target is for parsing and rule evaluation to take under 2 ms at p99.
You can explore the 31 rules in the catalogue and their AST conditions in the AST Rules Reference.