Pular para o conteúdo
DM Danilo Moura Desenvolvimento · Segurança da informação · LGPD
Araxá/MG · atende em 260 km Falar comigo

Início / Engenharia

ADR decisões de arquitetura

Por que este site
foi feito assim

Este site é a única amostra de código que eu posso mostrar sem pedir autorização a um cliente. Então ele está documentado como eu documento projeto de verdade: cada decisão com o contexto que a motivou e o que se perdeu com ela. Decisão sem custo declarado é propaganda, não engenharia.

473Verificações automatizadas
0Dependências de terceiros
0Requisições para fora do domínio
15Decisões registradas abaixo

STACK o que roda aqui

Em uma linha

PHP 8 com includes, MySQL via PDO com consultas preparadas, CSS e JavaScript escritos à mão, Apache com .htaccess por pasta. Fontes e scripts servidos pelo próprio domínio. Publicação por cópia de arquivos, sem passo de build.

CamadaEscolhaAlternativa descartada
Back-endPHP 8 sem frameworkLaravel — exige Composer e processo de build
BancoMySQL com PDO preparadoORM — abstração desnecessária para 5 tabelas
Front-endCSS e JS sem bibliotecaReact — 200 KB para um site majoritariamente estático
3DMotor próprio em canvas 2DThree.js — script externo quebraria a CSP
Tipografiawoff2 no próprio domínioGoogle Fonts — expõe o IP do visitante
TestesRunner próprio, 473 verificaçõesPHPUnit — exige Composer, ausente na produção
PublicaçãoCópia de arquivos por FTPPipeline com container — sem shell na hospedagem

ADR uma a uma

Decisões

Formato de registro de decisão de arquitetura: contexto, decisão, consequência e custo. A quarta parte é a que costuma faltar.

ADR-01 · Arquitetura

PHP e MySQL, sem framework

Contexto

O site precisa rodar em hospedagem compartilhada com cPanel, sem acesso a shell, sem Composer e sem processo em segundo plano. O mesmo vale para os sistemas que eu entrego aos clientes.

Decisão

PHP puro com includes, MySQL via PDO com consultas preparadas. Nenhuma dependência de terceiros no servidor.

Consequência

Publicação é copiar arquivos. Não existe passo de build, cache de container nem versão de framework para acompanhar. Qualquer desenvolvedor PHP assume o projeto sem estudar uma abstração específica.

O que se perdeu

Perco roteador, ORM e migrações prontas. Rota nova exige uma pasta e uma regra de reescrita; mudança de schema é SQL escrito à mão. Num projeto de vinte tabelas isso pesaria — em um site institucional, não.

ADR-02 · Front-end

Motor 3D próprio em canvas 2D

Contexto

O site precisa de elementos tridimensionais que reajam ao mouse. O caminho óbvio seria Three.js por CDN.

Decisão

Escrevi um motor de ~200 linhas: rotação por matriz, projeção em perspectiva e desenho ordenado por profundidade em canvas 2D.

Consequência

Nenhum script de terceiro, o que permite a CSP ser script-src 'self' sem exceção. Sem os ~600 KB da biblioteca. Quatro cenas com a mesma base: treliça, malha, matriz de risco e anel de dados.

O que se perdeu

Sem iluminação, textura ou modelo importado. Se o site algum dia precisar de um modelo 3D de verdade, o motor não serve e a decisão se inverte — o certo aí é assumir a dependência e afrouxar a CSP conscientemente.

ADR-03 · Privacidade

Nenhum recurso carregado de terceiro

Contexto

O site começou usando Google Fonts. Isso não cria cookie, mas entrega o IP de todo visitante a um terceiro no instante em que a página abre — e é tratamento de dado pessoal.

Decisão

Fontes hospedadas no próprio domínio (subconjunto latino, woff2, 196 KB). Sem CDN, sem mapa incorporado, sem pixel, sem ferramenta de análise externa.

Consequência

Zero requisição para fora. Num site que vende adequação à LGPD, declarar a exposição seria o mínimo; eliminá-la é o argumento. Também permitiu fechar a CSP por completo.

O que se perdeu

As fontes viram arquivos que eu preciso atualizar e cujas licenças preciso distribuir. Perco o cache compartilhado entre sites do CDN — que, na prática, os navegadores modernos já particionam por origem, então a perda é menor do que parece.

ADR-04 · Segurança

CSP sem exceção, e o HTML que ela exige

Contexto

A política começou com style-src 'self' 'unsafe-inline', porque as páginas usavam atributos style= para ajustes pontuais. Uma auditoria externa sinalizou.

Decisão

