Arquitetura e Security by Design no RustCMS
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:
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.
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.rsToda variável de ambiente — banco de dados, chaves OAuth, segredos de sessão — passa obrigatoriamente por
config.rs. Não existestd::env::varem 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_senhaeverificar_senhasão centralizadas emservices/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
HttpOnlysempre ativo,Securecondicionado aPRODUCAO=true(desativado em HTTP local por padrão), eSameSite: Lax— nãoStrict, 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
sqlxcom 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 colunasbusca_fts(tsvector gerada automaticamente pelo PostgreSQL) forçam a listagem explícita de colunas: usarSELECT *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çãonext_seguro(), que aceita apenas paths começando com exatamente uma barra.//evil.com, URLs absolutas ejavascript: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.
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.