PROD欧州主権の BaaS プラットフォームダッシュボードを開く →

ネイティブAI · 9 分読み取り

LLM SQL インジェクションに対して NL2SQL を保護する

Affane Daylami · Fondateur · 2026年4月16日

ブログに戻る

NL2SQL エンドポイントは自然言語の質問を SQL クエリに変換し、このクエリがデータベースに対して実行されます。したがって、リスクは古典的な SQL インジェクション、つまりフォーム内で不適切にエスケープされた文字列ではなく、どの SQL を記述するかを単独で決定する言語モデルです。システム プロンプトで SELECT のみを生成するようにモデルに丁寧に要求しても、構造的には何も妨げられません。これは命令であり、アクセス制御ではありません。有効な唯一の方法は、構文ツリーを構築するパーサーを使用して、後で生成された SQL を検証することです。

この英語のテキストはフランス語のオリジナルから自動的に生成されたもので、まだレビューされていません。
このページは自動翻訳されました。英語版が正式です。

This guide details the method that actually works: structural restriction to SELECT, closed whitelist of functions, mandatory row cap, and locking of the queried schema. Each step relies on the validator actually implemented in Aurabase's NL2SQL engine, a capability of its native AI built into thebackend, not a third-party service thrown together after the fact. If the subject is new to you, our overview of NL2SQL lays the foundations, and the step-by-step tutorial shows how to build the complete endpoint.

必需品

  • プロンプト エンジニアリング (「SELECT のみを生成する」) はセキュリティ チェックではありません。モデルは幻覚を見せたり、曖昧な質問に誘導されたり、単に指示を無視したりする可能性があります。
  • 有効な検証は構造的なものです。パーサーはリクエストの構文ツリー (AST) を構築し、明示的に許可されていないものはデフォルトで拒否します。
  • 4 つの具体的なレイヤーによってリスクが制限されます。厳密な SELECT (サブクエリ、CTE、UNION のいずれもなし)、10 個の関数のクローズド ホワイトリスト、LIMIT 必須で制限付き、システム カタログおよび非テナント スキーマへのアクセスのブロックです。
  • クエリされるスキーマはサーバーから取得される必要があり、クライアント リクエストのフィールドから取得されることはありません。それ以外の場合は、呼び出し元が検証をバイパスするために独自のスキーマを提供することを妨げるものはありません。
  • Aurabase では、このバリデータ (Rust クレート sqlparser) は、コードに文書化された敵対的なケース ( FILTER、イントラ集計 ORDER BY、または OFFSETに隠された禁止関数) でテストされます。
#
本当の問題

システムプロンプトの指示が何もブロックしないのはなぜですか?

「SELECT クエリのみを生成する」というシステム プロンプトは優先事項であり、障壁ではありません。モデルはほとんどの場合、指示に従うようにトレーニングされているため、技術的な制約によって他のものを書くことが物理的に妨げられるためではありません。 2 つのクラスの障害があると、本番環境ではこの信頼性が不十分になります。

1つ目は質問自体から来ています。ユーザーは、意図がなかったり、単純に定式化に創造的であったりするため、機密テーブルへの結合、予期されるロジックを回避するフィルター、システム関数呼び出しなど、作成すべきでない SQL にモデルを押し付けるような方法で質問を誘導する可能性があります。このモデルは、正当な質問とそれを操作するために設計された質問を区別しません。

The second requires no malice. A model can hallucinate a table name, forget the LIMIT that the prompt asked for, or generate an SELECT * without any restrictions on a large table. The result is the same in both cases: potentially expensive or intrusive SQL, which has passed the prompt filter and is about to be executed against a real database.

役立つイメージ

システム プロンプトは依然として有用であり、ほとんどの場合、モデルを正しい結果に導きます。しかし、「立ち入り禁止」の標識は、読み方を知らない人や、それを無視しようとする人を止めることはできません。正面のパネルだけでなく、後ろに閉じたドアが必要です。

#
ステップ1

生成された SQL を生の文字列ではなく構文ツリーに解析します。

防御の第一線は、モデルによって生成された SQL をターゲット方言の実際のパーサーで解析し、生のテキストではなく結果の構造を検証することです。文字列内の禁止語 (「DROP」、「DELETE」、「;」) の検索は、大文字小文字の違い、キーワードの途中に挿入されたコメント、引用符の入力など、簡単に回避されます。構文ツリーは、クエリが実際に何を行うかを明確に説明します。

Aurabase は、Rust クレート sqlparser とその方言 PostgreSqlDialectを使用してこのステップを実装します。解析する前であっても、最初の字句フィルターは、ツリーに一度追加すると正しく推論するのが難しい 2 つの構造、つまり文字列内の任意のコンテンツを隠すことができるドル引用符 ($$...$$) と、命令の実際の終わりを隠すことができる複数行コメント (/* */) を拒否します。

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;--) をブロックします。パーサーは実行可能な命令を 1 つだけ返します。元の質問でどのように表現されているかに関係なく、2 番目の命令には決して到達しません。

