PRODСуверенная европейская платформа BaaSОткрыть панель управления →

Родной ИИ · 9 минута чтения

Защитите NL2SQL от внедрения LLM SQL.

Affane Daylami · Fondateur · 16 апреля 2026 г.

Вернуться в блог

Конечная точка NL2SQL преобразует вопрос на естественном языке в запрос SQL, затем этот запрос выполняется в вашей базе данных. Таким образом, риск заключается не в классической SQL-инъекции, плохо экранированной строке в форме: это языковая модель, которая сама решает, какой SQL писать. Вежливое обращение к модели генерировать только SELECT в системной подсказке структурно ничего не предотвращает, это инструкция, а не контроль доступа. Единственный работающий метод — это проверка сгенерированного после этого SQL-кода с помощью синтаксического анализатора, который строит его синтаксическое дерево.

Этот текст на английском языке был создан автоматически на основе французского оригинала и еще не проверялся.
Эта страница была переведена автоматически. Английская версия является авторитетной.

В этом руководстве подробно описан метод, который действительно работает: структурное ограничение на SELECT, закрытый белый список функций, обязательное ограничение строк и блокировка запрашиваемой схемы. Каждый шаг опирается на валидатор, фактически реализованный в механизме NL2SQL Aurabase, возможности его собственного искусственного интеллекта , встроенного в бэкэнд, а не стороннего сервиса, созданного постфактум. Если эта тема для вас нова, наш обзор NL2SQL закладывает основы, а пошаговое руководство показывает, как создать полную конечную точку.

Самое необходимое

  • Оперативное проектирование («генерирует только SELECT») — это , а не проверка безопасности: модель может галлюцинировать, руководствоваться неоднозначным вопросом или просто игнорировать инструкции.
  • Соблюдаемая проверка является структурной: синтаксический анализатор создает синтаксическое дерево (AST) запроса и по умолчанию отклоняет все, что не разрешено явно.
  • Четыре конкретных уровня ограничивают риск: строгий SELECT (ни подзапрос, ни CTE, ни UNION), закрытый белый список из десяти функций, LIMIT обязательный и ограниченный, заблокированный доступ к системному каталогу и нетенантным схемам.
  • Запрашиваемая схема должна исходить от сервера , а не из поля клиентского запроса: в противном случае ничто не мешает вызывающему объекту предоставить свою собственную схему для обхода проверки.
  • В Aurabase этот валидатор (крейт Rust sqlparser) тестируется на состязательных случаях, описанных в коде: запрещенные функции, скрытые в FILTER, во внутриагрегате ORDER BYили в OFFSET.
#
Настоящая проблема

Почему инструкция в системной подсказке ничего не блокирует?

Системное приглашение с надписью «генерирует только запросы SELECT» является предпочтением, а не препятствием. Модель уважает это большую часть времени, потому что она обучена следовать инструкциям, а не потому, что технические ограничения физически не позволяют ей писать что-либо еще. Два класса неудач делают эту уверенность недостаточной в производстве.

Первое вытекает из самого вопроса. Пользователь, злонамеренный или просто креативный в своей формулировке, может направить вопрос так, чтобы подтолкнуть модель к SQL, которую ему не следовало писать: объединение с чувствительной таблицей, фильтр, обходящий ожидаемую логику, вызов системной функции. Модель не делает различия между законным вопросом и вопросом, предназначенным для манипулирования им.

Второе не требует злого умысла. Модель может галлюцинировать имя таблицы, забыть LIMIT, запрошенный в подсказке, или сгенерировать SELECT * без каких-либо ограничений для большой таблицы. Результат в обоих случаях один и тот же: потенциально дорогостоящий или навязчивый SQL, который прошел фильтр подсказок и вот-вот будет выполнен на реальной базе данных.

Изображение, которое помогает

Системная подсказка остается полезной, большую часть времени она направляет модель к правильному результату. Но знак «проход запрещен» не останавливает того, кто не умеет читать или решает его игнорировать. Вам нужна закрытая дверь сзади, а не просто панель спереди.

