[BR|EN]~/samuel-felix

cat cases/guardiao-impacta.md

Guardião Impacta

App de conscientização em segurança da informação, feito como TCC e usado por participantes reais numa pesquisa de campo.

Resumo

O Guardião Impacta foi um app de celular (PWA) que ensinava segurança da informação a quem não trabalha com TI, com aulas curtas, trivia, pontos, conquistas e ranking. Foi o TCC do meu grupo em Ciência da Computação e ficou em produção durante a coleta de dados da pesquisa, encerrada em 02/11/2026. Depois disso, o app foi desligado.

Testes automatizados 475 na API (100% das linhas) e 385 na interface (99,8%)
Teste de carga 100 usuários simultâneos por 3 minutos: nenhuma falha, 95% das respostas em até 20 ms; no estresse com 1.000, até 41 ms
Segurança 6 problemas achados na revisão de código e corrigidos; OWASP ZAP sem nenhuma falha depois
Entrega cada push no main com a CI verde ia sozinho para produção, sem porta SSH aberta

Contexto

Programas de conscientização em segurança costumam ter baixo engajamento, principalmente entre profissionais de áreas não técnicas. A pesquisa avaliou se um app gamificado engaja mais do que os treinamentos tradicionais que os participantes já tinham feito.

O TCC foi feito por um grupo de cinco pessoas. Minha parte foi o desenvolvimento do app inteiro (interface, API e banco), a infraestrutura, o CI/CD e a escrita da monografia. Dos 159 commits do repositório, 156 são meus.

Arquitetura

Diagrama: participantes e administradores acessam por HTTPS o Caddy, numa VM Azure com Docker Compose; o Caddy encaminha /api para a API Node.js, que usa o PostgreSQL e envia e-mails por SMTP. Desenvolvedores fazem push no GitHub, e o GitHub Actions testa e publica na VM.
Arquitetura em produção (figura da monografia).

Decisões de engenharia

Pontuação garantida no banco, não no celular. Os pontos definiam o ranking e alimentavam os dados da pesquisa. Calculados no aparelho, poderiam ser manipulados. Por isso a regra ficou no servidor: transações com FOR UPDATE impediam que duas requisições da mesma rodada corressem em paralelo, e índices únicos parciais faziam cada questão pontuar uma única vez por pessoa, mesmo com cliques repetidos ou requisições simultâneas. Os testes de integração cobriam essas corridas.

Sessão conferida no banco a cada requisição. O token JWT só dizia quem era a pessoa; o middleware buscava o usuário no banco toda vez. Desativar uma conta ou mudar o papel dela valia na hora, e trocar a senha invalidava os tokens antigos.

Schema versionado. O banco era definido por migrations SQL numeradas, aplicadas em ordem quando a API subia. Migration já aplicada nunca era editada; toda mudança virava um arquivo novo.

Detalhes no README do projeto.

Como adicionar conteúdo

Escrever uma aula não exigia banco nem programação: cada aula era uma pasta com um arquivo HTML.

conteudo/aulas/
  16-assunto-novo/
    aula.html     título, texto e perguntas
    imagens/      imagens usadas na aula
  1. Copiar a pasta conteudo/modelo/ para conteudo/aulas/16-assunto-novo/. O número define a ordem na trilha.
  2. Editar o aula.html. Cada pergunta tem 4 alternativas, uma marcada como correta, e uma explicação que aparece depois da resposta:
<div data-pergunta="pedido-de-senha" data-pontos="10">
  <p data-enunciado>Alguém liga dizendo ser do suporte e pede sua senha. O que fazer?</p>
  <ol>
    <li>Passar a senha, já que é do suporte</li>
    <li>Passar só metade da senha</li>
    <li data-correta>Recusar, desligar e reportar à segurança</li>
    <li>Pedir que mande um e-mail antes</li>
  </ol>
  <p data-explicacao>Ninguém legítimo pede sua senha. Recuse e reporte.</p>
