ENGENHARIA

Arquitetura e Security by Design no RustCMS

25 AGO 2026 ◆ 6 MIN DE LEITURA ◆ 77 VISUALIZAÇÕES

Há dois modos de construir um sistema seguro. O primeiro adiciona segurança por cima: autenticação bolada na semana 3, validação de entrada corrigida após o primeiro bug report, rate limiting ativado quando o servidor cai sob carga. O segundo a embute na arquitetura desde o início, de modo que violar uma regra de segurança exige violar a estrutura do código.

O RustCMS foi construído com o segundo modo como premissa. Este artigo documenta como.

A Arquitetura em Camadas

Toda requisição ao RustCMS percorre o mesmo caminho, sem exceção:

Browser → Router → Middleware → Handler → Service → Repository → PostgreSQL

Cada camada tem uma responsabilidade única. Nenhuma salta sobre a seguinte. O diagrama abaixo mostra o fluxo e — mais importante — o que acontece quando uma requisição é barrada antes de chegar ao handler.

BROWSER / CLIENTE REQUISIÇÃO HTTP · HTTPS 01 AXUM 0.8 ROUTER ROTAS DECLARADAS EXPLICITAMENTE 02 MIDDLEWARE — EXECUTA ANTES DE QUALQUER HANDLER 03 RATE LIMIT DashMap → 429 CSRF tempo constante → 403 AUTH sessão · OIDC → /login HANDLERS ADMIN · PÚBLICO · OAUTH · API · UPLOAD 04 SERVICES LÓGICA DE NEGÓCIO · CONTROLE DE ACESSO 05 REPOSITORIES SQLX COMPILE-CHECKED · UMA STRUCT POR DOMÍNIO 06 POSTGRESQL PUBLIC · TOWER_SESSIONS · OAUTH_STATES 07
TODA REQUISIÇÃO PERCORRE ESTAS CAMADAS EM ORDEM. O MIDDLEWARE (03) REJEITA ANTES DE QUALQUER HANDLER EXECUTAR — RATE LIMIT BLOQUEIA COM 429, CSRF REJEITA COM 403, AUTH REDIRECIONA PARA LOGIN.

O ponto-chave não é a existência dessas camadas, mas a sua ordenação garantida. Um handler de conteúdo restrito nunca executa sem que a autenticação tenha sido verificada. Um formulário POST nunca é processado sem validação CSRF. Isso não é convenção — é estrutura imposta pelo compilador e pelo framework.

Segurança Estrutural

Os princípios abaixo não são checklist de hardening aplicado depois. São decisões que moldam a arquitetura desde o início.

  • 01

    Nenhum Segredo Fora de config.rs

    Toda variável de ambiente — banco de dados, chaves OAuth, segredos de sessão — passa obrigatoriamente por config.rs. Não existe std::env::var em nenhum outro módulo. Isso cria um único lugar para auditar configuração sensível e um único ponto de falha visível durante revisão de código. Adicionar uma nova variável sem passar pelo módulo de config é um erro de compilação, não de runtime.

  • 02

    Senhas com Argon2id

    O RustCMS usa Argon2id — vencedor da Password Hashing Competition e recomendação atual do OWASP — para todas as senhas locais. Parâmetros conservadores de custo de memória e iterações tornam ataques de GPU economicamente inviáveis. As funções hash_senha e verificar_senha são centralizadas em services/auth.rs; não há outro caminho para verificar uma senha no sistema.

  • 03

    CSRF em Tempo Constante

    Tokens CSRF são gerados por sessão e validados com comparação em tempo constante — sem risco de timing attack via comparação ingênua de strings. Rotas de API (/api/) e callbacks OAuth são excluídas explicitamente da verificação; todo o resto está coberto por padrão. A exclusão é uma lista branca explícita, não uma lista negra.

  • 04

    MFA/TOTP com Proteção Anti-Replay

    A autenticação de dois fatores é integrada nativamente via totp-rs. Cada código TOTP é válido para uma única autenticação — mesmo dentro da janela de 30 segundos — eliminando ataques de replay em que um código interceptado é reutilizado imediatamente. MFA pode ser opcional ou obrigatório por usuário.

  • 05

    Sessões Seguras por Padrão

    Cookies de sessão têm HttpOnly sempre ativo, Secure condicionado a PRODUCAO=true (desativado em HTTP local por padrão), e SameSite: Lax — não Strict, pois Strict quebra callbacks OAuth ao retornar do provedor externo. Sessões são persistidas no PostgreSQL, não em memória.

  • 06

    Bloqueio Antes do Efeito Colateral

    Conteúdo restrito é verificado antes de registrar page views, renderizar templates, ou retornar qualquer dado. Um usuário não autenticado que tenta acessar um artigo restrito não gera page view, não recebe dados parciais, e não revela a existência do conteúdo além de um redirect. O acesso é verificado no middleware e novamente no service — dois níveis independentes.

  • 07

    SQL Seguro por Construção

    O sqlx com queries verificadas em tempo de compilação elimina SQL injection por construção, não por convenção — não há interpolação de string em queries. As colunas busca_fts (tsvector gerada automaticamente pelo PostgreSQL) forçam a listagem explícita de colunas: usar SELECT * nessas tabelas é um erro de compilação.

  • 08

    Proteção Contra Open Redirect

    Todo parâmetro ?next= — em login local, cadastro de membro, e todos os quatro fluxos OAuth — passa pela função next_seguro(), que aceita apenas paths começando com exatamente uma barra. //evil.com, URLs absolutas e javascript: são rejeitados antes de qualquer redirect.

OAuth e o Problema do ?next=

Um detalhe que a maioria dos sistemas erra ao implementar OAuth com redirecionamento pós-login: o parâmetro ?next= não pode viajar na sessão Axum durante um fluxo OAuth. A sessão não sobrevive ao redirect externo para o provedor — quando o callback chega de volta, a sessão que existia antes do redirect não está mais disponível.

Solução Adotada

O destino pós-login é persistido na tabela oauth_states no PostgreSQL, associado ao estado OAuth criptograficamente aleatório gerado no início do fluxo. Quando o callback retorna, o estado é validado, o next é recuperado do banco, passa por next_seguro(), e o usuário é redirecionado corretamente. Nenhum dado de redirecionamento viaja no lado do cliente.

Esse mecanismo funciona igualmente para usuários CMS e membros da área restrita, e para todos os quatro provedores suportados: Google, Microsoft, GitHub e OIDC genérico.

Content Security Policy Dinâmica

A CSP é gerada dinamicamente por request, com suporte a variáveis CSP_EXTRA_* para extensões controladas pelo operador — permitindo que uma instância específica inclua scripts de terceiros sem afetar o padrão seguro de outras instâncias. A fase atual usa unsafe-inline em alguns contextos; o roadmap inclui a remoção disso com nonces por request.

Multi-site e Isolamento de Dados

O RustCMS suporta múltiplas instâncias no mesmo cluster PostgreSQL via search_path na DATABASE_URL. Cada instância opera em seu próprio schema, com isolamento completo de dados entre sites. Migrações são append-only — uma migração existente nunca é editada, apenas novas são adicionadas — preservando rastreabilidade e tornando qualquer rollback auditável.

Segurança não é uma feature — é uma consequência de decisões de arquitetura tomadas cedo e mantidas com disciplina. O RustCMS foi construído com essa premissa desde a primeira linha. Não há mágica: há camadas bem definidas, verificações antes de efeitos colaterais, e nenhum atalho que troque conveniência por risco.

O código é aberto. A arquitetura está documentada. Cada decisão tem uma razão.