Converti cerca de 180 atributos style= em classes utilitárias e removi o unsafe-inline. Depois tirei o innerHTML do JavaScript e adicionei require-trusted-types-for 'script'.

Consequência

Estilo e script injetados são recusados pelo navegador mesmo que alguém consiga inserir HTML na página. Não há domínio externo liberado em nenhuma diretiva.

O que se perdeu

Ajuste visual agora exige criar classe. Pior: quem escrever style= por hábito vê o estilo não aplicar e pensa que é bug de CSS — a tentação é afrouxar a política para "resolver". Um teste automatizado falha nesse caso, justamente para transformar a armadilha em erro visível.

ADR-05 · Segurança

Ferramenta pública que busca URL de terceiro

Contexto

O diagnóstico gratuito faz o servidor buscar um endereço digitado por um desconhecido. É a definição de SSRF: alguém pode pedir que o servidor acesse 127.0.0.1, a rede interna da hospedagem ou o endpoint de metadados da nuvem.

Decisão

Todo endereço passa por validação antes de cada requisição e de novo a cada redirecionamento: só http e https, só portas 80 e 443, sem credenciais na URL, e o nome é resolvido com todos os IPs conferidos contra faixas privadas, reservadas, loopback e link-local. Redirecionamento é seguido manualmente, no máximo quatro saltos.

Consequência

A validação por salto é o detalhe que importa: o primeiro endereço pode ser público e o destino do redirecionamento não. Trocar isso por CURLOPT_FOLLOWLOCATION reabriria o buraco.

O que se perdeu

Mais código e mais chamadas de rede que a versão ingênua. A suíte cobre cada caso de ataque, porque essa é exatamente a função que alguém "simplifica" no futuro.

ADR-06 · Ética

Verificação passiva, nunca invasiva

Contexto

Seria fácil fazer a ferramenta procurar /.env, /.git/config ou /backup.sql no site do visitante. O relatório ficaria mais impressionante.

Decisão

O diagnóstico só analisa a resposta pública do servidor — o mesmo que o navegador de qualquer visitante já recebe. Nenhuma varredura de caminho, nenhum teste de credencial, nenhuma carga enviada a formulário. E há uma declaração de responsabilidade antes de consultar.

Consequência

A ferramenta é coerente com o que o site promete em /metodo/: não faço teste em sistema de terceiro sem autorização formal. Coerência aqui vale mais que um relatório mais completo.

O que se perdeu

O relatório não cobre permissões de arquivo, backup, contas ativas nem listagem de diretório — que é justamente o que exige autorização e credencial. Isso fica para o diagnóstico contratado, e a página diz isso com todas as letras.

ADR-07 · Confiabilidade

Falha de notificação não pode custar um lead

Contexto

A notificação de lead novo depende de uma API externa (Meta ou Telegram). API externa cai, fica lenta e tem token que vence.

Decisão

O lead é gravado no banco e o e-mail sai antes da notificação. Onde o servidor permite, a resposta é entregue ao navegador antes do envio. Timeout de 3 s para conectar e 5 s no total; falha é registrada em log e ignorada.

Consequência

Testei com token inválido: o visitante recebeu confirmação instantânea, o lead entrou no funil e a falha ficou registrada com o erro exato. O aviso é conveniência minha, não pode ser ponto único de falha do formulário.

O que se perdeu

Posso perder a notificação sem perceber na hora. Por isso o painel mostra as últimas tentativas e tem botão de teste.

ADR-08 · LGPD

Consentimento com prova, e retenção que se cumpre

Contexto

O ônus de provar que o consentimento foi obtido é do controlador. E política de privacidade que promete apagar dados em 24 meses só vale se existir como apagar.

Decisão

Cada escolha sobre cookies grava data, versão da política, finalidades, IP e navegador. A política tem número de versão: ao mudar a lista de cookies, o número sobe e o banner reaparece. O painel tem uma tela que lista o que passou do prazo de retenção e elimina em lote.

Consequência

A promessa escrita na política é verificável na prática, não uma frase decorativa.

O que se perdeu

O registro do consentimento é, ele próprio, um tratamento de dado pessoal — precisa estar declarado, e está. Leads marcados como fechados ficam fora do expurgo por terem base contratual; se o contrato terminar, alguém precisa lembrar de recolocá-los na fila. É uma decisão que depende de gente.

ADR-09 · Qualidade

Suíte de testes sem gerenciador de pacotes

Contexto

PHPUnit é o padrão. Mas o site roda em hospedagem compartilhada sem Composer, e publicação é cópia de arquivos.

Decisão

