Uma API de tickets de suporte aceita {"title": 7, "description": "<texto muito longo>", "email": "alice@example.com"} sem verificar a estrutura da requisição. Uma etapa posterior de formatação falha ao processar o título numérico, enquanto um manipulador de erros registra a descrição e o e-mail enviados. O atacante não precisou de conta nem de uma cadeia de exploração (exploit chain), apenas de uma requisição que atravessasse a fronteira da API com dados que o serviço tratava como confiáveis. A questão é onde o serviço deve rejeitar entradas malformadas, excessivamente grandes ou inesperadas antes que elas cheguem à lógica de negócios ou aos logs.
Utilizaremos um único serviço FastAPI ao longo do curso. POST /tickets aceita um novo ticket, GET /tickets/{id} lê um ticket, POST /login autentica um usuário e GET /me retorna o usuário autenticado. Cada endpoint recebe dados controlados por um cliente — mesmo quando esse cliente é um navegador confiável atualmente —, portanto, cada um precisa de uma fronteira explícita entre a entrada recebida e o estado interno.
A validação de entrada é a regra de fronteira que aceita apenas dados compatíveis com o contrato do serviço. Para POST /tickets, uma requisição válida possui um título em texto curto, uma descrição com limite de tamanho e um e-mail no formato exigido. JSON malformado causa falha ainda mais cedo, pois o framework não consegue construir o corpo da requisição a partir de uma sintaxe inválida. O resultado útil não é apenas a redução de exceções. Entradas rejeitadas nunca chegam ao armazenamento de tickets, à renderização da resposta ou aos logs de diagnóstico que poderiam expor dados pessoais.
O serviço possui três fronteiras que vale a pena manter visíveis durante o desenvolvimento: o processamento (parsing) da requisição, a autenticação em POST /login e a renderização da resposta. A primeira fronteira é o nosso ponto de partida, pois toda requisição não autenticada chega até ela. Vamos rastrear os campos controlados através do serviço:
Este diagrama mostra que title, description e email chegam de fora do serviço, enquanto o repositório em memória recebe apenas dados que passaram pela fronteira da requisição.
Como o diagrama separa os dados do cliente dos dados do repositório, o endpoint POST /tickets precisa de regras nas quais o manipulador (handler) possa confiar, em vez de suposições espalhadas pelo código subsequente. O contrato especifica os campos permitidos, seus tipos e seus limites. Um título pode permitir de 1 a 120 caracteres, enquanto uma descrição pode permitir até 4.000. Limites são controles de segurança, pois restringem o armazenamento, o processamento, o volume de logs e o tamanho da resposta antes que um atacante possa ampliá-los.
A normalização deve ocorrer na mesma fronteira quando o serviço exige uma forma canônica. Podemos remover espaços em branco nas extremidades de um título antes de armazená-lo, mas não devemos corrigir silenciosamente um e-mail inválido nem truncar uma descrição excessivamente longa. Corrigir altera a intenção do cliente; rejeitar torna o contrato observável. Validadores de campo do Pydantic podem transformar valores aceitos, mas devem retornar o resultado aceito, e regras que exigem a entrada bruta (raw input) devem lidar com entradas de qualquer tipo.
O que POST /tickets deve retornar quando o email for not-an-email? O manipulador não deve criar o ticket 42, e o cliente precisa de um formato de erro estável, em vez de um traceback interno. O modelo de requisição a seguir aplica limites aos campos e registra o campo rejeitado sem registrar o próprio e-mail rejeitado: