mcpField
A
ai-security-hardening

ai-security-hardening

github.com/bagelholeセキュリティ1.2k328 回インストール23 週間前に更新
リポジトリ

ai-security-hardening について

本番環境の AI システムを保護するためのセキュリティ強化スキルです。プロンプトインジェクション、データ漏えい、脱獄、サプライチェーンのリスクに対し、フィルタリング、API 保護、モデル検証、ネットワーク分離、監査を扱います。

主な機能

  1. 01プロンプトインジェクション対策
  2. 02出力フィルタリングと PII の除去
  3. 03LLM API のアクセス制御とレート制限
  4. 04モデルウェイトの完全性検証
  5. 05AI サービスのネットワーク分離
  6. 06対話の監査ログ

活用シーン

  1. 01機密データを扱う LLM アプリの保護
  2. 02AI エージェントへのプロンプトインジェクションリスク低減
  3. 03モデル出力に含まれる個人情報のフィルタリング
  4. 04モデルウェイトと API エンドポイントの保護

インストール

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

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill ai-security-hardening

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

詳細

AI セキュリティの強化

本番環境で、プロンプトインジェクション、ジェイルブレイク、データ漏えい、サプライチェーンの脅威から LLM および AI システムを保護します。

このスキルを使用する場面

次のような場合に、このスキルを使用してください。

  • 機密性の高いユーザーデータを扱う LLM 搭載アプリケーションをデプロイする場合
  • AI エージェントへのプロンプトインジェクション攻撃を防ぐ場合
  • 出力フィルタリングとコンテンツモデレーションを実装する場合
  • モデルの重みと API エンドポイントを窃取から保護する場合
  • AI システムで SOC2 または ISO 27001 への準拠を達成する場合

AI 固有の脅威モデル

Threat                    Risk                          Control
─────────────────────────────────────────────────────────────────────
Prompt injection          System prompt override         Input sanitization, separate context
Data exfiltration         PII in model outputs           Output filtering, DLP scanning
Jailbreaking             Policy bypass                  Content moderation, guardrails
Model theft               Weight extraction via API      Rate limiting, access controls
Training data poisoning   Backdoored fine-tuned model    Dataset validation, provenance
Supply chain attack       Malicious model weights        Signature verification, scanning
Insecure output           XSS/SQLi from LLM response     Output encoding, parameterized queries

プロンプトインジェクション対策

import re
from typing import Optional

INJECTION_PATTERNS = [
    r"ignore\s+(all\s+)?(previous|prior|above)\s+instructions",
    r"you\s+are\s+now\s+",
    r"new\s+instructions?:",
    r"system\s+prompt",
    r"forget\s+everything",
    r"act\s+as\s+",
    r"jailbreak",
    r"dan\s+mode",
    r"<\s*system\s*>",
    r"\[INST\]",
]

def detect_prompt_injection(user_input: str) -> tuple[bool, Optional[str]]:
    """Return (is_suspicious, matched_pattern)."""
    normalized = user_input.lower().strip()
    for pattern in INJECTION_PATTERNS:
        if re.search(pattern, normalized, re.IGNORECASE):
            return True, pattern
    return False, None

def sanitize_user_input(user_input: str, max_length: int = 4000) -> str:
    """Sanitize input before passing to LLM."""
    # Truncate
    user_input = user_input[:max_length]

    # Remove null bytes and control characters
    user_input = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', user_input)

    # Check for injection
    suspicious, pattern = detect_prompt_injection(user_input)
    if suspicious:
        raise ValueError(f"Potential prompt injection detected: {pattern}")

    return user_input

NeMo Guardrails によるガードレール

# guardrails.yaml
from nemoguardrails import RailsConfig, LLMRails

config = RailsConfig.from_path("./guardrails-config")
rails = LLMRails(config)

async def safe_llm_call(user_message: str) -> str:
    response = await rails.generate_async(
        messages=[{"role": "user", "content": user_message}]
    )
    return response["content"]
# guardrails-config/config.yml
models:
  - type: main
    engine: openai
    model: gpt-4o-mini

rails:
  input:
    flows:
      - check jailbreak
      - check sensitive data
  output:
    flows:
      - check output for PII
      - check output for harmful content

出力フィルタリングと PII の除去

import re
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine

analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()

PII_ENTITIES = ["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER", "CREDIT_CARD",
                "US_SSN", "IBAN_CODE", "IP_ADDRESS", "LOCATION"]

def scrub_pii_from_output(text: str) -> str:
    """Remove PII from LLM output before returning to user."""
    results = analyzer.analyze(text=text, entities=PII_ENTITIES, language="en")
    if not results:
        return text
    anonymized = anonymizer.anonymize(text=text, analyzer_results=results)
    return anonymized.text