Runner próprio de ~120 linhas, rodando com o mesmo PHP que serve o site. 473 verificações em seis grupos, um deles de auditoria de segurança, incluindo um teste de integração que sobe o servidor embutido e percorre todas as páginas.

Consequência

A suíte roda em qualquer lugar, inclusive no servidor. Uma suíte que exige composer install é uma suíte que ninguém executa antes de publicar por FTP — e que testa um ambiente diferente da produção.

O que se perdeu

Sem mocks, sem cobertura de código, sem o ecossistema de plugins. Se o projeto crescer para o tamanho em que isso importa, migrar para PHPUnit é o caminho — e aí a decisão se inverte.

ADR-10 · Qualidade

Testes que protegem decisões, não só comportamento

Contexto

Decisão de arquitetura se perde. Daqui a seis meses, ninguém lembra por que não pode usar innerHTML aqui, e o comentário no código não impede o commit.

Decisão

Um grupo de testes verifica regras estruturais: CSP sem unsafe-inline, nenhum style= no HTML, nenhum sink de DOM XSS, nenhum domínio externo, nenhum segredo versionado, caminho interno fora do robots.txt, versão do consentimento sincronizada entre PHP e JavaScript.

Consequência

Cada uma dessas verificações corresponde a algo que já deu errado neste projeto ou que uma auditoria externa apontou. O teste é a memória do porquê, e falha antes do deploy em vez de depois.

O que se perdeu

São testes acoplados à estrutura do projeto: refatoração legítima faz alguns falharem, e aí é preciso decidir se a decisão mudou ou se foi descuido. O comentário em cada teste existe para essa conversa.

ADR-11 · Confiabilidade

Degradação graciosa quando falta configuração

Contexto

Credenciais moram em um arquivo fora do controle de versão. Deploy incompleto acontece — principalmente por FTP, que costuma pular arquivos começados com ponto.

Decisão

Se segredos.php não existir, o site não quebra: constantes recebem valores neutros, as páginas públicas abrem, o formulário cai para envio por e-mail e o painel avisa que o banco está indisponível.

Consequência

Um erro de publicação vira degradação parcial, não site fora do ar. A suíte roda inteira sem o arquivo, que é como um clone limpo se comporta.

O que se perdeu

Falha silenciosa é um risco em si: dá para o site rodar semanas sem banco sem ninguém notar. Por isso o painel mostra o estado real da configuração em vez de assumir que está tudo certo.

ADR-12 · Front-end

Versão dos estáticos derivada do conteúdo

Contexto

CSS e JavaScript eram servidos com ?v=1 escrito à mão, e com cache de um ano no servidor. Bastava esquecer de incrementar uma vez para o visitante ficar com o arquivo antigo — site quebrado, nenhum erro no log, impossível de reproduzir na máquina de quem publicou.

Decisão

Um helper calcula a versão a partir do hash do próprio arquivo. A URL muda sozinha quando o conteúdo muda, e só quando ele muda.

Consequência

Cache longo e agressivo deixa de ter risco. Some uma classe inteira de bug que só aparece em produção, no navegador de outra pessoa.

O que se perdeu

Um md5_file por arquivo estático por requisição — desprezível para três arquivos, e o resultado fica em cache dentro da requisição. Em um site com dezenas de estáticos, o certo seria calcular na publicação e gravar num manifesto.

ADR-13 · Operação

Publicação automatizada, com verificação do que ficou no ar

Contexto

A publicação era FTP manual. É o elo mais fraco: dá para esquecer arquivo, subir versão errada e — o pior — pular arquivo oculto, porque cliente de FTP costuma ignorar nomes começados com ponto. Provavelmente foi assim que o .user.ini não subiu, e o resultado apareceu meses depois como cookie de sessão sem HttpOnly numa auditoria externa.

Decisão

Publicação disparada por push, com a suíte de testes como portão obrigatório. Segredos, testes e logs são excluídos do envio; arquivos ocultos são incluídos. Depois de publicar, um verificador consulta o site no ar: cabeçalhos aplicados, cookie com HttpOnly, áreas internas fechadas, nenhum recurso de terceiro.

Consequência

A diferença entre "o código está certo" e "o site está certo" deixa de depender de alguém lembrar de conferir. O erro que uma auditoria levou meses para achar agora aparece em segundos, no próprio deploy.

O que se perdeu

Passa a existir um caminho automatizado que escreve em produção — e credenciais de FTP guardadas no GitHub. Mitigação: conta de FTP restrita à pasta do site, nunca a conta principal do cPanel, e modo de simulação para testar o fluxo sem enviar nada.

