Segurança de APIs Python com OWASP

Uma API Python segura não nasce de um middleware isolado. A resposta rápida é: autentique quem chama, autorize cada ação e cada objeto, valide toda entrada, exponha somente origens necessárias, limite abuso, mantenha secrets fora do código e registre eventos sem registrar credenciais. Esses controles precisam existir no desenho, no código, na infraestrutura e nos testes.

A OWASP API Security organiza riscos recorrentes, mas não é uma lista para marcar depois do deploy. Use-a durante a modelagem de ameaças: quais dados existem, quem pode lê-los, como um atacante automatizaria chamadas e qual dano ocorreria se uma credencial vazasse? A REST Security Cheat Sheet da OWASP complementa o trabalho com recomendações operacionais.

Autenticação não é autorização

Autenticação (authn) confirma a identidade. Autorização (authz) decide se essa identidade pode executar a operação sobre aquele recurso. Um token válido não autoriza automaticamente GET /invoices/42. O servidor deve verificar se a fatura pertence ao usuário ou se seu papel concede acesso. Essa checagem evita a autorização quebrada por objeto, um risco central em APIs.

Prefira protocolos estabelecidos, tokens curtos e escopos pequenos. Valide assinatura, emissor, público, expiração e algoritmo no servidor; não aceite o algoritmo indicado pelo cliente sem uma configuração fechada. Para sessões, cookies Secure, HttpOnly e SameSite reduzem exposição no navegador. A documentação de segurança do FastAPI mostra os componentes OAuth2 disponíveis, mas a política de acesso continua sendo responsabilidade da aplicação.

O exemplo mantém a regra perto do recurso:

from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from pydantic import BaseModel, ConfigDict

app = FastAPI()

class User(BaseModel):
    id: int
    is_admin: bool = False

class Invoice(BaseModel):
    model_config = ConfigDict(extra="forbid")
    id: int
    owner_id: int
    total_cents: int

def current_user() -> User:
    # Em produção, valide aqui uma credencial real e suas claims.
    return User(id=7)

def load_invoice(invoice_id: int) -> Invoice | None:
    return Invoice(id=invoice_id, owner_id=7, total_cents=5900)

@app.get("/invoices/{invoice_id}")
def get_invoice(invoice_id: int, user: Annotated[User, Depends(current_user)]):
    invoice = load_invoice(invoice_id)
    if invoice is None:
        raise HTTPException(status_code=404, detail="Invoice not found")
    if invoice.owner_id != user.id and not user.is_admin:
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden")
    return invoice

Não confie em owner_id enviado no corpo para determinar o proprietário; derive-o da identidade autenticada. Para uma introdução ao framework, consulte o tutorial de FastAPI para APIs RESTful.

Validação e respostas previsíveis

Valide tipo, tamanho, formato, faixa e semântica. Rejeitar campos extras reduz mass assignment: o cliente não pode acrescentar is_admin=true a um modelo permissivo. Limite também tamanho do corpo, quantidade de itens e profundidade de estruturas no proxy ou servidor. Serializers ajudam a definir esse contrato, como mostra o guia de Django REST Framework, mas consultas SQL ainda devem ser parametrizadas e caminhos de arquivo precisam de controles próprios.

Não devolva tracebacks, SQL, caminhos internos ou detalhes criptográficos. Respostas públicas podem ter uma mensagem estável e um request_id; o diagnóstico completo fica em um canal interno protegido. O guia de tratamento de erros em Python ajuda a evitar except Exception que silencia falhas importantes.

CORS e rate limiting têm funções específicas

CORS diz ao navegador quais origens podem ler respostas. Não substitui authn, authz ou proteção CSRF. Liste origens exatas, métodos e cabeçalhos necessários. Evite refletir qualquer Origin; credenciais com origem curinga são especialmente perigosas. Separe configurações de desenvolvimento e produção.

Rate limiting reduz força bruta, raspagem e consumo acidental, mas não corrige endpoints caros. Aplique cotas por identidade e, antes da autenticação, por IP ou chave de cliente. Login e recuperação de senha merecem limites mais estritos. Em múltiplas instâncias, use um contador compartilhado e respostas 429 com Retry-After. Combine isso com timeouts, paginação, limites de upload e orçamento de concorrência. Escolha números após observar tráfego legítimo; um valor copiado pode bloquear clientes ou não conter abuso.

Secrets, dependências e configuração

Credenciais não pertencem ao Git, arquivo .env versionado, log, traceback ou imagem de container. Em produção, injete-as por um gerenciador de secrets, dê acesso mínimo à identidade da carga e planeje rotação. Falhe ao iniciar quando uma configuração obrigatória estiver ausente. Se uma chave vazar, revogue-a; apagar o commit não elimina cópias.

Fixe e revise dependências, mantenha Python e bibliotecas suportados e execute análise no pipeline. Ambientes isolados, descritos no guia de venv em Python, evitam conflitos, mas não substituem atualização e inventário. Containers devem rodar sem root e com somente os arquivos necessários; veja as práticas de Docker com Python.

Logging útil sem dados sensíveis

Registre eventos estruturados: horário, rota normalizada, status, latência, identificador de requisição e identificador interno pseudônimo. Nunca registre corpo inteiro por padrão, Authorization, cookies, senha ou token. Mascaramento deve acontecer antes de o evento deixar o processo. Defina acesso, retenção e alertas para falhas repetidas de login, picos de 403, 429 e alterações administrativas.

Um request_id gerado pelo servidor permite correlacionar proxy, aplicação e banco sem expor dados pessoais. Não aceite cegamente um identificador externo: valide formato e tamanho ou gere outro. Para configurar níveis e handlers sem duplicação, consulte logging em Python.

Erros frequentes

  • Verificar o token e esquecer a permissão sobre cada objeto.
  • Usar CORS * como suposta proteção da API.
  • Aceitar campos desconhecidos ou confiar em preço, papel e proprietário enviados pelo cliente.
  • Aplicar um único limite global, fácil de contornar e ruim para clientes legítimos.
  • Registrar headers e corpos completos para facilitar depuração.
  • Manter chaves sem rotação ou compartilhar uma credencial entre serviços.
  • Retornar erros diferentes que revelam se um e-mail ou recurso existe.

Checklist antes de publicar

  • Modele recursos, papéis, escopos e casos de negação; teste acesso horizontal e vertical.
  • Valide assinatura e claims, expire credenciais e tenha revogação ou rotação.
  • Rejeite campos extras, limite payloads e parametrize consultas.
  • Configure CORS com origens exatas e trate CSRF quando cookies autenticam.
  • Aplique limites distribuídos, timeouts, paginação e limites de upload.
  • Injete secrets fora do artefato e restrinja acesso por serviço.
  • Remova dados sensíveis dos logs e proteja sua retenção.
  • Automatize testes, incluindo 401, 403, 404, 422 e 429, com pytest.

Segurança é uma propriedade contínua. Revise ameaças quando surgirem endpoints, integrações ou novos tipos de dado, acompanhe alertas e transforme incidentes e testes em controles repetíveis.