mcpField
P
python-security

python-security

github.com/claude-dev-suiteセキュリティ4651 回インストール2 日前に更新
リポジトリ

python-security について

Python 開発者向けのセキュリティリファレンススキルです。脆弱性チェック、安全なコーディング、Web アプリの一般的なリスクを実例とともに扱います。

主な機能

  1. 01pip-audit と safety による依存関係監査
  2. 02Bandit の静的解析と設定
  3. 03SQL インジェクションと XSS の防止
  4. 04認証と入力検証のガイダンス
  5. 05安全なアップロード、シークレット、ログ、CORS

活用シーン

  1. 01Python 依存関係の既知の脆弱性を監査
  2. 02コードの一般的なセキュリティ問題を確認
  3. 03より安全な認証と入力処理を実装

インストール

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

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill python-security

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

詳細

Python セキュリティ - クイックリファレンス

このスキルを使用しない場合

  • OWASP の一般的な概念 - owasp または owasp-top-10 スキルを使用してください
  • Node.js/TypeScript のセキュリティ - 基本のセキュリティスキルを使用してください
  • Java のセキュリティ - java-security スキルを使用してください
  • シークレット管理 - secrets-management スキルを使用してください

詳細な知識: フレームワーク固有のセキュリティドキュメントについては、technology に fastapi または django を指定して mcp__documentation__fetch_docs を使用してください。

依存関係の監査

# pip-audit - Official Python audit tool
pip-audit

# Safety - Check for known vulnerabilities
safety check

# pip-audit with requirements file
pip-audit -r requirements.txt

# Snyk for Python
snyk test

# Check outdated packages
pip list --outdated
CI/CD との統合
# GitHub Actions
- name: Security audit
  run: |
    pip install pip-audit safety
    pip-audit
    safety check

Bandit - 静的解析

# Run Bandit on project
bandit -r src/

# Generate JSON report
bandit -r src/ -f json -o bandit-report.json

# Skip specific tests
bandit -r src/ --skip B101,B601

# High severity only
bandit -r src/ -ll
Bandit の設定(.bandit)
# .bandit
skips: ['B101']  # Skip assert warnings in tests
exclude_dirs: ['tests', 'venv']
よくある Bandit の警告
コード問題修正方法
B101assert が使用されています本番環境では適切な検証を使用してください
B105パスワードがハードコードされています環境変数を使用してください
B301Pickle が使用されていますJSON またはより安全なシリアライズを使用してください
B601シェルインジェクションリスト形式の引数で subprocess を使用してください
B608SQL インジェクションパラメーター化クエリを使用してください

SQL インジェクションの防止

SQLAlchemy - 安全
# SAFE - Parameterized query
from sqlalchemy import text

result = db.execute(
    text("SELECT * FROM users WHERE email = :email"),
    {"email": email}
)

# SAFE - ORM query
user = db.query(User).filter(User.email == email).first()

# SAFE - FastAPI with SQLAlchemy
@app.get("/users/{user_id}")
async def get_user(user_id: int, db: Session = Depends(get_db)):
    return db.query(User).filter(User.id == user_id).first()
SQLAlchemy - 安全ではありません
# UNSAFE - String formatting
query = f"SELECT * FROM users WHERE email = '{email}'"  # NEVER!
db.execute(query)

# UNSAFE - % formatting
query = "SELECT * FROM users WHERE email = '%s'" % email  # NEVER!
Django ORM - 安全
# SAFE - ORM querysets
User.objects.filter(email=email)
User.objects.get(pk=user_id)

# SAFE - Raw query with params
User.objects.raw("SELECT * FROM users WHERE email = %s", [email])

# SAFE - Extra with params
User.objects.extra(where=["email = %s"], params=[email])

XSS の防止

Django テンプレート(自動エスケープ)

{{ user_input }}


{{ user_input|safe }}  
FastAPI/Jinja2
# Configure auto-escaping
from jinja2 import Environment, select_autoescape

env = Environment(
    autoescape=select_autoescape(['html', 'xml'])
)
手動によるサニタイズ
# Use bleach for HTML sanitization
import bleach

clean_html = bleach.clean(
    user_input,
    tags=['p', 'b', 'i', 'a'],
    attributes={'a': ['href']},
    strip=True
)

認証 - FastAPI

python-jose を使った JWT
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext

SECRET_KEY = os.environ["JWT_SECRET"]
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def create_access_token(data: dict) -> str:
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_password(plain: str, hashed: str) -> bool:
    return pwd_context.verify(plain, hashed)

def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)
FastAPI OAuth2 依存関係
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = get_user(username)
    if user is None:
        raise credentials_exception
    return user

認証 - Django

Django の設定
# settings.py
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
]