#
ステップ2

構造的に単純な SELECT に制限する

ツリーが取得されたら、最も広範な検証は、1 種類のルート ノード、クエリ (Statement::Query) のみを受け入れ、その他のすべてを拒否することです: INSERT、 UPDATE、 DELETE、 DROP、 CREATE、 ALTER。これはもはや即時指示ではなく、解析されるオブジェクトのタイプに関する条件であり、質問を巧みに定式化してもこれを回避することはできません。

SELECT 内であっても、いくつかの構造は危険なままであり、明示的に拒否する必要があります。

建設が拒否されましたなぜ危険なのでしょうか?
CTE / あり最後の SELECT の前に、意図しない追加のロジックを連鎖させることができます。
サブクエリ、UNION / INTERSECT / EXCEPT1 つの質問で 1 つのクエリで実行できる範囲が拡大します。
選択...入力テーブルを作成します: 読み取りを装った書き込み。
更新用 / 共有用ロックのインストール、運用トラフィックとの競合のリスク。
テーブル関数 (generate_series、pg_read_file...)オンデマンドで生成される回線を介したシステム アクセスまたはサービス拒否。

リポジトリから取得したテスト ケースは、最後の点を具体的に示しています。目に見える書き込みキーワードや疑わしい関数が含まれていないにもかかわらず、SELECT * INTO backup FROM users は拒否されます。リクエストの形式だけで失格となります。

#
ステップ3

ブラックリストではなく機能ホワイトリスト

禁止された関数のブラックリスト (pg_sleep、 pg_read_file、 dblink...) では、危険な関数を 1 つずつ予測する必要がありますが、Postgres では数百もの危険な関数が公開されています。ホワイトリストは立証責任を逆転させます。許可される関数は count、 sum、 avg、 min、 max、 lower、 upper、 coalesce、 date_trunc、 nowの 10 個の関数のみです。まだ誰も追加しようと考えていない 1 つの正当な機能を含め、その他すべてはデフォルトで禁止されています。

単一の検証パスだけでは必ずしも十分とは限りません。ツリーの構造的走査では、そのエントリ ポイント (射影、WHERE、JOIN、GROUP BY...) が 1 つずつリストされますが、忘れがちな 1 つです。禁止された関数は、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 バリデータは 2 番目の網羅的なパスを追加します。これは、構造パスとは関係なく、ツリー内のすべての式をどこにでも通過します。これは多層防御を想定しており、最初のパスがケースを見逃した場合、2 番目のパスが追いつきます。

#
ステップ4

返された行をバインドします: LIMIT 必須および上限付き

SELECT * は許可されたままなので、データ マイニングに役立ちます。リスクは主役ではなく、モデルによって作成されたクエリに上限がないことです。質問の形式が不十分だと、テーブル全体が返され、それが意味するメモリ コストと応答時間が増加する可能性があります。

Aurabase applies a simple and transparent rule. If the generated SQL does not have a LIMIT, the server adds one (100 lines by default, value announced to the model in the system prompt). If the SQL requests an LIMIT beyond a hard cap (1000 rows by default), the query is explicitly refused rather than silently reduced. Both values ​​are configurable on the server side (AI_NL2SQL_DEFAULT_LIMIT, AI_NL2SQL_MAX_LIMIT), and the server even refuses to start if the fault exceeds the ceiling.

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 システム カタログへのアクセスと、呼び出し元に属さないスキーマへのアクセスという 2 つの異なるリークによって脅かされます。どちらも、ダウンストリームに配置された RLS ポリシーに関係なく、検証時にブロックされます。

pg_catalog は常に search_pathの一部です。つまり、 pg_authid や pg_stat_activity のような非修飾名は接頭辞なしで直接アクセスします。 Aurabase バリデータは、修飾されているかどうかに関係なく、 pg_で始まる名前、 information_schema 、内部スキーマ aura_consoleをブロックします。

2 つのコンポーネントの名前 (schema.table) では、呼び出し元プロジェクトのスキーマのみが許可され、他の値は拒否されます。 3 つ以上のコンポーネントを含む名前は自動的に拒否されます。生成されたクエリ レベルのこの境界は、マルチテナント分離に関する記事で詳しく説明されているデータベース レベルの分離に追加されたものです。: 1 つは、生成された SQL が別のスキーマをターゲットにすることを防ぎ、もう 1 つは接続自体が別のデータベースに到達することを防ぎます。どちらも他方に置き換わるものではありません。

#
ステップ6

クライアントにクエリされたスキーマを再定義させないでください

