W tym przewodniku opisano szczegółowo metodę, która faktycznie działa: ograniczenie strukturalne do SELECT, zamknięta biała lista funkcji, obowiązkowe ograniczenie wiersza i blokowanie kwestionowanego schematu. Każdy krok opiera się na walidatorze faktycznie zaimplementowanym w silniku NL2SQL Aurabase, możliwościach jego natywnej sztucznej inteligencji wbudowanej w backend, a nie na usłudze strony trzeciej połączonej po fakcie. Jeśli temat jest dla Ciebie nowy, nasz przegląd NL2SQL kładzie podstawy, a samouczek krok po kroku pokazuje, jak zbudować kompletny punkt końcowy.
Najważniejsze
- Szybka inżynieria („generuje tylko SELECT”) to , a nie kontrola bezpieczeństwa: model może mieć halucynacje, kierować się niejednoznacznym pytaniem lub po prostu ignorować instrukcje.
- Sprawdzanie poprawności ma charakter strukturalny: parser konstruuje drzewo syntaktyczne (AST) żądania i domyślnie odrzuca wszystko, co nie jest wyraźnie autoryzowane.
- Cztery konkretne warstwy ograniczają ryzyko: ścisły SELECT (ani podzapytanie, ani CTE, ani UNION), zamknięta biała lista dziesięciu funkcji,
LIMITobowiązkowy i ograniczony, zablokowany dostęp do katalogu systemu i schematów niedzierżawionych. - Schemat, którego dotyczy zapytanie, musi pochodzić z serwera , nigdy z pola w żądaniu klienta: w przeciwnym razie nic nie stoi na przeszkodzie, aby osoba wywołująca dostarczyła własny schemat w celu ominięcia sprawdzania poprawności.
- W Aurabase ten walidator (Rust crate
sqlparser) jest testowany z kontradyktoryjnymi przypadkami udokumentowanymi w kodzie: zabronione funkcje ukryte wFILTER, w wewnątrzzagregowanymORDER BYlub wOFFSET.
Dlaczego instrukcja w podpowiedzi systemowej niczego nie blokuje?
Monit systemowy z informacją „generuje tylko zapytania SELECT” jest preferencją, a nie barierą. Model szanuje to przez większość czasu, ponieważ został przeszkolony, aby postępować zgodnie z instrukcjami, a nie dlatego, że ograniczenia techniczne fizycznie uniemożliwiają mu napisanie czegokolwiek innego. Dwie klasy awarii sprawiają, że ta pewność jest niewystarczająca w produkcji.
Pierwsze wynika z samego pytania. Użytkownik, który ma złe intencje lub jest po prostu kreatywny w swoim sformułowaniu, może skierować pytanie w taki sposób, aby popchnąć model w kierunku SQL, którego nie powinien pisać: złączenie z wrażliwą tabelą, filtr omijający oczekiwaną logikę, wywołanie funkcji systemowej. Model nie rozróżnia pomiędzy pytaniem uzasadnionym a pytaniem mającym na celu manipulację nim.
To drugie nie wymaga złośliwości. Model może mieć halucynacje związane z nazwą tabeli, zapomnieć LIMIT, o które prosił monit, lub wygenerować SELECT * bez żadnych ograniczeń w przypadku dużej tabeli. Rezultat jest taki sam w obu przypadkach: potencjalnie kosztowny lub natrętny SQL, który przeszedł filtr podpowiedzi i ma zostać wykonany na prawdziwej bazie danych.
Podpowiedź systemowa pozostaje przydatna, w większości przypadków kieruje model w kierunku właściwego wyniku. Ale znak „zakaz dostępu” nie powstrzymuje nikogo, kto nie umie czytać lub postanawia go zignorować. Potrzebujesz zamkniętych drzwi z tyłu, a nie tylko panelu z przodu.
Przetwarzaj wygenerowany kod SQL w drzewo składni, nigdy w surowy ciąg znaków
Pierwszą linią obrony jest przeanalizowanie kodu SQL wygenerowanego przez model za pomocą prawdziwego parsera dla docelowego dialektu, a następnie sprawdzenie wynikowej struktury, a nie surowego tekstu. Wyszukiwanie zabronionych słów w ciągu znaków („DROP”, „DELETE”, „;”) jest w prosty sposób pomijane: inna wielkość liter, komentarz wstawiony w środku słowa kluczowego, wpisany cudzysłów. Drzewo składni jednoznacznie opisuje, co faktycznie robi zapytanie.
Aurabase realizuje ten krok za pomocą skrzynki Rust sqlparser i jej dialektu PostgreSqlDialect. Nawet przed analizą pierwszy filtr leksykalny odrzuca dwie konstrukcje, które są trudne do prawidłowego uzasadnienia po umieszczeniu w drzewie: cudzysłów w dolarach ($$...$$), który może ukryć dowolną treść w ciągu znaków, oraz komentarze wielowierszowe (/* */), które mogą ukryć prawdziwy koniec instrukcji.
To odrzucenie samych wielu instrukcji blokuje najbardziej znaną formę wstrzykiwania SQL poprzez układanie: SELECT * FROM users; DROP TABLE users;--. Parser zwraca tylko jedną wykonalną instrukcję, drugiej po prostu nigdy nie osiąga, niezależnie od tego, jak jest sformułowana w pierwotnym pytaniu.
Ogranicz strukturalnie do prostego SELECT
Po uzyskaniu drzewa najszersza walidacja polega na zaakceptowaniu tylko jednego typu węzła głównego, zapytania (Statement::Query) i odrzuceniu wszystkich pozostałych: INSERT, UPDATE, DELETE, DROP, CREATE, ALTER. Nie jest to już szybka instrukcja, jest to warunek dotyczący rodzaju analizowanego obiektu, którego żadne umiejętne sformułowanie pytania nie jest w stanie obejść.
Nawet w ramach SELECT kilka konstrukcji pozostaje niebezpiecznych i zasługuje na własne wyraźne odrzucenie:
| Konstrukcja odrzucona | Dlaczego jest to niebezpieczne? |
|---|---|
| CTE / Z | Można połączyć dodatkową niezamierzoną logikę przed końcowym WYBIEREM. |
| Podzapytania, UNION / INTERSECT / EXCEPT | Rozszerza obszar możliwości pojedynczego pytania w jednym zapytaniu. |
| WYBIERZ...W | Tworzy tabelę: pisanie zamaskowane jako czytanie. |
| DO AKTUALIZACJI / DO UDOSTĘPNIENIA | Montaż śluz, ryzyko rywalizacji z ruchem produkcyjnym. |
| Funkcje tabelowe (generate_series, pg_read_file...) | Dostęp do systemu lub odmowa usługi za pośrednictwem linii generowanych na żądanie. |
Przypadek testowy pobrany z repozytorium konkretnie ilustruje ostatni punkt: SELECT * INTO backup FROM users zostaje odrzucony, mimo że nie zawiera ani widocznego słowa kluczowego do zapisu, ani podejrzanej funkcji. Już sama forma żądania jest wystarczająca, aby je zdyskwalifikować.
Biała lista funkcji, a nie czarna lista
Czarna lista zabronionych funkcji (pg_sleep, pg_read_file, dblink...) wymaga przewidywania każdej niebezpiecznej funkcji jedna po drugiej, podczas gdy Postgres ujawnia kilkaset z nich. Biała lista odwraca ciężar dowodu: autoryzowanych jest tylko dziesięć funkcji, count, sum, avg, min, max, lower, upper, coalesce, date_trunc, now. Wszystko inne jest domyślnie zabronione, w tym jedna uzasadniona funkcja, której nikt jeszcze nie pomyślał o dodaniu.
Nie zawsze wystarczy jedno przejście walidacyjne. Strukturalne przechodzenie przez drzewo zawiera listę punktów wejścia jeden po drugim (projekcja, WHERE, JOIN, GROUP BY...) i łatwo o jednym zapomnieć: zabronioną funkcję można ukryć w klauzuli FILTER (WHERE pg_sleep(10) IS NOT NULL), w wewnątrzzagregowanej klauzuli ORDER BY (sum(id ORDER BY pg_sleep(10))), w WITHIN GROUP, DISTINCT ONlub OFFSET.
Dlatego walidator Aurabase dodaje drugi wyczerpujący przebieg, który przechodzi przez wszystkie wyrażenia w drzewie, gdziekolwiek się znajdują, niezależnie od ścieżki strukturalnej. Jest to zakładana głęboka obrona: jeśli pierwsze podanie nie trafi w sedno, drugie go dogania.
Powiązane zwrócone linie: LIMIT obowiązkowe i ograniczone
SELECT * pozostaje autoryzowany, jest przydatny do eksploracji danych. Ryzyko nie jest gwiazdą, lecz brakiem pułapu zapytania napisanego przez model: źle sformułowane pytanie może zwrócić całą tabelę, co wiąże się z kosztem pamięci i czasem odpowiedzi.
Aurabase stosuje prostą i przejrzystą zasadę. Jeśli wygenerowany kod SQL nie zawiera LIMIT, serwer dodaje go (domyślnie 100 linii, wartość ogłaszana modelowi w wierszu poleceń). Jeśli SQL żąda LIMIT przekraczającego limit (domyślnie 1000 wierszy), zapytanie jest jawnie odrzucane, a nie dyskretnie redukowane. Obie wartości są konfigurowalne po stronie serwera (AI_NL2SQL_DEFAULT_LIMIT, AI_NL2SQL_MAX_LIMIT), a serwer nawet odmawia uruchomienia, jeśli błąd przekroczy pułap.
Odmowa, a nie ciche ograniczenie, ma bezpośredni interes: zastosowanie sufitu bez ostrzeżenia dałoby dzwoniącemu złudzenie, że jego prośba została spełniona, podczas gdy wynik zostałby skrócony bez jego wiedzy. limit_injected zawsze mówi, czy wartość pochodzi z modelu, czy z serwera.
Zablokuj dostęp do schematu: katalogu systemowego i schematu krzyżowego
Silnikowi NL2SQL podłączonemu do prawdziwej bazy danych zagrażają dwa różne wycieki: dostęp do katalogu systemu Postgres oraz dostęp do schematu, który nie należy do wywołującego. Obydwa blokują po zatwierdzeniu, niezależnie od jakichkolwiek zasad RLS umieszczonych poniżej.
pg_catalog jest zawsze częścią search_path, co oznacza, że niekwalifikowana nazwa, taka jak pg_authid lub pg_stat_activity, uzyskuje do niej bezpośredni dostęp, bez prefiksu. Walidator Aurabase blokuje dowolną nazwę rozpoczynającą się od pg_, a także information_schema i schemat wewnętrzny aura_console, niezależnie od tego, czy jest ona kwalifikowana czy nie.
W przypadku nazwy dwuskładnikowej (schema.table) dozwolony jest tylko schemat projektu wywołującego, każda inna wartość jest odrzucana. Imię składające się z trzech lub więcej elementów jest automatycznie odrzucane. Ta granica na poziomie wygenerowanego zapytania stanowi dodatek do izolacji na poziomie bazy danych szczegółowo opisanej w naszym artykule na tematizolacji wielu dzierżawców: jedna zapobiega skierowaniu wygenerowanego SQL na inny schemat, druga zapobiega dotarciu samego połączenia do innej bazy danych. Żadne z nich nie zastępuje drugiego.
Nigdy nie pozwól klientowi na przedefiniowanie kwestionowanego schematu
Na dowolny interfejs API NL2SQL, który akceptuje parametr opisujący schemat lub tabele dozwolone w zapytaniu klienta, czyha dyskretna pułapka. Jeśli ten sam parametr zostanie użyty do skonstruowania podpowiedzi i sprawdzenia wyjściowego kodu SQL, osoba wywołująca może kłamać na temat tego, co jest dozwolone, a następnie weryfikacja sprawdzana jest w oparciu o to kłamstwo, a nie o rzeczywistość bazy danych.
Aurabase introspekuje rzeczywisty schemat podstawowy projektu przy każdym wywołaniu, korzystając z krótkiej trzydziestosekundowej pamięci podręcznej zapewniającej wydajność i jawnie odrzuca (błąd 400) wszelkie pola schema, allowed_schemalub schema_context wysłane w treści żądania, zamiast akceptować je, a następnie po cichu nadpisywać. Różnica ma znaczenie: pole zaakceptowane, a następnie zignorowane daje iluzję kontroli, która nie istnieje; pole odrzucone mówi to od razu.
Przed rozpoczęciem produkcji przeprowadź audyt własnego potoku NL2SQL
Niezależnie od tego, czy korzystasz z Aurabase, czy budujesz własny potok na bazie ogólnego LLM, poniższe punkty obejmują to, czego najczęściej brakuje.
Jeśli sam napiszesz walidator
- Analizuj SQL za pomocą prawdziwego parsera dla Twojego dokładnego dialektu, nigdy za pomocą dopasowywania wzorców w ciągu znaków.
- Przyjmij domyślne odrzucenie: należy odrzucić dowolny typ węzła i każdą funkcję, która nie została wyraźnie autoryzowana, a nie tylko już zidentyfikowane niebezpieczne przypadki.
- Akceptuj tylko jedną instrukcję na zapytanie. Jest to najprostsze odrzucenie zapytań stosowych.
- Przetestuj walidator z rzeczywistymi przypadkami kontradyktoryjnymi (funkcja zabroniona w FILTER, w wewnątrzzagregowanej ORDER BY, w OFFSET), a nie tylko z oczywistymi przypadkami.
- Mimo wszystko wykonaj zweryfikowany SQL z rolą Postgres z obniżonymi uprawnieniami na oczekiwanym schemacie: walidator ogranicza formę zapytania, rola ogranicza to, co może fizycznie osiągnąć, jeśli sprawa ci umknęła.
Jeśli oceniasz framework NL2SQL innej firmy
- Zapytaj wyraźnie, czy walidacja ma charakter strukturalny (AST), czy tylko szybka instrukcja: odpowiedź zmienia wszystko.
- Sprawdź, czy limit wiersza jest stosowany domyślnie, a nie tylko udokumentowany na Twój koszt jako najlepsza praktyka.
- Sprawdź, czy schemat użyty do walidacji może zostać dostarczony przez klienta API, co spowodowałoby ponowne otwarcie dokładnie opisanej powyżej wady.
- Przed wyborem porównaj kilka narzędzi pod kątem tego konkretnego kryterium: nasze porównanie narzędzi NL2SQL szczegółowo opisuje, co wyróżnia podejścia dostępne w 2026 roku.
Walidator zmniejsza ryzyko, nie zastępuje RLS
Solidny walidator AST zmniejsza ryzyko u źródła: SQL, który dociera do Twojej bazy danych, ma już znaną i ograniczoną formę. Nie zastępuje to jednak zasad RLS na wrażliwych tabelach, które decydują, które wiersze dany użytkownik ma prawo zobaczyć. Obie warstwy odpowiadają na różne pytania: walidator ogranicza formę wygenerowanego zapytania, RLS ogranicza dane, które może zwrócić dla konkretnego użytkownika. Utrzymuj oba aktywne, nawet jeśli jeden wydaje się zbędny w stosunku do drugiego.
NL2SQL obejmuje ustrukturyzowane pytania dotyczące tabel. W przypadku pytań dotyczących nieustrukturyzowanej zawartości, dokumentów, notatek, biletów, natywny RAG Aurabase postępuje według porównywalnej logiki bezpieczeństwa, szczegółowo opisanej w naszym samouczku dotyczącym potoku RAG na pgvector.