# Swe Security

> Diretrizes de segurança para projetos Python. Use quando o usuário pedir para revisar segurança de código, checar vulnerabilidades, configurar autenticação/autorização, tratar inputs de usuário, gerenciar secrets, validar dados, revisar headers HTTP, configurar CORS, prevenir SQL injection/XSS/CSRF, ou qualquer tarefa relacionada à segurança de aplicações web Python.

- Skill: `nxs-cafi/swe-security` (Agent Skill)
- Install (CLI): `npx skillmds@latest add nxs-cafi/swe-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/nxs-cafi/swe-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: nxs-cafi (https://skillmd.com/u/nxs-cafi)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/nxs-cafi/swe-security

---


# Segurança — Python SWE Agent

## OWASP Top 10 — Aplicação em Python

### A01 — Broken Access Control

```python
# ❌ RUIM: sem verificação de ownership
@router.get("/orders/{order_id}")
async def get_order(order_id: UUID, current_user: User = Depends(get_current_user)):
    return await order_repo.find_by_id(order_id)  # qualquer usuário acessa qualquer pedido!

# ✅ BOM: verifica que o recurso pertence ao usuário
@router.get("/orders/{order_id}")
async def get_order(
    order_id: UUID,
    current_user: User = Depends(get_current_user),
    use_case: GetOrderUseCase = Depends(),
):
    order = await use_case.execute(order_id=order_id, requester_id=current_user.id)
    if order is None:
        raise HTTPException(status_code=404)
    return order

# No use case:
class GetOrderUseCase:
    async def execute(self, order_id: UUID, requester_id: UUID) -> Order | None:
        order = await self._repo.find_by_id(order_id)
        if order is None:
            return None
        if order.customer_id != requester_id:
            raise ForbiddenError("You don't have access to this order")
        return order
```

### A02 — Cryptographic Failures

```python
# ❌ NUNCA: MD5/SHA1 para senhas, dados sensíveis em texto plano
import hashlib
hashed = hashlib.md5(password.encode()).hexdigest()  # PROIBIDO

# ✅ BOM: bcrypt/argon2 para senhas
from passlib.context import CryptContext

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

def hash_password(plain: str) -> str:
    return pwd_context.hash(plain)

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

# ✅ Dados sensíveis em trânsito: sempre HTTPS, nunca logar PII
# ❌ NUNCA logar:
logger.info(f"User logged in: email={user.email}, password={password}")  # PROIBIDO
# ✅ BOM:
logger.info("User logged in", extra={"user_id": str(user.id)})
```

### A03 — Injection

```python
# ❌ RUIM: SQL raw com f-string
cursor.execute(f"SELECT * FROM users WHERE email = '{email}'")  # SQL INJECTION!

# ✅ BOM: ORM ou parameterized query
# Django ORM
User.objects.filter(email=email)

# SQLAlchemy
session.query(User).filter(User.email == email)

# Raw SQL seguro com parâmetros
cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

# ❌ RUIM: eval/exec de input de usuário
eval(user_input)  # NUNCA FAÇA ISSO

# ❌ RUIM: subprocess sem sanitização
import subprocess
subprocess.run(f"ls {user_path}", shell=True)  # Command Injection!

# ✅ BOM: lista de argumentos, sem shell=True
subprocess.run(["ls", user_path], shell=False)
```

### A07 — Authentication Failures

```python
# JWT seguro com FastAPI
from jose import JWTError, jwt
from datetime import datetime, timedelta, timezone

SECRET_KEY = os.environ["JWT_SECRET_KEY"]  # nunca hardcoded
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

def create_access_token(subject: str) -> str:
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    payload = {"sub": subject, "exp": expire, "iat": datetime.now(timezone.utc)}
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

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

    user = await user_repo.find_by_id(UUID(user_id))
    if user is None:
        raise credentials_exception
    return user
```

### A05 — Security Misconfiguration

```python
# FastAPI — headers de segurança obrigatórios
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware

app = FastAPI()

# CORS restritivo em produção
app.add_middleware(
    CORSMiddleware,
    allow_origins=os.environ.get("ALLOWED_ORIGINS", "").split(","),  # não "*" em prod!
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

# Remover headers que revelam stack
@app.middleware("http")
async def remove_server_header(request, call_next):
    response = await call_next(request)
    response.headers.pop("server", None)
    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    return response
```

## Gestão de Secrets

```python
# ❌ NUNCA: secrets no código
DATABASE_URL = "postgresql://user:password123@localhost/db"  # PROIBIDO
API_KEY = "sk-1234567890abcdef"  # PROIBIDO

# ✅ BOM: sempre via variável de ambiente + validação no boot
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")

    database_url: str  # obrigatório — falha no boot se ausente
    secret_key: str
    redis_url: str = "redis://localhost:6379"

    # validação extra
    @validator("secret_key")
    def secret_key_must_be_strong(cls, v):
        if len(v) < 32:
            raise ValueError("SECRET_KEY must be at least 32 characters")
        return v

settings = Settings()  # lança exceção se variável obrigatória faltar
```

```bash
# .gitignore — SEMPRE incluir
.env
.env.local
.env.*.local
*.pem
*.key
secrets/
```

## Validação de Input

```python
# FastAPI + Pydantic v2 — validação automática
from pydantic import BaseModel, field_validator, EmailStr, constr
from typing import Annotated

class CreateUserRequest(BaseModel):
    email: EmailStr
    password: Annotated[str, constr(min_length=8, max_length=72)]
    name: Annotated[str, constr(min_length=1, max_length=100, strip_whitespace=True)]

    @field_validator("password")
    @classmethod
    def password_complexity(cls, v: str) -> str:
        if not any(c.isupper() for c in v):
            raise ValueError("Password must contain at least one uppercase letter")
        if not any(c.isdigit() for c in v):
            raise ValueError("Password must contain at least one digit")
        return v
```

## Ferramentas de Segurança — CI Obrigatório

```yaml
# .github/workflows/security.yml
- name: Bandit (SAST)
  run: bandit -r src/ -ll -ii

- name: Safety (CVEs em dependências)
  run: safety check --full-report

- name: Semgrep
  run: semgrep --config=p/python --error

- name: Trivy (container scan)
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: fs
    severity: HIGH,CRITICAL
    exit-code: 1
```

```bash
# Instalar e rodar localmente
pip install bandit safety semgrep
bandit -r src/ -ll           # encontra problemas comuns de segurança
safety check                  # verifica CVEs nas dependências
semgrep --config=p/python .   # análise estática avançada
```

## Checklist de Segurança — Code Review

- [ ] Nenhum secret hardcoded (regex: `password\s*=\s*["'][^"']+["']`)
- [ ] Todas as queries ao banco usam ORM ou parameterized
- [ ] Todos os endpoints autenticados verificam ownership do recurso
- [ ] CORS configurado com origins explícitas (sem `*`)
- [ ] Senhas usam argon2/bcrypt (nunca MD5/SHA1/SHA256 simples)
- [ ] JWT tem expiração curta e validação de `sub`
- [ ] Inputs de usuário são validados com Pydantic antes de usar
- [ ] Nenhum `eval()`, `exec()`, `pickle.loads()` com dados externos
- [ ] Logs não contêm PII (email, CPF, senha, token)
- [ ] `subprocess` não usa `shell=True` com variáveis de usuário
- [ ] Rate limiting nos endpoints de autenticação
- [ ] Headers de segurança HTTP configurados