個別トラップは、クライアントのクエリで許可されるスキーマまたはテーブルを記述するパラメータを受け入れる NL2SQL API を待機します。これと同じパラメータを使用してプロンプトを構築し、出力 SQL を検証する場合、呼び出し元は何が許可されているかについて嘘をつき、検証ではデータベースの現実ではなく、この嘘に対して検証が行われます。

Aurabase は、パフォーマンスのために 30 秒の短いキャッシュを使用して、呼び出しごとに実際のプロジェクトのベーススキーマをイントロスペクトし、リクエスト本文で送信された schema、 allowed_schema、または schema_context フィールドを受け入れてサイレントに上書きするのではなく、明示的に拒否 (400 エラー) します。違いは重要です。フィールドが受け入れられて無視されると、存在しないコントロールであるかのような錯覚を与えます。拒否されたフィールドにはすぐにそれが表示されます。

#
チェックリスト

運用前に独自の NL2SQL パイプラインを監査する

Aurabase を使用する場合でも、汎用 LLM 上に独自のパイプラインを構築する場合でも、次の点で最も見落としがちな点をカバーします。

自分でバリデータを作成する場合

  • 文字列内のパターン マッチングを使用するのではなく、実際のパーサーを使用して SQL を解析して正確な方言を見つけます。
  • デフォルトの拒否を採用します。すでに特定されている危険なケースだけでなく、明示的に許可されていないノードや機能の種類を問わず、拒否する必要があります。
  • クエリごとに 1 つのステートメントのみを受け入れます。これは、スタックされたクエリに対する最も単純な拒否です。
  • 明白なケースだけでなく、実際の敵対的なケース (FILTER、内部集計 ORDER BY、OFFSET で禁止されている関数) でバリデータをテストします。
  • すべてにもかかわらず、予期されるスキーマに対する権限が制限された Postgres ロールを使用して、検証済みの SQL を実行します。バリデーターはクエリの形式を制限し、ケースが回避された場合にロールが物理的に達成できることを制限します。

サードパーティの NL2SQL フレームワークを評価している場合

  • 検証が構造的 (AST) であるか、それともただのプロンプト指示であるかを明確に尋ねます。答えによってすべてが変わります。
  • ラインキャップが単にベストプラクティスとして文書化されているだけでなく、デフォルトで適用されていることを確認してください。
  • 検証に使用されるスキーマが API クライアントによって提供できるかどうかを確認します。これにより、まさに上記の欠陥が再度発生します。
  • 選択する前に、この特定の基準に基づいていくつかのツールを比較してください。 NL2SQL ツールの 比較 では、2026 年に利用可能なアプローチの違いについて詳しく説明します。
#
多層防御

バリデーターはリスクを軽減しますが、RLS に代わるものではありません

堅牢な AST バリデーターは、ソースでのリスクを軽減します。データベースに到達する SQL は、すでに既知の制限された形式を持っています。ただし、これは、特定のユーザーが表示する権利を持つ行を決定する機密テーブルの RLS ポリシーを置き換えるものではありません。 2 つのレイヤーはさまざまな質問に答えます。バリデーターは生成されるクエリの形式を制限し、RLS は特定のユーザーに返すことができるデータを制限します。一方が他方と重複しているように見える場合でも、両方をアクティブにしておきます。

NL2SQL covers structured questions about your tables. For questions about unstructured content, documents, notes, tickets, Aurabase's native RAG follows a comparable security logic, detailed in our RAG pipeline tutorial on pgvector.

#
よくある質問

よくある質問

NL2SQL のセキュリティ保護にはプロンプト エンジニアリングはまったく役に立たないのでしょうか?+
いいえ、ほとんどの場合、モデルを正しく適切な SQL に導くのに役立ちます。しかし、これはセキュリティ チェックではありません。曖昧な質問や操作された質問はそれを回避することができ、即時の指示は物理的に何もブロックしません。プロンプトの品質に関係なく、生成後の構造バリデーターは依然として必要です。
生成された SQL がすでに検証され、SELECT に制限されている場合でも、RLS をアクティブ化する必要がありますか?+
はい。バリデーターは、データに対するビジネス権限ではなく、リクエストの形式 (サブクエリなし、ホワイトリスト以外の関数なし、制限付き LIMIT) を制限します。 RLS は、完全に有効な SELECT 内であっても、特定のユーザーがどの行を表示する権利を持つかを決定するレイヤーであり続けます。
10 個の機能からなるクローズド ホワイト リストでは、考えられる質問が制限されすぎませんか?+
はい、それは任意です。これは、読み取り分析 (count、sum、avg、min、max、 lower、upper、coalesce、date_trunc、now) のほとんどをカバーし、まだ誰も追加しようと考えていない正当な関数を含む、その他すべてをデフォルトで拒否します。それぞれの追加は、ブラックリストの見落としではなく、明示的な決定である必要があります。

導入の準備はできていますか?

5 分でバックエンドが完成します。

クレジット カードは不要 · 500 MB 無料 · 50,000 MAU