---
name: security-checklist
description: Backend security vulnerability checklist for Python/FastAPI applications including authentication, injection prevention, and data protection
---

# Security Checklist

이 Skill은 백엔드 코드의 보안 취약점을 검증하기 위한 체크리스트입니다.

---

## 🔴 Critical Security Issues

### 1. Hardcoded Secrets
```python
# ❌ CRITICAL: Hardcoded credentials
API_KEY = "sk-1234567890abcdef"
DB_PASSWORD = "super_secret_password"
JWT_SECRET = "my-jwt-secret"

# ✅ GOOD: Environment variables
API_KEY = os.getenv("API_KEY")
DB_PASSWORD = settings.database.password
JWT_SECRET = config.jwt_secret
```

### 2. SQL Injection
```python
# ❌ CRITICAL: String interpolation in SQL
query = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(query)

# ✅ GOOD: Parameterized queries
query = "SELECT * FROM users WHERE id = :id"
cursor.execute(query, {"id": user_id})

# ✅ GOOD: SQLAlchemy ORM
user = session.query(User).filter(User.id == user_id).first()
```

### 3. Authentication Bypass
```python
# ❌ CRITICAL: No auth check
@router.get("/admin/users")
async def get_all_users():
    return await repo.get_all()

# ✅ GOOD: Proper authentication
@router.get("/admin/users")
async def get_all_users(
    current_user: User = Depends(get_current_admin_user)
):
    return await repo.get_all()
```

### 4. Authorization Gaps
```python
# ❌ CRITICAL: No ownership check
@router.delete("/posts/{post_id}")
async def delete_post(post_id: int):
    await repo.delete(post_id)

# ✅ GOOD: Verify ownership
@router.delete("/posts/{post_id}")
async def delete_post(
    post_id: int,
    current_user: User = Depends(get_current_user)
):
    post = await repo.get(post_id)
    if post.author_id != current_user.id:
        raise HTTPException(403, "Not authorized")
    await repo.delete(post_id)
```

---

## 🟡 Warning Level Issues

### 5. Sensitive Data in Logs
```python
# ❌ WARNING: Logging sensitive data
logger.info(f"User login: {email}, password: {password}")
logger.debug(f"API response: {response.json()}")  # May contain PII

# ✅ GOOD: Mask sensitive data
logger.info(f"User login: {email}")
logger.debug(f"API response: {mask_pii(response.json())}")
```

### 6. Missing Input Validation
```python
# ❌ WARNING: No validation
@router.post("/users")
async def create_user(data: dict):
    return await service.create(data)

# ✅ GOOD: Pydantic validation
class UserCreate(BaseModel):
    email: EmailStr
    age: int = Field(ge=0, le=150)
    
@router.post("/users")
async def create_user(data: UserCreate):
    return await service.create(data)
```

### 7. Missing Rate Limiting
```python
# ❌ WARNING: No rate limiting on public endpoint
@router.post("/auth/login")
async def login(credentials: LoginRequest):
    return await auth_service.login(credentials)

# ✅ GOOD: Rate limiting applied
@router.post("/auth/login")
@limiter.limit("5/minute")
async def login(request: Request, credentials: LoginRequest):
    return await auth_service.login(credentials)
```

### 8. Insecure Password Handling
```python
# ❌ WARNING: Plain text or weak hashing
hashed = hashlib.md5(password.encode()).hexdigest()

# ✅ GOOD: Use bcrypt or argon2
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"])
hashed = pwd_context.hash(password)
```

---

## 💡 Best Practices

### 9. Error Message Leakage
```python
# ❌ BAD: Exposes internal details
except Exception as e:
    raise HTTPException(500, f"Database error: {str(e)}")

# ✅ GOOD: Generic message, log details
except Exception as e:
    logger.error(f"Database error: {e}")
    raise HTTPException(500, "An error occurred")
```

### 10. CORS Configuration
```python
# ❌ BAD: Allow all origins
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
)

# ✅ GOOD: Specific origins
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://myapp.com"],
    allow_credentials=True,
)
```

### 11. JWT Best Practices
```python
# ✅ Include expiration
token = jwt.encode(
    {"sub": user_id, "exp": datetime.utcnow() + timedelta(hours=1)},
    SECRET_KEY,
    algorithm="HS256"
)

# ✅ Verify expiration on decode
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
```

---

## Security Review Checklist

| Check | Severity | Status |
|-------|----------|--------|
| No hardcoded secrets | 🔴 CRITICAL | ☐ |
| Parameterized SQL queries | 🔴 CRITICAL | ☐ |
| Authentication on protected routes | 🔴 CRITICAL | ☐ |
| Authorization/ownership checks | 🔴 CRITICAL | ☐ |
| No sensitive data in logs | 🟡 WARNING | ☐ |
| Input validation present | 🟡 WARNING | ☐ |
| Rate limiting on auth endpoints | 🟡 WARNING | ☐ |
| Secure password hashing | 🟡 WARNING | ☐ |
| Generic error messages | 💡 SUGGESTION | ☐ |
| Proper CORS config | 💡 SUGGESTION | ☐ |
| JWT expiration set | 💡 SUGGESTION | ☐ |