#
Шаг 1

Анализируйте сгенерированный SQL в синтаксическое дерево, а не в необработанную строку.

Первая линия защиты — проанализировать SQL, созданный моделью, с помощью реального синтаксического анализатора целевого диалекта, а затем проверить полученную структуру, а не необработанный текст. Поиск запрещенных слов в строке символов («DROP», «DELETE», «;») обходится тривиально: разный регистр, комментарий, вставленный в середину ключевого слова, набранные кавычки. Синтаксическое дерево однозначно описывает, что на самом деле делает запрос.

Aurabase реализует этот шаг с помощью крейта Rust sqlparser и его диалекта PostgreSqlDialect. Еще до синтаксического анализа первый лексический фильтр отклоняет две конструкции, которые трудно правильно обосновать в дереве: долларовые кавычки ($$...$$), которые могут скрыть произвольное содержимое строки, и многострочные комментарии (/* */), которые могут скрыть реальный конец инструкции.

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()));
}

Этот отказ от использования нескольких операторов блокирует наиболее известную форму SQL-инъекции путем стека: SELECT * FROM users; DROP TABLE users;--. Парсер возвращает только одну действенную инструкцию, вторая просто никогда не достигается, независимо от того, как она сформулирована в исходном вопросе.

#
Шаг 2

Структурно ограничиться простым SELECT.

После получения дерева самая широкая проверка состоит в том, чтобы принять только один тип корневого узла, запрос (Statement::Query), и отклонить все остальное: INSERT, UPDATE, DELETE, DROP, CREATE, ALTER. Это уже не подсказка, а условие на тип разбираемого объекта, обойти которое не может никакая умелая постановка вопроса.

Даже внутри SELECT некоторые конструкции остаются опасными и заслуживают явного отказа:

Строительство отклоненоПочему это опасно?
КТР/СМожет связать дополнительную непреднамеренную логику перед окончательным SELECT.
Подзапросы, UNION/INTERSECT/EXCEPTРасширяет область действия одного вопроса в одном запросе.
ВЫБРАТЬ... ВСоздает таблицу: письмо замаскировано под чтение.
ДЛЯ ОБНОВЛЕНИЯ / ДЛЯ ПОДЕЛИТЬСЯУстановка замков, риск возникновения конфликтов с производственным трафиком.
Табличные функции (generate_series, pg_read_file...)Доступ к системе или отказ в обслуживании через линии, генерируемые по требованию.

Тестовый пример, взятый из репозитория, конкретно иллюстрирует последний пункт: SELECT * INTO backup FROM users отклонен, даже если он не содержит ни ключевого слова видимой записи, ни подозрительной функции. Формы запроса достаточно, чтобы его дисквалифицировать.

#
Шаг 3

Белый список функций, а не черный список

Черный список запрещенных функций (pg_sleep, pg_read_file, dblink...) требует предвидеть каждую опасную функцию одну за другой, в то время как Postgres раскрывает несколько сотен из них. Белый список меняет бремя доказательства: разрешено только десять функций: count, sum, avg, min, max, lower, upper, coalesce, date_trunc, now. Все остальное запрещено по умолчанию, включая одну законную функцию, которую еще никто не догадался добавить.

Одного прохода проверки не всегда достаточно. Структурный обход дерева перечисляет его точки входа одну за другой (проекция, WHERE, JOIN, GROUP BY...), и одну легко забыть: запрещенную функцию можно спрятать в предложении FILTER (WHERE pg_sleep(10) IS NOT NULL), во внутриагрегатном ORDER BY (sum(id ORDER BY pg_sleep(10))), в WITHIN GROUP, DISTINCT ONили 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());
}

Поэтому валидатор Aurabase добавляет второй исчерпывающий проход, который проходит через все выражения в дереве, где бы они ни находились, независимо от структурного пути. Это предполагаемая глубокая защита: если первый проход пропускает случай, второй его догоняет.

