PRODSuwerenna europejska platforma BaaSOtwórz Panel →

Natywna sztuczna inteligencja · 9 min odczytu

Zabezpiecz NL2SQL przed wstrzyknięciem SQL LLM

Affane Daylami · Fondateur · 16 kwietnia 2026

Powrót do bloga

Punkt końcowy NL2SQL przekształca pytanie w języku naturalnym w zapytanie SQL, a następnie to zapytanie jest wykonywane w bazie danych. Ryzyko nie polega zatem na klasycznym wstrzyknięciu SQL, źle zmienionym łańcuchu znaków w formularzu: jest to model języka, który sam decyduje, który kod SQL zapisać. Grzeczne poproszenie modelu o wygenerowanie SELECT tylko w wierszu poleceń systemowych nie zapobiega niczemu strukturalnie, jest to instrukcja, a nie kontrola dostępu. Jedyną skuteczną metodą jest sprawdzenie poprawności wygenerowanego później kodu SQL za pomocą parsera, który konstruuje jego drzewo składniowe.

Ten tekst w języku angielskim został wygenerowany automatycznie na podstawie francuskiego oryginału i nie był jeszcze recenzowany.
Ta strona została przetłumaczona automatycznie. Wersja angielska jest miarodajna.

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, LIMIT obowią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 w FILTER, w wewnątrzzagregowanym ORDER BYlub w OFFSET.
#
Prawdziwy problem

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.

Obraz, który pomaga

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.

#
Krok 1

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.

nl2sql/validator.rsrust
let dialect = PostgreSqlDialect {};
let mut statements = Parser::parse_sql(&dialect, sql)
    .map_err(|e| AuraError::BadRequest(format!("Erreur de parsing SQL: {}", e)))?;

if statements.len() > 1 {
    return Err(AuraError::BadRequest("Multi-statements non autorisés".to_string()));
}

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.

#
Krok 2

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 odrzuconaDlaczego jest to niebezpieczne?
CTE / ZMożna połączyć dodatkową niezamierzoną logikę przed końcowym WYBIEREM.
Podzapytania, UNION / INTERSECT / EXCEPTRozszerza obszar możliwości pojedynczego pytania w jednym zapytaniu.
WYBIERZ...WTworzy tabelę: pisanie zamaskowane jako czytanie.
DO AKTUALIZACJI / DO UDOSTĘPNIENIAMontaż ś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ć.

#
Krok 3

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.

nl2sql/tests.rsrust
#[test]
fn test_rejette_fonction_interdite_dans_filter() {
    assert!(validate(
        "SELECT count(*) FILTER (WHERE pg_sleep(10) IS NOT NULL) FROM users",
        None, None
    ).is_err());
}

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.

#
Krok 4

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.

réponse /v1/ai/{project_id}/nl2sql (extrait)json
{
  "sql": "SELECT count(*) FROM orders WHERE status = 'paid' LIMIT 100",
  "limit": 100,
  "limit_injected": true
}
Informacje

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.

#
Krok 5

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.

#
Krok 6

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.

#
Lista kontrolna

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.
#
Głęboka obrona

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.

#
Często zadawane pytania

Często zadawane pytania

Czy szybka inżynieria jest całkowicie bezużyteczna do zabezpieczenia NL2SQL?+
Nie, w większości przypadków jest to przydatne do kierowania modelu w kierunku prawidłowego i odpowiedniego kodu SQL. Nie jest to jednak kontrola bezpieczeństwa: dwuznaczne lub zmanipulowane pytanie może ją ominąć, a szybka instrukcja niczego fizycznie nie blokuje. Walidator strukturalny po wygenerowaniu pozostaje niezbędny, niezależnie od jakości podpowiedzi.
Czy nadal powinniśmy aktywować RLS, jeśli wygenerowany SQL jest już zweryfikowany i ograniczony do SELECT?+
Tak. Walidator ogranicza formę żądania (brak podzapytania, brak funkcji niebędącej białą listą, z ograniczeniem LIMIT), a nie prawa biznesowe dotyczące danych. RLS pozostaje warstwą decydującą, które linie dany użytkownik ma prawo zobaczyć, nawet w ramach doskonale prawidłowego SELECT.
Czy zamknięta biała lista dziesięciu funkcji nie zawęża zbytnio możliwych pytań?+
Tak i jest to dobrowolne. Obejmuje większość analiz odczytu (liczba, suma, średnia, min, maks, dolna, górna, koalescencja, date_trunc, teraz) i domyślnie odrzuca wszystko inne, w tym legalną funkcję, której nikt jeszcze nie pomyślał o dodaniu. Każde dodanie musi być wyraźną decyzją, a nie przeoczeniem na czarnej liście.

GOTOWY DO WDROŻENIA?

Twój backend w pięć minut.

Karta kredytowa nie jest wymagana · 500 MB za darmo · 50 000 MAU