Using an AST to validate AI-generated PostgreSQL before it runs Nur Zaman has developed sql-guard, a TypeScript package that validates AI-generated PostgreSQL queries using an abstract syntax tree (AST) before execution. The tool enforces allowlists for tables and functions, rejects multi-statement queries by default, and returns structured violations to prevent unsafe database operations. If an LLM is generating PostgreSQL in your application, there is one moment worth treating separately: after the model returns SQL, but before your code calls db.query . Prompt rules are useful. They can make the model more likely to produce the sort of query you want. They do not decide which tables the application is allowed to read, whether multiple statements are acceptable, or whether a function call should run. I have been working on sql-guard https://github.com/nur-zaman/sql-guard , a TypeScript package for that gap. It parses PostgreSQL into an abstract syntax tree AST , checks the tree against an explicit policy, and rejects anything it cannot validate confidently. SQL is structured. A query may have joins, subqueries, aliases, unions, and common table expressions CTEs . Checking raw text can catch an obvious keyword, but it cannot reliably answer what the query actually does. For example: SELECT FROM public.users; SELECT 1; DELETE FROM public.users; WITH removed AS DELETE FROM public.users RETURNING id SELECT FROM removed; All three examples contain SELECT , but they are not equivalent. The second has two statements. The third uses a data-modifying CTE. A validator needs to understand the query structure rather than look for a few strings. An AST makes that possible. It lets the validator inspect statement types, source tables, function calls, and nested expressions. It also means an alias or CTE name cannot conceal the base table being read. sql-guard is built around allowlists. You state what a particular feature may use, and the validator checks the generated SQL against that list. Here is a small policy for an assistant that can look at users and orders: js import { validate } from 'sql-guard'; const policy = { allowedTables: 'public.users', 'public.orders' , allowedFunctions: 'count', 'lower' , }; const result = validate 'SELECT lower u.email FROM public.users AS u', policy, ; if result.ok { console.error result.errorCode, result.violations ; // Do not execute the SQL. } // Execute only after result.ok is true. The default policy allows one SELECT statement and no function calls. Tables and functions have to be allowed explicitly. Multi-statement input is disabled by default. That makes the initial policy intentionally strict. A request for public.secret users , information schema.tables , or pg catalog.pg read file ... is denied unless the policy names it. Unqualified names such as SELECT FROM users can be ambiguous. By default, sql-guard expects schema-qualified names. You can choose a simple default schema for a single-schema application: js const policy = { defaultSchema: 'public', allowedTables: 'users', 'orders' , }; Or you can provide a resolver when the application needs more control over how names map to schema-qualified tables. Functions need the same care. An unqualified lower ... and pg catalog.current database ... are separate policy entries. This lets a feature allow exactly the form it intends to use. The package returns structured violations and an error code rather than just true or false . An application can log a rejected query without attempting to execute it. If a rejected query should interrupt the request immediately, assertSafeSql throws a SqlValidationError with the same details. This check is only about query shape. It does not sanitize values, execute SQL, evaluate row-level security, decide column-level permissions, or notice runtime schema changes. The surrounding controls still matter: If any one of those controls fails, the others should limit the damage. The SQL validator is one small boundary in that chain. The package currently supports PostgreSQL and requires Node.js 18 or later.