mcpField
S
sql-security

sql-security

github.com/mizchiセキュリティ41654 回インストール1 週間前に更新
リポジトリ

sql-security について

ホスト言語のコードで組み立てるSQLを確認するためのセキュリティスキルです。Node.jsスキャナーが一部の単一行補間や文字列連結を検出し、検出範囲の限界と認証情報チェック用のsecretlintも説明します。

主な機能

  1. 01SQL補間パターンのソーススキャン
  2. 02SQL文字列連結の検出
  3. 03生成物とテストファイルの除外
  4. 04確認済み検出への除外コメント
  5. 05認証情報検出にsecretlintを利用

活用シーン

  1. 01変更をpushする前のSQLスキャン
  2. 02PRレビューへのスキャン結果の追加
  3. 03誤検出の可能性がある項目の手動確認
  4. 04リポジトリ内のハードコード認証情報の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mizchi/skills --skill sql-security

インストール後、Claude Code で/sql-securityを入力して読み込まれたか確認します。

同梱ファイルREADME.mdscripts/sql-injection-scan.mjs

詳細

SQL セキュリティ

ホストコード(MoonBit/TS/Rust/Go)から SQL を実行するプロジェクトで、SQL 関連インシデントの主な原因となる次の 2 つに対して、手軽な防御策を導入したい場合に使います。

  1. SQL インジェクション:プレースホルダーを通じてバインドする代わりに、文字列テンプレートで値を SQL フラグメントに埋め込むことです。
  2. クエリ内のシークレット:ハードコードされたトークンや接続文字列が git の履歴に漏れることです。これはこのスキルではなく secretlint で対処します。詳しくは以下の「併用ツール:secretlint」を参照してください。

sql-injection-scan.mjs

node scripts/sql-injection-scan.mjs your-project/src

このスキャナーはディレクトリを走査し、生成ファイル(db/gen/、sqlc_*.mbt、*.test.mbt、_build/、dist/、target/)を無視して、次のものを検出します。

  • template-interp:SQL キーワード(SELECT、INSERT INTO、WHERE、AND ( など)で始まり、${...} プレースホルダーを含むバッククォート文字列リテラル。例:`WHERE c.vector_id IN (${placeholders})`。
  • string-concat:引用符で囲まれた SQL 文字列の直後に + と識別子が続くもの。例:"SELECT * FROM " + table。

キーワードの照合では、意図的に大文字と小文字を区別しています。from/where/join は英語の文章で頻繁に使われるため、大文字と小文字を区別せずに照合すると、誤検出が何百件も発生するためです。

検出結果がある場合、スキャナーは終了コード 1 を返します。安全だと判明した場合でも、すべての検出結果を手動で確認してください。

制限事項 — スキャン結果がクリーンでも安全性の証明にはなりません

スキャナーは1 行ずつ読み取ります。テンプレートリテラルのルールが適用されるには、開始バッククォート、${...} プレースホルダー、終了バッククォートがすべて同じ物理行にある必要があります。これにより、次のような結果になります。

  • 複数行のテンプレートリテラルは、検出されずに見逃されます。 バッククォートがある行で始まり、${value} が継続行にあるクエリ(よくある整形スタイル)は、実際にはインジェクションであっても、検出件数 0、終了コード 0 になります。スキャナーが検出できない例:

    const sql = `
      SELECT id FROM users
      WHERE name = ${name}   // <-- real injection, not flagged
    `;
    
  • 終了コード 0 の意味は「1 行で完結する検出対象が見つからなかった」であり、「安全」ではありません。 SQL インジェクションをレビューする際は、複数行の SQL やプログラムで組み立てられる SQL も手動で確認してください。スキャン結果がクリーンだからといって、レビューを通過したと判断してはいけません。スキャナーに検出させたい場合は、複数行のクエリを 1 行にまとめてください。

これは意図的なコストと網羅性のトレードオフ(依存関係ゼロ、パーサーなし)であり、バグではありません。ただし、レビュー時に補う必要がある見落としの可能性があります。

検出結果の読み方

検出結果があっても、必ずしもインジェクションとは限りません。正当なケースも多くあります。

  • 内部でのプレースホルダー展開:サーバー由来の件数から、IN 句用の ?,?,?,...,? を組み立てるケース。埋め込まれる値はユーザー入力ではなく ? です。今後のスキャンで無視できるよう、オプトアウトコメントを付けてください。
  • sqlc が解析できない FTS5/ベクトルクエリ:カラムリストや ? の数を埋め込む動的な句ビルダー。ユーザー入力は引き続き .bind(...) を通ります。
  • インラインのマイグレーションスクリプト:デプロイ時に DDL を組み立てます。

実際の SQLi を 1 件見逃すコストは、誤検出を 5 件トリアージするコストよりはるかに高いため、スキャナーは意図的に多めに検出します。

オプトアウトマーカー

安全だと確認済みの行を検出対象から外すには、同じ行または直前の行に sql-security: ok マーカーを含むコメントを追加してください。

// sql-security: ok (placeholders is server-derived `?` count, values bind separately)
const sql = `WHERE c.vector_id IN (${placeholders})`;

スキャナーは //(TS/MoonBit/Rust/Go)と #(Python/シェル)のコメントマーカーを受け付けます。

併用ツール:secretlint

認証情報の漏えい(「SQL セキュリティ」のもう半分)には secretlint を使います。pkfire で管理するリポジトリの pre-push フックには、次の設定をおすすめします。

pnpm add -D secretlint @secretlint/secretlint-rule-preset-recommend

続いて、pre-push で次を実行します。

pnpm exec secretlint --secretlintignore .gitignore "**/*"

これはリポジトリごとの設定であり、ユーザー全体に適用される secretlint の設定を補完します。pkfire フックを使うプロジェクト向けに、mizchi/skills/pkfire スキルには assets/recipes/14-secretlint-pre-push.pkl にすぐ使えるレシピがあります。

実行するタイミング

  • pre-push:軽量で、コードをリモートに送信する前に 1 回実行します。
  • pre-commit:任意です。ここでの secretlint は大きな差分に対して遅くなる場合がありますが、sql-injection-scan は十分軽量です。
  • PR レビュー:スキャナーの出力をレビュー用チェックリストに貼り付けます。

対象外

  • 保存型 XSS/テンプレートインジェクション:これらは SQL ではなく、フロントエンド/テンプレートのレイヤーを経由します。
  • 認可のバイパス:行レベルのアクセスチェックはアプリケーションのロジックであり、SQL の問題ではありません。
  • 負荷の高いクエリによる DoS:sql-plan-audit(新しい SCAN エントリを探します)とエッジでのレート制限で対応します。

エンジンの拡張性

スキャナーの正規表現はエンジンに依存しません。SQL キーワードのセットは、SQLite/Postgres/MySQL ですぐに利用できます。Postgres を多用するプロジェクトでより幅広くカバーする必要がある場合は、RETURNING/OVERLAPS/LIMIT OFFSET を追加してください。

要件

  • Node 20 以降。

ファイル

  • scripts/sql-injection-scan.mjs — 依存関係のないテキストスキャナー。

コメント