Design Patterns de Segurança

Design patterns são soluções de projeto recorrentes para problemas comuns em engenharia de software. Eles não são frameworks nem trechos de código prontos: são abstrações que guiam decisões de arquitetura e implementação. Ao aplicá-los com intenções claras de segurança, reduzimos ataques decorrentes de más escolhas de design (ex.: exposição desnecessária de superfícies, estados compartilhados inseguros, acoplamento que vaza segredos).

1. O que são Design Patterns

Popularizados pelo Gang of Four (GoF), os padrões se agrupam em três famílias:

  • Criação (Factory, Builder, Prototype, Singleton): controlam como objetos são instanciados.
  • Estrutural (Adapter, Facade, Proxy, Decorator, Composite, Flyweight, Bridge): organizam composição e acoplamento.
  • Comportamental (Strategy, Command, Chain of Responsibility, Observer, Template Method, State, Iterator, Mediator, Memento, Visitor): definem como objetos colaboram e distribuem responsabilidades.

O valor para segurança surge quando escolhemos padrões que limitam superfície de ataque, impõem invariantes e evitam estados perigosos.

2. Padrões GoF com ângulo de segurança — exemplos práticos

  • Factory: centraliza a criação de objetos sensíveis (p.ex., clientes de criptografia), impedindo instâncias com parâmetros fracos. Útil para enforce de políticas (algoritmos, tamanhos de chave, sources de entropia).
  • Builder: constrói objetos complexos apenas quando todos os campos obrigatórios seguros foram preenchidos; evita “defaults perigosos” (ex.: cipher suites inseguras).
  • Singleton: use com parcimônia. Um singleton com estado mutável pode virar hotspot de condição de corrida e vazamento de segredos. Se necessário, preferir Singleton imutável e injeção de dependência.
  • Adapter: normaliza entradas externas antes de alcançar o domínio; local ideal para validações e canonicalização (prevenção de injeções e path traversal).
  • Facade: reduz superfície de API pública expondo apenas operações seguras; oculta detalhes internos que poderiam ser explorados.
  • Proxy: intercepta chamadas para aplicar autorização, rate limiting e políticas de acesso por contexto sem poluir o domínio.
  • Decorator: acrescenta auditoria, métricas e sanitização de dados sensíveis sem alterar a classe base.
  • Composite: atenção a propagação de privilégios em hierarquias; defina regras de herança de acessos de forma explícita para não “elevar” permissões por engano.
  • Bridge: desacopla abstrações de implementações; permite trocar provedores (ex.: KMS) sem reescrever regras de segurança no domínio.
  • Strategy: alterna algoritmos de forma segura (p.ex., políticas de hashing); facilita a rotação de algoritmos sem tocar no fluxo de negócio.
  • Command: encapsula ações auditáveis com idempotência e autorização por tipo de comando; ótimo para trilhas de auditoria e replay safety.
  • Chain of Responsibility: pipeline de validações e filtros (ex.: autenticação → autorização → validação de entrada → verificação de cota). Cada elo falha de forma segura (fail-closed).
  • Observer: cuidado com event injection e assinaturas não confiáveis; valide a origem e limite efeitos colaterais.
  • Template Method: define passos obrigatórios de um fluxo com ganchos de segurança (ex.: “verificar permissão” antes de “executar operação”).
  • State: modela transições de sessão/conta (ex.: locked, verified, pending MFA), evitando ações proibidas em estados inseguro/indefinido.
  • Memento: ao versionar estado, não grave segredos em claro; use criptografia e redaction nos snapshots.
  • Visitor: útil para aplicar verificações uniformes em estruturas complexas (ex.: varrer uma AST para bloquear chamadas perigosas).

3. Padrões arquiteturais com implicações de segurança

  • Layered Architecture (camadas): proíba “saltos” de camada (UI → data); use interfaces e contracts para garantir que validações ocorram na borda correta.
  • Hexagonal / Ports & Adapters: coloca entradas/saídas em “portas” explícitas; ótimo ponto único para autenticação, mapeamento e sanitização.
  • Microservices: favorece princípio do menor privilégio por serviço; combine com comunicação autenticada (mTLS/assinaturas) e segregação de dados por contexto.
  • Event-Driven: defina contratos de evento imutáveis, assine mensagens e valide publishers; evite dados sensíveis em payloads.
  • CQRS: separa leitura de escrita; autorizações mais simples e modelos de leitura redigidos para não vazar campos sensíveis.

4. Concorrência e imutabilidade (padrões práticos)

  • Immutable Object: objetos de configuração/segredos devem ser imutáveis após criação; evita TOCTOU e condições de corrida.
  • Thread Pool com filas: controla consumo de recursos e previne DoS por exaustão interna.
  • Copy-on-write para estruturas compartilhadas: reduz risco de corrupção de estado sensível.

5. Anti-patterns que criam vulnerabilidades

  • God Object: concentração de responsabilidades e segredos; torna auditoria e controles finos inviáveis.
  • Service Locator opaco: mascara dependências, dificulta testes de segurança e policy enforcement.
  • Estado global mutável: segredos e flags em static; origem de corridas e vazamentos.
  • Serialização insegura: usar formatos que executam gadgets (ex.: object deserialization) — prefira formatos de dados puros (JSON/CBOR) e whitelists de tipos.
  • Fail-open: tratamento de erro que libera acesso “para não quebrar”; sempre fail-closed.
  • Roll-your-own crypto: abstraia via Factory/Strategy e delegue a bibliotecas maduras.

6. Mini-blueprints (como combinar padrões de forma segura)

  • API segura por camadas: Adapter (normaliza entrada) → Chain of Responsibility (auth → authz → validações) → domínio → Decorator (auditoria) → Facade (exposição mínima).
  • Gestão de segredos: Factory escolhe provedores; Builder exige parâmetros seguros; objetos de segredo imutáveis; Proxy controla acesso por contexto.
  • Fluxo de ações críticas: cada ação como Command com idempotency key, autorização por tipo, trilha de auditoria e Template Method impondo checagens obrigatórias.
  • Uploads de arquivo: Facade expõe operação única; Adapter valida mimetype; Sandboxing para processamento; Decorator remove metadados e aplica scan.

7. Boas práticas para adotar padrões com foco em segurança

  • Decida o padrão pelo risco: escolha o que melhor protege invariantes de negócio (integridade, confidencialidade, disponibilidade).
  • Documente invariantes: ao introduzir um padrão, registre quais propriedades de segurança ele garante e quais trade-offs cria.
  • Teste os contratos: escreva testes que quebrem se o padrão for violado (ex.: pular etapa obrigatória do Template Method).
  • Evite sobre-engenharia: padrão mal aplicado aumenta complexidade e cria novas brechas.
  • Prefira imutabilidade e limites explícitos de acoplamento; são aliados naturais da segurança.

Conclusão

Design patterns existem para resolver problemas de projeto. Quando usados com critério, eles reforçam limites, tornam invariantes auditáveis e reduzem superfícies de ataque. A chave não é “usar muitos padrões”, mas escolher os adequados, aplicá-los de forma consistente e testá-los contra os riscos que o seu sistema realmente enfrenta.