Deze handleiding beschrijft de methode die daadwerkelijk werkt: structurele beperking tot SELECT, gesloten witte lijst met functies, verplichte rijlimiet en vergrendeling van het opgevraagde schema. Elke stap is afhankelijk van de validator die feitelijk is geïmplementeerd in de NL2SQL-engine van Aurabase, een mogelijkheid van de native AI ingebouwd in debackend, en niet van een service van derden die achteraf wordt samengevoegd. Als het onderwerp nieuw voor je is, legt ons overzicht van NL2SQL de basis, en laat de stap-voor-stap tutorial zien hoe je het volledige eindpunt bouwt.
De essentie
- Prompt engineering (“genereert alleen SELECT”) is geen veiligheidscontrole: een model kan hallucineren, zich laten leiden door een dubbelzinnige vraag of eenvoudigweg de instructies negeren.
- De geldigheid ervan is structureel: een parser construeert de syntactische boom (AST) van het verzoek en wijst standaard alles af dat niet expliciet is geautoriseerd.
- Vier concrete lagen beperken het risico: strikte SELECT (noch subquery, noch CTE, noch UNION), gesloten witte lijst van tien functies,
LIMITverplicht en beperkt, geblokkeerde toegang tot de systeemcatalogus en niet-tenantschema's. - Het opgevraagde schema moet afkomstig zijn van de server en nooit van een veld in het clientverzoek: anders weerhoudt niets een beller ervan zijn eigen schema op te geven om de validatie te omzeilen.
- Bij Aurabase wordt deze validator (Rust krat
sqlparser) getest met vijandige gevallen gedocumenteerd in de code: verboden functies verborgen inFILTER, in een intra-aggregaatORDER BYof inOFFSET.
Waarom blokkeert een instructie in de systeemprompt niets?
Een systeemprompt met de tekst "genereert alleen SELECT-query's" is een voorkeur, geen barrière. Het model respecteert dit meestal omdat het is getraind om de instructies op te volgen, niet omdat een technische beperking het fysiek verhindert iets anders te schrijven. Twee soorten mislukkingen maken dit vertrouwen in de productie onvoldoende.
De eerste komt voort uit de vraag zelf. Een gebruiker, met slechte bedoelingen of eenvoudigweg creatief in zijn formulering, kan de vraag zo richten dat het model in de richting van SQL wordt geduwd die hij niet had mogen schrijven: een join met een gevoelige tabel, een filter dat de verwachte logica omzeilt, een systeemfunctieaanroep. Het model maakt geen onderscheid tussen een legitieme vraag en een vraag die bedoeld is om deze te manipuleren.
De tweede vereist geen boosaardigheid. Een model kan een tabelnaam hallucineren, de LIMIT vergeten waar de prompt om vroeg, of een SELECT * genereren zonder enige beperking voor een grote tabel. Het resultaat is in beide gevallen hetzelfde: potentieel dure of opdringerige SQL, die het promptfilter heeft gepasseerd en op het punt staat te worden uitgevoerd op een echte database.
De systeemprompt blijft nuttig, maar stuurt het model het grootste deel van de tijd naar het juiste resultaat. Maar een bordje ‘geen toegang’ houdt niemand tegen die niet kan lezen, of die besluit het te negeren. Je hebt een gesloten deur achter nodig, niet alleen een paneel vooraan.
Parseer de gegenereerde SQL in een syntaxisboom, nooit in een onbewerkte tekenreeks
De eerste verdedigingslinie is het ontleden van de SQL die door het model wordt geproduceerd met een echte parser voor het doeldialect, en vervolgens het valideren van de resulterende structuur, en niet van de onbewerkte tekst. Een zoekopdracht naar verboden woorden in de tekenreeks ("DROP", "DELETE", ";") wordt triviaal omzeild: ander hoofdlettergebruik, commentaar ingevoegd in het midden van een trefwoord, getypte aanhalingstekens. Een syntaxisboom beschrijft op ondubbelzinnige wijze wat de query feitelijk doet.
Aurabase implementeert deze stap met de Rust-krat sqlparser en zijn dialect PostgreSqlDialect. Zelfs vóór het parseren verwerpt een eerste lexicale filter twee constructies die moeilijk correct te redeneren zijn zodra ze zich in de boom bevinden: dollarcitaten ($$...$$), waarmee willekeurige inhoud in een tekenreeks kan worden verborgen, en commentaar met meerdere regels (/* */), waarmee het echte einde van een instructie kan worden verborgen.
Deze afwijzing van multi-statements alleen al blokkeert de meest bekende vorm van SQL-injectie door stapelen: SELECT * FROM users; DROP TABLE users;--. De parser retourneert slechts één bruikbare instructie, de tweede wordt eenvoudigweg nooit bereikt, ongeacht hoe deze in de oorspronkelijke vraag is verwoord.
Structureel beperken tot een eenvoudige SELECT
Zodra de boomstructuur is verkregen, is de breedste validatie het accepteren van slechts één type rootnode, een query (Statement::Query), en het afwijzen van al het andere: INSERT, UPDATE, DELETE, DROP, CREATE, ALTER. Het is niet langer een prompte instructie, het is een voorwaarde voor het type van het ontlede object, die geen enkele bekwame vraagstelling kan omzeilen.
Zelfs binnen een SELECT blijven verschillende constructies gevaarlijk en verdienen ze hun eigen expliciete afwijzing:
| Bouw afgewezen | Waarom is het gevaarlijk? |
|---|---|
| CTE / MET | Kan extra onbedoelde logica aan elkaar koppelen vóór de laatste SELECT. |
| Subquery's, UNION / INTERSECT / BEHALVE | Vergroot de oppervlakte van wat een enkele vraag in een enkele query kan doen. |
| SELECTEER...INTO | Creëert een tabel: schrijven vermomd als lezen. |
| VOOR UPDATE / VOOR DELEN | Installatie van sloten, risico op conflicten met productieverkeer. |
| Tabelfuncties (generate_series, pg_read_file...) | Systeemtoegang of Denial of Service via lijnen die op aanvraag worden gegenereerd. |
Een testcase uit de repository illustreert concreet het laatste punt: SELECT * INTO backup FROM users wordt afgewezen, ook al bevat het geen zichtbaar schrijfwoord of verdachte functie. De vorm van het verzoek is voldoende om het te diskwalificeren.
Functie witte lijst, geen zwarte lijst
Een zwarte lijst met verboden functies (pg_sleep, pg_read_file, dblink...) vereist dat elke gevaarlijke functie één voor één wordt geanticipeerd, terwijl Postgres er honderden blootlegt. Een witte lijst keert de bewijslast om: er zijn slechts tien functies geautoriseerd, count, sum, avg, min, max, lower, upper, coalesce, date_trunc, now. Al het andere is standaard niet toegestaan, inclusief een legitieme functie waarvan nog niemand heeft gedacht deze toe te voegen.
Eén enkele validatiepas is niet altijd voldoende. Bij een structurele doorloop van de boom worden de ingangspunten één voor één vermeld (projectie, WHERE, JOIN, GROUP BY...), en je vergeet er gemakkelijk één: een verboden functie kan worden verborgen in een FILTER (WHERE pg_sleep(10) IS NOT NULL)-clausule, in een intra-geaggregeerde ORDER BY (sum(id ORDER BY pg_sleep(10))), in WITHIN GROUP, DISTINCT ONof OFFSET.
De Aurabase-validator voegt daarom een tweede uitgebreide passage toe, die alle uitdrukkingen in de boom doorloopt, waar ze zich ook bevinden, onafhankelijk van het structurele pad. Het is een veronderstelde diepgaande verdediging: als de eerste pass een zaak mist, haalt de tweede de achterstand in.
De geretourneerde regels zijn begrensd: LIMIT verplicht en afgedekt
SELECT * blijft geautoriseerd, het is handig voor datamining. Het risico is niet het grootste risico, maar het ontbreken van een plafond voor een door een model geschreven query: een slecht geformuleerde vraag kan een hele tabel terugbrengen, met de geheugenkosten en responstijd die dat met zich meebrengt.
Aurabase hanteert een eenvoudige en transparante regel. Als de gegenereerde SQL geen LIMITheeft, voegt de server er één toe (standaard 100 regels, waarde aangekondigd aan het model in de systeemprompt). Als de SQL een LIMIT aanvraagt die verder gaat dan een harde cap (standaard 1000 rijen), wordt de query expliciet geweigerd in plaats van stilletjes verminderd. Beide waarden zijn configureerbaar aan de serverzijde (AI_NL2SQL_DEFAULT_LIMIT, AI_NL2SQL_MAX_LIMIT), en de server weigert zelfs te starten als de fout het plafond overschrijdt.
Het weigeren in plaats van stilzwijgend bezuinigen heeft een direct belang: een plafond dat zonder dit te zeggen wordt toegepast, zou de beller de illusie geven dat zijn verzoek is gehonoreerd, terwijl het resultaat zou zijn ingekort zonder dat hij het wist. limit_injected zegt altijd of de waarde afkomstig is van het model of de server.
Toegang tot het schema vergrendelen: systeemcatalogus en cross-schema
Twee verschillende lekken bedreigen een NL2SQL-engine die is verbonden met een echte database: toegang tot de Postgres-systeemcatalogus en toegang tot een schema dat niet van de beller is. Beide blokkeren bij validatie, ongeacht eventueel RLS-beleid dat stroomafwaarts is geplaatst.
pg_catalog maakt altijd deel uit van search_path, wat betekent dat een niet-gekwalificeerde naam zoals pg_authid of pg_stat_activity er rechtstreeks toegang toe heeft, zonder een voorvoegsel. De Aurabase-validator blokkeert elke naam die begint met pg_, evenals information_schema en het interne schema aura_console, ongeacht of deze gekwalificeerd is of niet.
Op een naam met twee componenten (schema.table) is alleen het schema van het aanroepende project toegestaan; elke andere waarde wordt afgewezen. Een naam met drie of meer componenten wordt automatisch geweigerd. Deze grens op het gegenereerde queryniveau is een aanvulling op de isolatie op databaseniveau die wordt beschreven in ons artikel overisolatie van meerdere tenants: de ene voorkomt dat de gegenereerde SQL zich richt op een ander schema, de andere voorkomt dat de verbinding zelf een andere database bereikt. Het een vervangt het ander niet.
Laat de client het opgevraagde schema nooit opnieuw definiëren
Er ligt een discrete valstrik op de loer voor elke NL2SQL API die een parameter accepteert die het schema of de tabellen beschrijft die zijn toegestaan in de query van de klant. Als dezelfde parameter wordt gebruikt om de prompt samen te stellen en de uitvoer-SQL te valideren, kan een aanroeper liegen over wat is toegestaan, en de validatie valideert vervolgens op basis van deze leugen in plaats van op basis van de realiteit van de database.
Aurabase onderzoekt bij elke aanroep het daadwerkelijke projectbasisschema, met een korte cache van dertig seconden voor prestaties, en wijst expliciet alle schema, allowed_schemaof schema_context velden af die in de hoofdtekst van het verzoek worden verzonden, in plaats van deze te accepteren en vervolgens stilletjes te overschrijven. Het verschil doet ertoe: een veld dat wordt geaccepteerd en vervolgens genegeerd, geeft de illusie van controle die niet bestaat; een geweigerd veld zegt het meteen.
Audit uw eigen NL2SQL-pijplijn vóór productie
Of u nu Aurabase gebruikt of uw eigen pijplijn bouwt bovenop een generieke LLM, de volgende punten behandelen wat het vaakst wordt gemist.
Als u de validator zelf schrijft
- Parseer SQL met een echte parser voor uw exacte dialect, nooit met patroonmatching in een string.
- Gebruik een standaardafwijzing: elk type knooppunt en elke functie die niet expliciet is geautoriseerd, moet worden geweigerd, niet alleen de gevaarlijke gevallen die al zijn geïdentificeerd.
- Accepteer slechts één instructie per query. Dit is de eenvoudigste afwijzing tegen het stapelen van query's.
- Test de validator met echte tegenstrijdige gevallen (functie verboden in FILTER, in een intra-geaggregeerde ORDER BY, in OFFSET), niet alleen met voor de hand liggende gevallen.
- Voer ondanks alles de gevalideerde SQL uit met een Postgres-rol met verminderde bevoegdheden op het verwachte schema: de validator beperkt de vorm van de query, de rol beperkt wat deze fysiek kan bereiken als een case u is ontgaan.
Als u een NL2SQL-framework van derden evalueert
- Vraag expliciet of de validatie structureel is (AST) of slechts een promptinstructie: het antwoord verandert alles.
- Controleer of er standaard een line cap wordt toegepast en niet alleen als best practice op uw kosten wordt gedocumenteerd.
- Controleer of het schema dat voor validatie wordt gebruikt, door de API-client kan worden geleverd, waardoor precies de hierboven beschreven fout opnieuw zou worden geopend.
- Vergelijk verschillende tools op dit specifieke criterium voordat u een keuze maakt: onze vergelijking van NL2SQL-tools geeft aan wat de benaderingen onderscheidt die beschikbaar zijn in 2026.
De validator vermindert het risico, maar vervangt RLS niet
Een solide AST-validator vermindert het risico bij de bron: de SQL die uw database bereikt heeft al een bekende en begrensde vorm. Het vervangt echter niet het RLS-beleid voor uw gevoelige tabellen, dat bepaalt welke rijen een bepaalde gebruiker mag zien. De twee lagen beantwoorden verschillende vragen: de validator beperkt de vorm van de gegenereerde query, de RLS beperkt de gegevens die deze voor een specifieke gebruiker kan retourneren. Houd beide actief, zelfs als de een overbodig lijkt ten opzichte van de ander.
NL2SQL behandelt gestructureerde vragen over uw tabellen. Voor vragen over ongestructureerde inhoud, documenten, notities en tickets volgt de eigen RAG van Aurabase een vergelijkbare beveiligingslogica, gedetailleerd beschreven in onze RAG-pijplijn-tutorial op pgvector.