Safe text-to-SQL: let an LLM query your database without writing your SQL

By Amtry · Updated 2026-11-11

Text-to-SQL means letting a user ask a question in plain language and getting a database query back. Large language models made it easy to build, and just as easy to build unsafely. This page explains where the risks come from, which common defenses fall short, and a structural approach that removes most of the attack surface instead of trying to detect it.

Why LLM-written SQL is risky

Common defenses, and where they fall short

These are worth having. The problem is that each one tries to catch a bad query after the model has written it.

A structural approach: the model fills in a plan, code writes the query

QueryForge changes who writes the query. The model never produces SQL. It fills in a typed query structure (an abstract syntax tree) limited to the fields and operators that your configuration registers. After that it is ordinary code:

natural language
   ↓  AI planner (model + your config)
typed query plan, validated
   ↓  generators (plain code: no AI, no network)
SQL / MongoDB / Elasticsearch, parameterized

For example, the request “cancelled orders over $200 from the last 30 days, newest first” compiles to a parameterized statement, with the values passed separately:

SELECT status, created_at, amount, customer_name
FROM orders
WHERE (status = $1 AND amount > $2 AND created_at >= $3)
ORDER BY created_at DESC
LIMIT 50

args: ["CANCELLED", 200, <now − 30 days>]

What the compiler guarantees

Tenant scope belongs to your application

In a multi-tenant product, the filter that limits a user to their own rows should not be a decision the model makes. In QueryForge, your application supplies the scope (a tenant ID, a user ID) and the filter is AND-ed onto the query after the model answers, so the model never learns that the field exists. With policy.requiredScope set, a query that arrives without the tenant filter is refused instead of running unscoped.

What this does not do

Try it

You can describe your data in a configuration file (the config builder makes one in your browser), then call it from Go, Python or Java. The documentation explains every key, and the source is on GitHub under the Apache-2.0 license.

Try the playgroundText to SQL in Go →GitHub