Secure Coding
Práticas de código seguro.
OWASP Top 10
1. Injection (SQL, Command, etc.)
✅ Correto:
# SQLAlchemy ORM (safe)
user = await session.execute(
select(User).where(User.email == user_email)
)
# Prepared statement
await session.execute(
text("SELECT * FROM users WHERE email = :email"),
{"email": user_email}
)
❌ Incorreto:
# SQL injection vulnerability
query = f"SELECT * FROM users WHERE email = '{user_email}'"
await session.execute(text(query))
2. Authentication & Authorization
# Sempre verificar autenticação
@router.get("/users/me")
async def get_current_user(
current_user: User = Depends(get_current_user) # ✅ Required auth
):
return current_user
# Verificar autorização
@router.delete("/users/{user_id}")
async def delete_user(
user_id: int,
current_user: User = Depends(get_current_user)
):
# ✅ Check permission
if not current_user.is_admin and current_user.id != user_id:
raise HTTPException(status_code=403, detail="Forbidden")
await service.delete_user(user_id)
3. Sensitive Data Exposure
# ✅ Nunca logar senhas
logger.info("User login attempt", extra={"email": email}) # OK
logger.info("Login", extra={"password": password}) # ❌ NEVER
# ✅ Hash passwords
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
# ✅ Não retornar dados sensíveis em API
class UserResponse(BaseModel):
id: int
email: str
name: str
# password_hash: NEVER include this!
4. XML External Entities (XXE)
Não usamos XML, mas se usar:
5. Broken Access Control
# ✅ Sempre verificar ownership
@router.get("/orders/{order_id}")
async def get_order(
order_id: int,
current_user: User = Depends(get_current_user)
):
order = await service.get_order(order_id)
# Verify user owns this order
if order.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(status_code=403)
return order
6. Security Misconfiguration
# ✅ Secure defaults
app = FastAPI(
docs_url="/docs" if DEBUG else None, # Disable in production
redoc_url="/redoc" if DEBUG else None,
debug=DEBUG
)
# ✅ CORS configuration
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://seuapp.com"], # Specific, not "*"
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
7. XSS (Cross-Site Scripting)
Frontend:
// ✅ React escapes by default
<div>{user.name}</div> // Safe
// ❌ Dangerous
<div dangerouslySetInnerHTML={{__html: user.bio}} /> // Only if sanitized!
// ✅ Sanitize if needed
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(user.bio)}} />
8. Insecure Deserialization
# ❌ Nunca usar pickle com dados não confiáveis
import pickle
data = pickle.loads(user_input) # DANGEROUS
# ✅ Usar JSON
import json
data = json.loads(user_input)
9. Using Components with Known Vulnerabilities
# Scan dependencies regularmente
pip-audit # Python
npm audit # Node
# Atualizar dependencies
pip install --upgrade package
npm update package
10. Insufficient Logging & Monitoring
# ✅ Log security events
logger.warning("Failed login attempt", extra={
"email": email,
"ip_address": request.client.host,
"user_agent": request.headers.get("user-agent")
})
logger.error("Unauthorized access attempt", extra={
"user_id": current_user.id,
"resource": resource_id,
"action": "delete"
})
Input Validation
Pydantic Models
from pydantic import BaseModel, Field, validator, EmailStr
class CreateUserRequest(BaseModel):
email: EmailStr # Validates email format
name: str = Field(..., min_length=2, max_length=100)
age: int = Field(..., ge=0, le=150)
password: str = Field(..., min_length=8)
@validator('password')
def password_strength(cls, v):
if not any(c.isupper() for c in v):
raise ValueError('Must contain uppercase')
if not any(c.isdigit() for c in v):
raise ValueError('Must contain digit')
return v
@validator('name')
def name_no_special_chars(cls, v):
if not v.replace(' ', '').isalnum():
raise ValueError('Name contains invalid characters')
return v
Rate Limiting
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.get("/api/public")
@limiter.limit("10/minute")
async def public_endpoint():
return {"data": "value"}
HTTPS Only
# Redirect HTTP to HTTPS
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware
if not DEBUG:
app.add_middleware(HTTPSRedirectMiddleware)
Security Headers
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
return response
Dependency Security
Code Scanning
Bandit (Python)
# Install
pip install bandit
# Scan
bandit -r app/ -f json -o bandit-report.json
# CI integration
bandit -r app/ -ll # Only high/medium severity