ADR-14 · Acessibilidade

Contraste medido, não estimado

Contexto

A paleta foi escolhida por leitura visual. Ao calcular os contrastes de verdade, dois tokens reprovavam no nível AA: o cinza de rótulos pequenos dava 3,49:1 sobre superfície (o mínimo para texto é 4,5:1) e a borda de campo dava 2,0:1 (o mínimo para componente de interface é 3:1).

Decisão

Corrigi os dois tokens e escrevi testes que calculam a razão de contraste lendo as cores direto do CSS. Se alguém escurecer um cinza por gosto, a suíte falha com o número na tela.

Consequência

Contraste deixa de ser opinião. Como o teste lê o CSS, ele não envelhece junto com a paleta.

O que se perdeu

Os cinzas ficaram um pouco mais claros do que eu tinha desenhado — perda estética real, e menor que o problema de alguém não conseguir ler um rótulo na tela do celular sob sol de canteiro. Além disso, teste automatizado cobre perto de um terço da WCAG: conformidade de verdade ainda depende de teste com pessoa.

ADR-15 · Privacidade

Medir páginas, não pessoas

Contexto

Eu não sabia quais páginas traziam contato. A resposta padrão seria Google Analytics — que quebraria o zero terceiros da ADR-03 e entregaria a navegação dos visitantes a uma empresa de publicidade.

Decisão

Medição própria, neste servidor, gravando apenas caminho da página, domínio de origem e faixa de largura de tela. Sem IP, sem cookie de medição, sem identificador de visitante, sem impressão digital. A atribuição de lead usa a página de entrada guardada em sessionStorage, que morre ao fechar a aba, e só com autorização.

Consequência

Dá para responder "quais páginas trazem cliente" sem tratar dado pessoal de navegação. A CSP continua fechada e nenhum terceiro entra.

O que se perdeu

Perco visitante único, taxa de retorno, tempo na página e funil por sessão — porque todas essas métricas exigem identificar quem está do outro lado. Os números também são um piso, não o total: quem recusa a medição não é contado. Foi troca consciente, e o painel diz isso na tela em vez de fingir precisão.

TESTES o que a suíte protege

473 verificações, zero dependências

Rodam com php tests/executar.php e na integração contínua, em PHP 8.1, 8.2 e 8.3. O grupo mais útil não testa comportamento: testa que uma decisão de arquitetura não foi desfeita sem querer.

  • Cada endereço interno que a defesa contra SSRF precisa recusar
  • Opt-in real: cookie corrompido ou de versão antiga não vira autorização
  • Escape de saída contra XSS e validação de token CSRF
  • CSP fechada, nenhum style=, nenhum sink de DOM XSS
  • Nenhum segredo em arquivo versionado
  • Integração: sobe o servidor e percorre todas as páginas

Escrever a suíte encontrou dois problemas reais: um bootstrap de sessão que falhava em silêncio, e um teste que passava por engano porque o cliente HTTP seguia redirecionamento e observava a página errada.

HONESTO ainda em aberto

O que eu faria diferente

Um site que só lista acertos não diz nada sobre quem o escreveu.

P.01

DNSSEC não configurado

Apontado por auditoria externa. Não é código: depende do registrador e do provedor de DNS. Está na fila, e o mesmo vale para o registro CAA.

P.02

Acessibilidade verificada em parte

Contraste medido e corrigido, alvos de toque de 44 px, rótulos associados, scope nas tabelas e 37 verificações automatizadas na suíte. Mas teste automatizado alcança perto de um terço dos critérios da WCAG: ordem de leitura, navegação por teclado e uso com leitor de tela exigem uma pessoa usando. Enquanto isso não acontecer, eu digo verificado em parte, não conforme.

P.03

Testes acoplados à estrutura

Os testes de guarda verificam a forma do projeto, não só o comportamento. É o que os torna úteis e, ao mesmo tempo, frágeis: uma refatoração legítima faz alguns falharem, e aí é preciso decidir caso a caso.

VER em funcionamento

Tudo isto está rodando agora

A ferramenta de diagnóstico exercita a defesa contra SSRF em tempo real. O banner de cookies desta página é o consentimento com registro de prova. O 3D é o motor descrito na ADR-02. Os cabeçalhos desta resposta são a CSP da ADR-04 — abra as ferramentas do navegador e confira.

Conversar sobre um projeto GitHub

CTA próximo passo

Me conte o que está travando hoje

Uma conversa de vinte minutos costuma ser suficiente para separar o que é problema de sistema, o que é problema de processo e o que é risco de conformidade. Sem custo e sem compromisso.