AUTH_PASSWORD_VALIDATORS = [
    {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
     'OPTIONS': {'min_length': 12}},
    {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
    {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]

SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = True
Django のレート制限
# Using django-ratelimit
from django_ratelimit.decorators import ratelimit

@ratelimit(key='ip', rate='5/m', method='POST', block=True)
def login_view(request):
    # login logic
    pass

入力の検証

Pydantic を使った FastAPI
from pydantic import BaseModel, EmailStr, Field, validator
import re

class CreateUserRequest(BaseModel):
    email: EmailStr
    password: str = Field(..., min_length=12, max_length=128)
    name: str = Field(..., min_length=2, max_length=100)

    @validator('password')
    def password_strength(cls, v):
        if not re.search(r'[A-Z]', v):
            raise ValueError('Password must contain uppercase')
        if not re.search(r'[a-z]', v):
            raise ValueError('Password must contain lowercase')
        if not re.search(r'\d', v):
            raise ValueError('Password must contain digit')
        if not re.search(r'[@$!%*?&]', v):
            raise ValueError('Password must contain special character')
        return v

    @validator('name')
    def name_alphanumeric(cls, v):
        if not re.match(r"^[a-zA-Z\s\-']+$", v):
            raise ValueError('Name contains invalid characters')
        return v
Django フォーム
from django import forms
from django.core.validators import RegexValidator

class UserRegistrationForm(forms.Form):
    email = forms.EmailField(max_length=255)
    password = forms.CharField(
        min_length=12,
        max_length=128,
        widget=forms.PasswordInput
    )
    name = forms.CharField(
        min_length=2,
        max_length=100,
        validators=[RegexValidator(r"^[a-zA-Z\s\-']+$")]
    )

コマンドインジェクションの防止

import subprocess
import shlex

# SAFE - Use list arguments
subprocess.run(["ls", "-la", directory], check=True)

# SAFE - Use shlex.split for shell-like parsing
subprocess.run(shlex.split(f"ls -la {shlex.quote(directory)}"), check=True)

# UNSAFE - shell=True with user input
subprocess.run(f"ls -la {directory}", shell=True)  # NEVER!

# UNSAFE - os.system
os.system(f"ls -la {directory}")  # NEVER!

安全なファイルアップロード

FastAPI
from fastapi import UploadFile, HTTPException
import uuid

ALLOWED_TYPES = {"image/jpeg", "image/png", "application/pdf"}
MAX_SIZE = 10 * 1024 * 1024  # 10MB

@app.post("/upload")
async def upload_file(file: UploadFile):
    # Validate content type
    if file.content_type not in ALLOWED_TYPES:
        raise HTTPException(400, "File type not allowed")

    # Read and check size
    contents = await file.read()
    if len(contents) > MAX_SIZE:
        raise HTTPException(400, "File too large")

    # Generate safe filename
    ext = Path(file.filename).suffix if file.filename else ""
    safe_name = f"{uuid.uuid4()}{ext}"

    # Store outside web root
    file_path = UPLOAD_DIR / safe_name
    async with aiofiles.open(file_path, 'wb') as f:
        await f.write(contents)

    return {"filename": safe_name}

シークレット管理

import os
from functools import lru_cache
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    jwt_secret: str
    api_key: str

    class Config:
        env_file = ".env"
        env_file_encoding = "utf-8"

@lru_cache
def get_settings():
    return Settings()

# Usage
settings = get_settings()
db_url = settings.database_url

# NEVER hardcode secrets
# JWT_SECRET = "hardcoded-secret"  # NEVER!

セキュリティイベントのログ記録

import logging
from datetime import datetime

logger = logging.getLogger("security")

def log_login_attempt(username: str, success: bool, ip: str):
    logger.info(
        "Login attempt",
        extra={
            "user": username,
            "success": success,
            "ip": ip,
            "timestamp": datetime.utcnow().isoformat()
        }
    )

def log_access_denied(username: str, resource: str, ip: str):
    logger.warning(
        "Access denied",
        extra={
            "user": username,
            "resource": resource,
            "ip": ip
        }
    )

# NEVER log sensitive data
# logger.info(f"Password: {password}")  # NEVER!
# logger.info(f"Token: {token}")        # NEVER!

CORS の設定

FastAPI
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.com"],  # Not ["*"] in production
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)
Django
# settings.py with django-cors-headers
CORS_ALLOWED_ORIGINS = [
    "https://myapp.com",
]
CORS_ALLOW_CREDENTIALS = True

アンチパターン

アンチパターン問題点正しい方法
subprocess での shell=Trueコマンドインジェクションリスト形式の引数を使用してください
SQL クエリ内の f-stringSQL インジェクションパラメーター化クエリを使用してください
pickle.loads(user_data)任意のコード実行JSON を使用するか、ソースを検証してください
eval(user_input)コードインジェクションユーザー入力に対して eval を決して使用しないでください
シークレットのハードコードシークレットの漏えい環境変数を使用してください
本番環境での DEBUG=True情報漏えいDEBUG=False に設定してください
{{user_input|safe}}XSS の脆弱性サニタイズ済みの場合を除き、使用を避けてください

クイックトラブルシューティング

問題考えられる原因解決策
Bandit B105 の警告パスワード文字列がハードコードされています環境変数に移してください
pip-audit が CVE を検出するパッケージに脆弱性があります修正済みバージョンに更新してください
ブラウザーで CORS エラーが発生するオリジンが許可されていません許可リストにオリジンを追加してください
JWT のデコードに失敗するシークレットが間違っているか、期限切れですシークレットと有効期限を確認してください
パスワードハッシュが一致しないハッシュアルゴリズムが異なりますハッシュと検証で同じコンテキストを使用してください
レート制限が機能しないミドルウェアの順序が間違っていますルートより前にレート制限機能を追加してください

セキュリティスキャンコマンド

# Static analysis
bandit -r src/

# Dependency audit
pip-audit
safety check

# All-in-one with Snyk
snyk test

# Check for secrets
gitleaks detect
trufflehog git file://.

# Django security check
python manage.py check --deploy

関連スキル

コメント