TL;DR: API入力は攻撃対象領域です。サイズ過大のフィールド、誤った型、不正な形式のボディ、インジェクション文字列を送るネガティブテストを作成し、エンドポイントが4xxで拒否し、決して5xxを返さないことを確認してください。
additionalProperties: false、enum、長さ制限を使ってスキーマ検証をセキュリティ制御に変えましょう。テストスイートはすべての変更でCI実行します。AIエージェントはペイロードを機械の速度で生成・転送するため、「このデータをロードする」が「このコードを実行する」に変わるリスクを大規模化させます。
多くのテストスイートは、「正しい入力を送ればAPIが動く」ことだけを証明しています。有効なボディに対して200が返ることは重要ですが、敵対的な入力への耐性は分かりません。信頼できない入力とは、リクエストボディ、クエリ文字列、ヘッダー、ファイルアップロード、Webhookペイロード、AIエージェントが組み立てたJSONなど、エンドポイント自身が生成していないすべてのデータです。これらには常に「誰かが最悪の形式で送ってくる」と仮定してください。
2026年7月、Hugging Faceは、盗まれたパスワードではなくデータが侵入経路となったセキュリティインシデントを説明しました。その侵害からの教訓については別記事で扱っています。本記事では後半の実装に集中します。攻撃者が送る種類の入力をテストで再現し、すべての変更で自動実行できるようにします。カテゴリはOWASP APIセキュリティトップ10と対応しています。Apidogは契約設計とテスト実行の選択肢の1つですが、考え方は既存のテストフレームワークにも適用できます。
入力は攻撃対象領域であり、フォームフィールドではない
バリデーションをUXのための処理だけとして扱ってはいけません。APIが受け入れる各フィールドは、呼び出し元が破れる契約です。破られた契約は、想定外のロジックに到達する経路になります。
例えば、次のような変化を想定します。
- 小さな整数のはずの
limitが999999999になる - 単純なファイル名のはずの
filenameが../../etc/passwdになる - 設定オブジェクトのはずの
configに命令やローダー指定が混入する
セキュリティテストは独立した後工程ではありません。すでに書いているネガティブテストを、被害が大きいフィールドに対して体系化する作業です。
各フィールドについて、次の問いをテストケースにしてください。
このフィールドに入る最悪の値は何か?
この習慣は、APIセキュリティベストプラクティスで扱う多くの対策の出発点になります。
「このデータをロードする」が「このコードを実行する」になった経緯
Hugging Faceのインシデントは、入力を厳密に扱うべき理由を示しています。同社は、悪意あるデータセットが侵入経路だったと説明しています。細工されたデータセットがリモートコードデータセットローダーをトリガーし、データセット設定にはテンプレートインジェクションが含まれていました。詳細はセキュリティインシデントレポートで確認できます。
失敗の形はシンプルです。
- エンドポイントが「データ」として記述された入力を受け取る
- データをロードする処理が実行される
- その処理が攻撃者の制御する命令を評価・実行できる
つまり、「このデータをロードする」が「このコードを実行する」に変わります。
テンプレートインジェクションも同じ構造です。本来は不活性な文字列である設定値が評価され、テキストが実行に変わります。
ローダー名、フォーマット、テンプレート、シリアル化済みオブジェクト、設定ブロブを受け取るAPIは、意図せず命令を受け取る可能性があります。敵対的な設定を送るテストがなければ、その入力が不活性なままであることを確認できていません。
セキュリティ制御としてのスキーマ検証
最初に追加しやすい防御は、APIエッジでの厳格なスキーマ検証です。スキーマはドキュメントではなく、ビジネスロジックに到達する前に不正な入力を落とすフィルターとして使えます。
JSON Schemaを使う場合、例えばデータセット設定を次のように制限できます。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"required": ["loader", "name"],
"properties": {
"loader": {
"enum": ["csv", "json", "parquet"]
},
"name": {
"type": "string",
"maxLength": 128,
"pattern": "^[\\w .-]+$"
},
"rows": {
"type": "integer",
"minimum": 0,
"maximum": 1000000
}
}
}
このスキーマには、それぞれ異なる防御効果があります。
| 制約 | 防ぐもの |
|---|---|
additionalProperties: false |
想定外のtemplateなどのフィールドの追加 |
enum |
pickle://のような未許可ローダー |
maxLength |
メモリ消費を狙った巨大文字列 |
pattern |
{{や'; DROP TABLEなど、許可形式外の文字列 |
重要なのは、攻撃パターンをすべて知ることではありません。アプリケーションが実際にサポートする入力だけを許可することです。この「狭さ」がセキュリティ特性になります。
ただし、スキーマ検証だけで全てのエクスプロイトを防げるわけではありません。スキーマは特に、「このエンドポイントが何を受け入れるか確認していない」という種類の問題を減らします。
ネガティブテスト:エンドポイントが拒否することを証明する
ハッピーパステストは、正しい入力から正しい出力が得られることを検証します。ネガティブテストは、不正な入力が制御された拒否になることを検証します。
拒否は機能です。
- 4xx: APIが境界で入力を拒否できている
- 5xx: 敵対的な入力が想定外のロジックに到達している可能性がある
各フィールドについて、少なくとも次の失敗クラスを列挙してください。
- 誤った型
- 必須フィールドの欠落
- 禁止フィールドの追加
- 長すぎる値
- 範囲外の値
- 不正な形式
- フィールドの用途に応じたインジェクション文字列
各ケースでは、少なくとも次をアサートします。
1. ステータスコードが4xxである
2. ステータスコードが5xxではない
3. 必要に応じて、副作用が発生していない
エラー文言への過度な依存は避けてください。"invalid loader"のような文字列を固定でアサートすると、無害な文言変更でもテストが壊れます。まずはステータスコードと副作用の有無を検証します。
開始用のフィールド別チェック項目は、APIセキュリティテストチェックリストも参考になります。
専用テストに値するインジェクションクラス
すべての攻撃パターンを網羅する必要はありません。まずは、再発時に明確に失敗する固定ケースを各クラスに1つずつ置きます。その後、自動化されたAPI脆弱性検出で探索範囲を広げます。
SQLインジェクション
クエリに到達しうるフィールドへ次のような文字列を送ります。
1); DROP TABLE datasets;--
期待する動作は、400での拒否または安全な空結果です。データベースエラーをレスポンスに露出してはいけません。
テンプレートインジェクション
名前やラベルなど、テンプレートに渡される可能性があるフィールドへ送ります。
{{ 7*7 }}
{{ config.__class__ }}
レスポンスに49が含まれる場合、サーバー側テンプレートが入力を評価しています。これは重大な発見です。
安全でない逆シリアル化とリモートコードローダー
平文値だけを想定するフィールドへ、許可されないローダーやシリアル化済みオブジェクトを送ります。
{
"loader": "pickle://s3/models/payload.pkl"
}
ローダーは許可リストで判定し、未知の値は拒否してください。
コマンドインジェクション
ファイル名、変換オプション、シェル引数になりうるフィールドへ送ります。
; id
$(id)
ユーザーIDなどのコマンド出力が返る200系レスポンスは、重大な脆弱性を示します。
サイズ過大、不正な形式、コンテンツタイプ混同
敵対的な入力は、巧妙な文字列だけではありません。サイズや構造が不正なペイロードは、アプリケーションのバリデーションより前にパーサーやワーカーを壊すことがあります。
サイズ過大ペイロード
次のような入力を送ります。
- 1フィールドに5MBの文字列
- 100万要素を含むJSON配列
- 想定する最大サイズを超えるファイルアップロード
健全なAPIは、過剰なメモリを確保する前にサイズ制限を適用し、通常は413 Payload Too Largeを返すべきです。
不正なJSON
以下をテストします。
- 切り詰められたJSON
- 末尾カンマを含むJSON
- 深くネストしたJSON
- 配列・オブジェクトの閉じ忘れ
期待する結果は高速な400系レスポンスです。ワーカーのハングや5xxではありません。
Content-Typeの不一致
ヘッダーとボディの不一致もテストしてください。
Content-Type: application/json
このヘッダーでXMLを送る、あるいは次のような不一致を試します。
-
application/xmlとして外部エンティティを含むペイロードを送る - JSONを
text/plainとして送る - JSON APIにフォームエンコードされたボディを送る
サーバーは、パース前にContent-Typeと期待するボディ形式の整合性を確認すべきです。
なぜAIエージェントがリスクを高めるのか
これらの入力検証の問題は、AIエージェント以前から存在します。エージェントが変えるのは、入力の量と速度です。
AIエージェントには次の特徴があります。
入力を合成する
人間が書かない組み合わせのフィールド値を生成する再試行・連鎖呼び出しを行う
1つの汚染されたドキュメントが、短時間で数千件の敵対的リクエストになる可能性がある信頼するよう指示されたデータを転送する
データセットやWebhookに隠されたペイロードが、APIへの実リクエストになる
Hugging Faceの「データをロードすることがコード実行に変わる」パターンは、エージェントが信頼境界を越えて実行してしまう種類の命令と一致します。APIチーム向けのプロンプトインジェクションも参照してください。
防御策自体は変わりません。ただし、エージェントのトラフィックを手動レビューできないため、テストと検証は自動化が必要です。
ネガティブテストスイートを構築し、変更のたびにCIで実行する
ネガティブケースは、すべてのプルリクエストで実行するテストスイートにします。以下は、ステージング環境に対して制御された拒否を確認するpytestの例です。
import httpx
import pytest
BASE = "https://staging.internal/v1"
HOSTILE_CONFIGS = [
{"loader": "pickle://s3/models/payload.pkl", "format": "auto"},
{"loader": "csv", "name": "{{ 7*7 }}"},
{"loader": "csv", "name": "{{ config.__class__ }}"},
{"loader": "csv", "filter": "1); DROP TABLE datasets;--"},
{"loader": "csv", "name": "A" * 5_000_000},
]
@pytest.mark.parametrize("config", HOSTILE_CONFIGS)
def test_dataset_config_is_refused(config):
response = httpx.post(
f"{BASE}/datasets",
json={"config": config},
timeout=10,
)
assert response.status_code in (400, 413, 422), response.text
assert response.status_code < 500, (
"5xx means the payload reached logic it should not"
)
assert "49" not in response.text, (
"template rendered: server-side template injection"
)
このテストでは、各ペイロードについて以下を確認しています。
- 不正な入力が
400、413、422のいずれかで拒否される - 不正な入力によって
5xxにならない - テンプレート式が評価されない
GitHub Actionsでは、最小限のジョブでPRとpushの両方に組み込めます。
name: api-abuse-tests
on: [push, pull_request]
jobs:
negative-input:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pytest tests/negative_input.py -q
このジョブをマージ条件に設定すれば、入力検証を緩める変更はデプロイ前に止められます。
Apidogのようなスキーマファーストのツールでは、OpenAPIコントラクトを基準にエンドポイントを設計し、リクエストとレスポンスを契約に対して検証できます。ハッピーパスの隣に、サイズ過大フィールド、誤った型、インジェクション文字列を含むネガティブシナリオを保存し、それぞれが4xxを返すことをアサートできます。Apidog CLIで同じシナリオをCI実行すれば、検証を静かに緩めた変更をビルド失敗として検出できます。Apidogをダウンロードして、既存のエンドポイントにまず1つネガティブシナリオを追加してみてください。
なお、Apidogは設計、テスト、モック、ドキュメンテーションのためのツールです。WAFの代替、ライブトラフィックのフィルタリング、SIEMの代替ではありません。また、契約検証だけで全てのエクスプロイトを防げるわけでもありません。目的は、APIが何を受け入れるかを明示し、「一度もチェックしていなかった」入力を本番で発見しないようにすることです。
よくある質問
ネガティブテストとファジングの違いは何ですか?
ネガティブテストは、意図的に選んだ不正入力を送るテストです。各失敗クラスに対して代表ケースを用意します。
ファジングは、ランダムまたは変異した大量の入力を送って、想定外のケースを探す手法です。
まずネガティブテストから始めてください。高速で決定論的なのでCIに組み込みやすく、その後に想定外の入力を探索する目的でファジングを追加します。
これらのテストは本番環境に対して実行すべきですか?
いいえ。ステージング環境または隔離されたテスト環境で実行してください。
サイズ過大ペイロードやコマンドインジェクションのプローブは、意図的にシステムへ負荷をかけます。脆弱性がある場合、データ変更などの副作用が起こる可能性もあります。
ファイアウォールやWAFがこれらを捕捉しないのですか?
WAFは多層防御の一部として有用ですが、アプリケーション自身が不正な入力を拒否する代替にはなりません。
WAFルールは回避される可能性があり、アプリケーション固有のビジネスロジックを理解できません。テストの目的は、エンドポイント自体が入力を安全に拒否することを証明することです。
エンドポイントごとにどれくらいのネガティブケースがあれば十分ですか?
フィールドごとに、影響を受ける失敗クラスを1つずつカバーすることを目安にしてください。
- 誤った型
- 範囲外
- 長すぎる値
- 禁止フィールド
- 不正な形式
- そのフィールドに適したインジェクション文字列
通常は、エンドポイントごとに数百件ではなく数件から始められます。単純な件数より、失敗クラスをカバーできているかが重要です。
スキーマ検証はインジェクションを完全に防ぎますか?
いいえ。スキーマ検証は唯一の防御層ではありません。
厳格なスキーマは、形式不正、サイズ過大、予期しないフィールドを排除できます。しかし、スキーマ上は有効な値でもSQLインジェクションやテンプレートインジェクションになる場合があります。
スキーマ検証に加えて、以下も継続してください。
- パラメータ化クエリ
- 安全な逆シリアル化
- 出力エンコーディング
- ローダーや実行可能形式の許可リスト
- CIでのネガティブテスト実行
Top comments (0)