def validate_output_safety(output: str) -> bool:
    """Check output doesn't contain prompt injection artifacts."""
    dangerous_patterns = [
        r"<\s*script\s*>",         # XSS
        r"javascript:",             # XSS
        r";\s*(DROP|DELETE|INSERT)",# SQLi
        r"\$\{.*\}",               # template injection
        r"`.*`",                   # command injection in some contexts
    ]
    for pattern in dangerous_patterns:
        if re.search(pattern, output, re.IGNORECASE):
            return False
    return True

LLM エンドポイントの API セキュリティ

from fastapi import FastAPI, HTTPException, Depends, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
import time
from collections import defaultdict

app = FastAPI()
security = HTTPBearer()

# Rate limiting (per API key)
request_counts = defaultdict(list)

def rate_limit(api_key: str, max_requests: int = 100, window_seconds: int = 60):
    now = time.time()
    requests = request_counts[api_key]
    # Remove old requests outside window
    request_counts[api_key] = [t for t in requests if now - t < window_seconds]
    if len(request_counts[api_key]) >= max_requests:
        raise HTTPException(status_code=429, detail="Rate limit exceeded")
    request_counts[api_key].append(now)

async def verify_token(
    credentials: HTTPAuthorizationCredentials = Depends(security)
) -> dict:
    try:
        payload = jwt.decode(credentials.credentials, SECRET_KEY, algorithms=["HS256"])
        rate_limit(payload["sub"])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.post("/v1/chat/completions")
async def chat(request: Request, token: dict = Depends(verify_token)):
    body = await request.json()

    # Input validation
    user_msg = body.get("messages", [{}])[-1].get("content", "")
    try:
        safe_input = sanitize_user_input(user_msg)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    # Call LLM and scrub output
    response = await call_llm(safe_input, token["scope"])
    response["choices"][0]["message"]["content"] = scrub_pii_from_output(
        response["choices"][0]["message"]["content"]
    )
    return response

モデルの重みのセキュリティ

# Verify model weights with SHA-256 hash before loading
MODEL_DIR="./models/llama-3.1-8b"
EXPECTED_HASH="sha256:abc123..."

# Generate hash of downloaded model
actual_hash=$(find "$MODEL_DIR" -name "*.safetensors" | sort | xargs sha256sum | sha256sum)
echo "Model hash: $actual_hash"

# Compare (automate in CI/CD)
if [ "$actual_hash" != "$EXPECTED_HASH" ]; then
  echo "ERROR: Model hash mismatch — possible tampering!"
  exit 1
fi

# Scan model files for embedded malware (ModelScan)
pip install modelscan
modelscan scan -p "$MODEL_DIR"

AI サービスのネットワーク分離

# Kubernetes NetworkPolicy — isolate LLM API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: llm-api-isolation
  namespace: ai-services
spec:
  podSelector:
    matchLabels:
      app: vllm
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: backend           # only backend can call LLM
    ports:
    - protocol: TCP
      port: 8000
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          name: monitoring        # metrics only
    ports:
    - protocol: TCP
      port: 9090
  # Block egress to internet — prevent data exfiltration
  # (allow only internal cluster traffic)

監査ログ

import structlog
from datetime import datetime, timezone

audit_log = structlog.get_logger("ai.audit")

def log_llm_interaction(
    user_id: str,
    session_id: str,
    model: str,
    prompt_tokens: int,
    completion_tokens: int,
    was_filtered: bool,
    injection_detected: bool,
):
    audit_log.info(
        "llm_interaction",
        timestamp=datetime.now(timezone.utc).isoformat(),
        user_id=user_id,
        session_id=session_id,
        model=model,
        prompt_tokens=prompt_tokens,
        completion_tokens=completion_tokens,
        was_filtered=was_filtered,
        injection_detected=injection_detected,
        # DO NOT log prompt/completion content — PII risk
    )

よくある問題

問題原因対処方法
インジェクションの誤検知によるブロック範囲が広すぎる正規表現パターンを調整します。トラフィックの多い環境では、ML ベースの分類器を使用します
モデル出力に含まれる PIIPII データでモデルがトレーニングされている出力レイヤーに Presidio による除去処理を追加します
API キーの漏えいログやレスポンスにキーが含まれているログ内のキーをマスキングし、キーの保管にはボールトを使用します
モデルの重みの改ざん未検証のダウンロードSHA-256 を必ず検証し、modelscan を使用します
レート制限の回避ユーザー単位ではなく IP 単位で制限しているIP ではなく、認証済みユーザー ID に基づいてレート制限を行います

ベストプラクティス

  • 生のプロンプトや生成結果をログに記録しないでください。PII や機密データが含まれている可能性があります。
  • LLM の出力は信頼できない入力として扱い、HTML にレンダリングする前に必ずエンコードしてください。
  • ネットワークポリシーを使用して、LLM ポッドから外部インターネットへの接続を防いでください。
  • API キーは四半期ごとにローテーションし、サービス間認証には有効期間の短い JWT トークンを使用してください。
  • インターネットからダウンロードしたモデルは、サービングする前に必ず modelscan を実行してください。

関連スキル

コメント