AIコーディング 2026.05.13

AI生成コードの脆弱性チェック完全ガイド【Snyk・CVSS・GitHub Actions統合・2026年版】

タグ:Claude / セキュリティ / AI生成コード / 脆弱性 / 開発

AI生成コードが抱えやすい脆弱性パターン

ClaudeやChatGPTなどのAIは高速にコードを生成しますが、セキュリティより「動作する」ことを優先する傾向があります。特に多い脆弱性パターンは以下の4つです。

脆弱性タイプCVSS目安典型的なAI生成の問題
SQLインジェクション9.0〜9.8入力値を直接SQLに埋め込む
ハードコードシークレット7.0〜9.0APIキー・パスワードをコードに直書き
コマンドインジェクション9.0〜10.0ユーザー入力をshellで実行
不十分な入力検証5.0〜7.0バリデーションなしにデータを処理

脆弱なコードと安全なコードの対比

SQLインジェクション(CVSS 9.4相当)

# ❌ AI生成コードによくある危険なパターン
def get_user_by_name(username: str) -> dict:
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()
    # ユーザー入力を直接SQLに埋め込んでいる
    query = f"SELECT * FROM users WHERE username = '{username}'"
    cursor.execute(query)
    return cursor.fetchone()

# 攻撃例: username = "admin' OR '1'='1"
# → SELECT * FROM users WHERE username = 'admin' OR '1'='1'
# → 全ユーザーのデータが返る
# ✅ 安全な実装:プレースホルダーを使う
def get_user_by_name(username: str) -> dict | None:
    conn = sqlite3.connect("users.db")
    cursor = conn.cursor()
    # パラメータ化クエリ(プレースホルダー)を使う
    cursor.execute("SELECT * FROM users WHERE username = ?", (username,))
    return cursor.fetchone()

ハードコードシークレット(CVSS 7.5相当)

# ❌ AIが生成しやすい危険なパターン
def send_notification(message: str):
    client = anthropic.Anthropic(
        api_key="sk-ant-api03-xxxxxxxxxxxx"  # ← ハードコード禁止
    )
    # ...

DATABASE_URL = "postgresql://admin:password123@prod-db.example.com/users"  # 危険
# ✅ 環境変数から取得する
import os
from anthropic import Anthropic

def send_notification(message: str):
    api_key = os.environ.get("ANTHROPIC_API_KEY")
    if not api_key:
        raise ValueError("ANTHROPIC_API_KEY が設定されていません")
    client = Anthropic(api_key=api_key)
    # ...

DATABASE_URL = os.environ["DATABASE_URL"]  # 環境変数から取得

コマンドインジェクション(CVSS 9.8相当)

# ❌ 危険:ユーザー入力を直接シェルに渡す
import subprocess

def ping_host(hostname: str) -> str:
    result = subprocess.run(
        f"ping -c 3 {hostname}",
        shell=True,  # shell=True は危険
        capture_output=True,
        text=True
    )
    return result.stdout

# 攻撃例: hostname = "example.com; rm -rf /var/log"
# ✅ 安全な実装:引数をリスト渡し・バリデーション付き
import subprocess
import re

def ping_host(hostname: str) -> str:
    # ホスト名として有効な文字のみ許可
    if not re.match(r'^[a-zA-Z0-9.-]+$', hostname):
        raise ValueError(f"無効なホスト名: {hostname}")
    
    result = subprocess.run(
        ["ping", "-c", "3", hostname],  # shell=False(デフォルト)
        capture_output=True,
        text=True,
        timeout=10
    )
    return result.stdout

Snykを使ったCI/CD統合

GitHub Actionsへの統合

# .github/workflows/security-scan.yml
name: セキュリティスキャン

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  snyk-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Snyk セキュリティスキャン(コード)
        uses: snyk/actions/python@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high  # High以上の脆弱性でCI失敗
      
      - name: Snyk 依存関係スキャン
        uses: snyk/actions/python@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          command: test
          args: --file=requirements.txt --severity-threshold=high

Snyk CLIでローカルスキャン

# インストール
npm install -g snyk

# 認証
snyk auth

# Pythonプロジェクトをスキャン
snyk test --file=requirements.txt

# コードの静的解析(SAST)
snyk code test

# 脆弱性のレポートをJSONで出力
snyk test --json > snyk-report.json

AI生成コードをセキュアにするプロンプト設計

# セキュリティを意識したプロンプト例

Pythonで以下の仕様のAPIエンドポイントを実装してください:

[仕様]
- ユーザー検索API(GET /users?name=xxx)
- SQLiteデータベースを使用

[セキュリティ要件(必須)]
- SQLはパラメータ化クエリのみ使用(f-stringでSQL組み立て禁止)
- 入力値のバリデーション(長さ・文字種の制限)
- APIキー・パスワードは環境変数から取得
- エラー時にスタックトレースを返さない
- OWASP Top 10 を考慮した実装

[禁止事項]
- shell=True のsubprocess
- eval() や exec() の使用
- ハードコードされた認証情報

ローカルでのセキュリティチェックリスト

AI生成コードをレビューする際のチェック項目です:

# security_checklist.py
# AIが生成したコードをレビューする際の確認事項

CHECKLIST = {
    "SQLインジェクション": [
        "f-string や %s でSQL文を組み立てていないか",
        "execute() の第2引数にタプルでパラメータを渡しているか",
    ],
    "シークレット管理": [
        "os.environ[] または os.getenv() で取得しているか",
        ".env ファイルが .gitignore に含まれているか",
        "コード中にsk-、ghp_、Bearer などの文字列がないか",
    ],
    "入力検証": [
        "外部からの入力をそのまま使っていないか",
        "型チェック・長さチェック・文字種チェックをしているか",
    ],
    "コマンド実行": [
        "shell=True を使っていないか",
        "ユーザー入力をコマンドに渡す前にバリデーションしているか",
    ],
    "エラーハンドリング": [
        "例外のスタックトレースを外部に返していないか",
        "詳細なエラー情報を本番環境でログに残しているか",
    ],
}

for category, items in CHECKLIST.items():
    print(f"\n## {category}")
    for item in items:
        print(f"  □ {item}")

あわせて読みたい

参考ソース