{"slug": "using-an-ast-to-validate-ai-generated-postgresql-before-it-runs", "title": "Using an AST to validate AI-generated PostgreSQL before it runs", "summary": "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.", "body_md": "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()`\n\n.\n\nPrompt 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.\n\nI 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.\n\nSQL 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.\n\nFor example:\n\n```\nSELECT * FROM public.users;\n\nSELECT 1; DELETE FROM public.users;\n\nWITH removed AS (\n  DELETE FROM public.users\n  RETURNING id\n)\nSELECT * FROM removed;\n```\n\nAll three examples contain `SELECT`\n\n, 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.\n\nAn 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.\n\n`sql-guard`\n\nis built around allowlists. You state what a particular feature may use, and the validator checks the generated SQL against that list.\n\nHere is a small policy for an assistant that can look at users and orders:\n\n``` js\nimport { validate } from 'sql-guard';\n\nconst policy = {\n  allowedTables: ['public.users', 'public.orders'],\n  allowedFunctions: ['count', 'lower'],\n};\n\nconst result = validate(\n  'SELECT lower(u.email) FROM public.users AS u',\n  policy,\n);\n\nif (!result.ok) {\n  console.error(result.errorCode, result.violations);\n  // Do not execute the SQL.\n}\n\n// Execute only after result.ok is true.\n```\n\nThe default policy allows one `SELECT`\n\nstatement and no function calls. Tables and functions have to be allowed explicitly. Multi-statement input is disabled by default.\n\nThat makes the initial policy intentionally strict. A request for `public.secret_users`\n\n, `information_schema.tables`\n\n, or `pg_catalog.pg_read_file(...)`\n\nis denied unless the policy names it.\n\nUnqualified names such as `SELECT * FROM users`\n\ncan be ambiguous. By default, `sql-guard`\n\nexpects schema-qualified names. You can choose a simple default schema for a single-schema application:\n\n``` js\nconst policy = {\n  defaultSchema: 'public',\n  allowedTables: ['users', 'orders'],\n};\n```\n\nOr you can provide a resolver when the application needs more control over how names map to schema-qualified tables.\n\nFunctions need the same care. An unqualified `lower(...)`\n\nand `pg_catalog.current_database(...)`\n\nare separate policy entries. This lets a feature allow exactly the form it intends to use.\n\nThe package returns structured violations and an error code rather than just `true`\n\nor `false`\n\n. An application can log a rejected query without attempting to execute it. If a rejected query should interrupt the request immediately, `assertSafeSql()`\n\nthrows a `SqlValidationError`\n\nwith the same details.\n\nThis 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.\n\nThe surrounding controls still matter:\n\nIf any one of those controls fails, the others should limit the damage. The SQL validator is one small boundary in that chain.\n\nThe package currently supports PostgreSQL and requires Node.js 18 or later.", "url": "https://wpnews.pro/news/using-an-ast-to-validate-ai-generated-postgresql-before-it-runs", "canonical_source": "https://dev.to/nur-zaman/using-an-ast-to-validate-ai-generated-postgresql-before-it-runs-1o47", "published_at": "2026-08-25 09:35:14+00:00", "updated_at": "2026-08-25 10:14:25.585362+00:00", "lang": "en", "topics": ["developer-tools", "artificial-intelligence", "large-language-models", "ai-safety"], "entities": ["Nur Zaman", "sql-guard", "PostgreSQL", "TypeScript"], "alternates": {"html": "https://wpnews.pro/news/using-an-ast-to-validate-ai-generated-postgresql-before-it-runs", "markdown": "https://wpnews.pro/news/using-an-ast-to-validate-ai-generated-postgresql-before-it-runs.md", "text": "https://wpnews.pro/news/using-an-ast-to-validate-ai-generated-postgresql-before-it-runs.txt", "jsonld": "https://wpnews.pro/news/using-an-ast-to-validate-ai-generated-postgresql-before-it-runs.jsonld"}}