#
Шаг 4

Привязка возвращаемых строк: LIMIT обязателен и ограничен.

SELECT * остается авторизованным, это полезно для интеллектуального анализа данных. Риск не в главной роли, а в отсутствии ограничения на запрос, написанный моделью: плохо сформулированный вопрос может вернуть целую таблицу, что влечет за собой затраты памяти и время ответа.

Aurabase применяет простое и прозрачное правило. Если сгенерированный SQL не имеет LIMIT, сервер добавляет его (по умолчанию 100 строк, значение объявляется модели в системном приглашении). Если SQL запрашивает LIMIT сверх жесткого ограничения (по умолчанию 1000 строк), запрос явно отклоняется, а не сокращается автоматически. Оба значения настраиваются на стороне сервера (AI_NL2SQL_DEFAULT_LIMIT, AI_NL2SQL_MAX_LIMIT), и сервер даже отказывается запускаться, если ошибка превышает потолок.

réponse /v1/ai/{project_id}/nl2sql (extrait)json
{
  "sql": "SELECT count(*) FROM orders WHERE status = 'paid' LIMIT 100",
  "limit": 100,
  "limit_injected": true
}
Информация

Отказ, а не молчаливое сокращение имеет прямой интерес: ограничение, установленное без предупреждения, создаст у звонящего иллюзию, что его запрос был выполнен, в то время как результат был бы усечен без его ведома. limit_injected всегда указывает, исходит ли значение от модели или от сервера.

#
Шаг 5

Блокировка доступа к схеме: системный каталог и кросс-схема

Две отдельные утечки угрожают механизму NL2SQL, подключенному к реальной базе данных: доступ к системному каталогу Postgres и доступ к схеме, которая не принадлежит вызывающей стороне. Оба блокируются после проверки, независимо от какой-либо политики RLS, размещенной в нисходящем направлении.

pg_catalog всегда является частью search_path, что означает, что неквалифицированное имя, такое как pg_authid или pg_stat_activity, обращается к нему напрямую, без префикса. Валидатор Aurabase блокирует любое имя, начинающееся с pg_, а также information_schema и внутреннюю схему aura_console, независимо от того, квалифицировано оно или нет.

Для двухкомпонентного имени (schema.table) разрешена только схема вызывающего проекта, любое другое значение отклоняется. Имя, состоящее из трех и более компонентов, автоматически отклоняется. Эта граница на уровне сгенерированного запроса является дополнением к изоляции уровня базы данных, подробно описанной в нашей статье о многопользовательской изоляции: одна не позволяет сгенерированному SQL нацеливаться на другую схему, другая предотвращает попадание самого соединения в другую базу данных. Ни одно не заменяет другое.

#
Шаг 6

Никогда не позволяйте клиенту переопределять запрашиваемую схему.

Дискретная ловушка подстерегает любой NL2SQL API, который принимает параметр, описывающий схему или таблицы, разрешенные в запросе клиента. Если этот же параметр используется для создания приглашения и проверки выходного SQL, вызывающая сторона может лгать о том, что разрешено, и затем проверка проверяется на соответствие этой лжи, а не реальности базы данных.

Aurabase анализирует фактическую базовую схему проекта при каждом вызове с коротким тридцатисекундным кешем для обеспечения производительности и явно отклоняет (ошибка 400) любые поля schema, allowed_schemaили schema_context , отправленные в теле запроса, вместо того, чтобы принимать и затем молча перезаписывать его. Разница имеет значение: поле, принятое, а затем проигнорированное, создает иллюзию контроля, которого не существует; отказное поле говорит об этом сразу.

#
Контрольный список

Аудит вашего собственного конвейера NL2SQL перед производством

Независимо от того, используете ли вы Aurabase или создаете свой собственный конвейер на основе обычного LLM, следующие пункты охватывают то, что чаще всего упускают из виду.