</div>
  1. Abrir o arquivo no navegador para ver a prévia com o visual do app.
  2. Rodar o validador. Se algo estiver errado, ele aponta o arquivo, a pergunta e o problema:
$ docker compose exec api npm run importar-aulas -- --validar
04-engenharia-social/aula.html: pergunta "pedido-de-senha": precisa de exatamente 1 alternativa com data-correta, tem 2
  1. Fazer o push. A CI rodava o mesmo validador e, com tudo verde, o deploy publicava. A API importava a aula ao subir, sem perder as respostas e os pontos já dados.

Perguntas de trivia eram cadastradas pela área de administração do app, sem código: enunciado, alternativas, explicação, nível (fácil, médio ou difícil) e a aula de referência, que virava o link “Rever na aula” quando a pessoa errava.

Com isso, o conteúdo virou arquivo versionado e testado como o código, e quem escrevia aula não precisava mexer no banco. O guia completo está em conteudo/README.md.

Entrega contínua

Cada push rodava esta sequência no GitHub Actions:

lint e formatação         eslint + prettier
caddy validate            um erro no Caddyfile derrubaria o site
aulas                     o validador da seção anterior
testes da API             contra um Postgres real, cobertura mínima de 80%
testes e build do front   cobertura mínima de 80%
deploy (só no main)       login no Azure por OIDC → Run Command na VM → deploy.sh

Agentes de IA sob controle

Usei o Claude Code como agente de desenvolvimento. Um agente com acesso ao terminal e à nuvem precisa de limites claros, e eles ficaram versionados no repositório:

A monografia declara o uso de IA generativa no desenvolvimento e na revisão do texto (seção 1.6).

Qualidade e desempenho

Segurança

Em 01/10/2026, no meio do desenvolvimento, fiz uma rodada de segurança com o Claude Code: revisão do código da API e da interface, seguida de quatro scanners contra a stack de produção rodando localmente.

A revisão de código achou 6 problemas, todos corrigidos com testes no commit 1b8948c:

Gravidade Problema Correção
Média O HTML de cada aula, com o gabarito, era público e sem login A rota passou a servir só imagens
Baixa-média Um evento de telemetria começando com - virava fórmula no CSV exportado para o Excel Escape de tudo que começa com - e não é número
Baixa Sem SMTP em produção, o link de troca de senha ia para o log Em produção, o e-mail é descartado sem registrar o link
Baixa Telemetria e trivia aceitavam 300 requisições por minuto Limite de 60 por minuto por usuário
Baixa O navegador guardava em disco as respostas da API Cache-Control: no-store em /api
Baixa O token de troca de senha ficava no histórico do navegador O token sai da URL assim que a página abre

Depois, os scanners:

Ferramenta Tipo Resultado
OWASP ZAP, varredura passiva DAST 0 falhas, 5 avisos, 62 regras sem alerta
OWASP ZAP, varredura ativa DAST 0 falhas, 2 avisos (cabeçalhos), 139 regras sem alerta
Semgrep (601 regras) SAST 0 problemas no código; 4 alertas de configuração (Dockerfiles de desenvolvimento e actions sem SHA)
Trivy dependências e imagens 0 vulnerabilidades nas dependências do app; 19 CVEs na imagem da API, todas no npm embutido na imagem do Node
Gitleaks segredos 1 alerta, falso positivo (JWT_SECRET= vazio no .env.example)

O npm saiu da imagem de produção da API (0dcca2d), e o Trivy passou a achar 0 CVEs nela. As CVEs da imagem do Caddy estavam no binário dele; o deploy passou a reconstruir as imagens com --pull a cada versão. A rodada também achou um bug de disponibilidade: com o banco fora do ar, a API caía em vez de responder 503. Isso foi corrigido em cb715eb.

Limites: o ZAP testou só as páginas públicas, porque não faz login sozinho. Os relatórios em HTML e JSON se perderam com a pasta temporária, e os números acima vêm do registro da sessão. Os relatórios não estão publicados; as correções estão nos commits linkados.

Pesquisa e privacidade

Limites e o que eu faria diferente

Telas