Если вы сами пишете валидатор

  • Разбирайте SQL с помощью реального синтаксического анализатора для вашего конкретного диалекта, а не с помощью сопоставления с образцом в строке.
  • Примите отказ по умолчанию: любой тип узла, любая функция, не разрешенная явно, должны быть отклонены, а не только уже выявленные опасные случаи.
  • Принимайте только один оператор для каждого запроса. Это самый простой отказ от составных запросов.
  • Тестируйте валидатор на реальных состязательных случаях (функция запрещена в FILTER, во внутриагрегатном ORDER BY, в OFFSET), а не только на очевидных случаях.
  • Несмотря ни на что, выполняйте проверенный SQL с ролью Postgres с пониженными привилегиями в отношении ожидаемой схемы: валидатор ограничивает форму запроса, роль ограничивает то, чего он может физически достичь, если случай ускользнул от вас.

Если вы оцениваете стороннюю платформу NL2SQL

  • Спросите прямо, является ли проверка структурной (AST) или это всего лишь подсказка: ответ меняет все.
  • Убедитесь, что ограничение строки применяется по умолчанию, а не просто задокументировано как лучшая практика за ваш счет.
  • Проверьте, может ли схема, используемая для проверки, быть предоставлена ​​клиентом API, который снова откроет именно описанную выше ошибку.
  • Прежде чем сделать выбор, сравните несколько инструментов по этому конкретному критерию: в нашем сравнении инструментов NL2SQL подробно рассказывается, что отличает подходы, доступные в 2026 году.
#
Глубокоэшелонированная защита

Валидатор снижает риск, не заменяет RLS

Надежный валидатор AST снижает риск в источнике: SQL, попадающий в вашу базу данных, уже имеет известную и ограниченную форму. Однако он не заменяет политики RLS в ваших конфиденциальных таблицах, которые определяют, какие строки имеет право видеть данный пользователь. Два уровня отвечают на разные вопросы: валидатор ограничивает форму сгенерированного запроса, RLS ограничивает данные, которые он может вернуть для конкретного пользователя. Сохраняйте оба активными, даже если один кажется избыточным по отношению к другому.

NL2SQL отвечает на структурированные вопросы о ваших таблицах. Для вопросов о неструктурированном контенте, документах, заметках, билетах собственный RAG Aurabase использует аналогичную логику безопасности, подробно описанную в нашем руководстве по конвейеру RAG на pgvector.

#
Часто задаваемые вопросы

Часто задаваемые вопросы

Является ли быстрое проектирование совершенно бесполезным для обеспечения безопасности NL2SQL?+
Нет, в большинстве случаев он по-прежнему полезен для направления модели на правильный и релевантный SQL. Но это не проверка безопасности: двусмысленный или манипулируемый вопрос может обойти ее, а подсказка физически ничего не блокирует. Структурный валидатор после генерации остается необходимым, независимо от качества подсказки.
Должны ли мы по-прежнему активировать RLS, если сгенерированный SQL уже проверен и ограничен SELECT?+
Да. Валидатор ограничивает форму запроса (без подзапроса, без функции, не являющейся белым списком, с ограничением LIMIT), а не бизнес-права на данные. RLS остается уровнем, который решает, какие строки имеет право видеть конкретный пользователь, даже в пределах совершенно допустимого SELECT.
Не слишком ли ограничивает возможные вопросы закрытый белый список из десяти функций?+
Да, и это добровольно. Он охватывает большую часть аналитики чтения (счет, сумма, среднее, мин, макс, нижнее, верхнее, объединение, date_trunc, сейчас) и по умолчанию запрещает все остальное, включая законную функцию, которую еще никто не подумал добавить. Каждое добавление должно быть явным решением, а не оплошностью в черном списке.

ГОТОВЫ К РАЗВЕРТЫВАНИЮ?

Ваш бэкэнд за пять минут.

Кредитная карта не требуется · 500 МБ бесплатно · 50 000 MAU