Voto Livre: denúncia anônima e LGPD em 3 dias
Desenvolvimento de ProdutoDestaque

Voto Livre: denúncia anônima e LGPD em 3 dias

Resumo para IA

Paulo H. Alkmin concebeu, desenvolveu e publicou o Voto Livre (votolivre.app), um site de utilidade pública e gratuito para quem foi demitido, ameaçado ou boicotado por causa do voto nas eleições de 2026 no Brasil. Foram cerca de três dias do primeiro commit até a produção com domínio próprio, Cloudflare Turnstile e encaminhamento dos registros ao Ministério Público, e ele fez 18 dos 19 commits do projeto. O site reúne guias práticos para trabalhadores e comerciantes, com checklist de provas e modelos de texto, canais oficiais de denúncia nacionais e das 27 UFs, contatos de 129 deputados federais gerados a partir da API de Dados Abertos da Câmara, um registro anônimo e um mapa em grade com a contagem de casos por estado. Cada registro pode ser encaminhado por e-mail à Ouvidoria do Ministério Público estadual, com cópia para quem administra o site e sem nenhum dado de quem enviou. É HTML estático gerado por um script de build em Python que usa só a biblioteca padrão, com JavaScript puro, duas Cloudflare Pages Functions e um banco D1, no plano gratuito da Cloudflare. Privacidade desde a concepção, segundo a LGPD: nenhum campo de identificação; IP, user-agent e cookies não são gravados; relatos que parecem conter CPF, CNPJ, telefone ou e-mail são recusados; só a data é guardada, sem a hora; o texto do relato só é armazenado se isso for ativado explicitamente, com exclusão após 180 dias; estatísticas com cache de 10 minutos; fontes servidas pelo próprio site e nenhum analytics. Proteção contra abuso sem rastrear ninguém: limite de envios com chave em HMAC do IP e segredo que muda todo dia, contador atômico no D1 no lugar de uma versão em KV com condição de corrida, UUID gerado no navegador para idempotência, token do Turnstile conferido por domínio e ação, campo-isca para robôs e limite de 12 KB no corpo lido em fluxo. Várias rodadas de revisão de segurança adversarial encontraram e corrigiram um DOM XSS, uma condição de corrida no limitador, um contorno do limite de tamanho do corpo e um limitador que se desligava em silêncio quando faltava configuração. O código é aberto, sob MIT, e o conteúdo está sob CC BY 4.0.

Site gratuito pra quem foi demitido, ameaçado ou boicotado pelo voto em 2026, feito em cerca de três dias, com registro anônimo e nada que rastreie quem visita.

27 de outubro de 2026
Desenvolvimento de Produto
Tecnologias
Cloudflare Pages FunctionsCloudflare D1TurnstilePrivacy by DesignLGPDApplication SecurityAccessibilityPython

O voto no Brasil é secreto. Isso não impede um patrão ou um grupo de WhatsApp de achar que sabe em quem você votou, e agir com base nisso.

O Voto Livre é um guia gratuito pra quem foi demitido, ameaçado ou boicotado por causa do voto nas eleições de 2026. Tem guias práticos pra trabalhadores e pra comerciantes, com checklist de provas e modelos de texto, canais oficiais de denúncia do país inteiro e de cada uma das 27 UFs, contatos de 129 deputados federais de partidos de esquerda e centro-esquerda tirados da API de Dados Abertos da Câmara, e um mapa com os casos por estado. Toda página começa com o número de emergência.

Concebi, desenvolvi e coloquei no ar em cerca de três dias, do primeiro commit até a produção com domínio próprio, proteção contra robôs e encaminhamento dos registros ao Ministério Público. Dos 19 commits, 18 são meus. O outro veio de um voluntário que ligou o site ao canal de assédio eleitoral do Ministério do Trabalho e Emprego.

Quase toda a engenharia foi pra um formulário só, o registro anônimo, usado por gente que pode sofrer represália. Desenhei tudo em volta de uma pergunta: o que o site precisa saber, e o que ele pode se recusar a saber?

1. No navegador

O formulário não tem campo pra nome, e-mail ou telefone. Antes de o relato sair do aparelho, o texto é conferido atrás de qualquer coisa que pareça CPF, CNPJ, telefone ou e-mail, e se achar, o envio é bloqueado. O servidor repete a mesma checagem. A página de privacidade do site avisa com todas as letras que esse filtro é automático e não substitui o cuidado de quem escreve.

O navegador também gera o UUID do registro. Se a conexão cair e a pessoa apertar "enviar" de novo, o registro conta uma vez só e não gasta cota extra. Um desafio do Turnstile e um campo-isca cuidam dos robôs, e o robô que preenche o campo-isca recebe um protocolo falso em vez de um erro.

Não tem script de analytics, e as fontes vêm do próprio site, então nenhum terceiro fica sabendo quem passou por ali.

2. Na borda

Uma Cloudflare Pages Function recebe o registro. Ela lê o corpo da requisição em fluxo e para nos 12 KB, não importa o Content-Length que o cliente informe, confere a origem e o tipo de conteúdo, e verifica se o token do Turnstile foi emitido pra este domínio e pra esta ação.

Aí entra o limite de envios, que precisava funcionar sem guardar o IP de ninguém. A chave é um HMAC do IP com um segredo que muda todo dia, e endereços IPv6 são agrupados por /64, o menor bloco que um assinante costuma receber. O contador fica no D1 e sobe numa única instrução INSERT ... ON CONFLICT ... RETURNING. Minha primeira versão usava KV, lia o número e depois gravava de volta, e isso deixava uma condição de corrida (TOCTOU): várias requisições em paralelo podiam ler o mesmo número e passar todas.

3. No banco

O D1 guarda as respostas das caixinhas do formulário: estado, tipo de caso, quem está registrando (a pessoa afetada, uma testemunha ou uma entidade), quando aconteceu e a data do registro, sem a hora. Essas linhas alimentam a contagem por estado. Guardar o texto do relato e a cidade depende de alguém ligar essa opção, e o texto guardado é apagado depois de 180 dias. Não tem IP, user-agent nem cookie em lugar nenhum.

Banco que nunca teve o nome não tem como vazar o nome.

4. No e-mail

É aqui que o relato sai do site. Ele vai por e-mail, pelo Resend ou pelo Cloudflare Email Service, pra Ouvidoria do Ministério Público do estado da pessoa, com cópia pra quem administra o site. A mensagem leva o relato e nada sobre quem mandou.

O roteamento tem regras próprias. O Ministério Público do Trabalho só recebe denúncia pelo sistema de peticionamento, então nos casos de trabalho o e-mail pede à Ouvidoria do MP estadual que encaminhe o relato à Procuradoria Regional do Trabalho competente, com base no direito de petição (art. 5º, XXXIV, da Constituição). Os registros de comerciantes citam os tipos penais que podem se aplicar. Quando o MP do estado não tem canal por e-mail, o registro vai pra Ouvidoria Nacional de Direitos Humanos, a mesma do Disque 100. Cada endereço da tabela de roteamento foi conferido, e a fonte fica anotada do lado.

O que chega ao órgão é uma notícia de fato pronta pra análise. A pessoa também recebe a mesma mensagem no próprio aparelho, pronta pra mandar ao Disque 100 pelo WhatsApp ou por e-mail, ou pra colar no formulário do MPT, saindo da conta dela.

Um modo cópia me deixou testar o fluxo inteiro, de ponta a ponta, antes de qualquer e-mail chegar a um órgão de verdade.

5. No mapa

O mapa público mostra os casos por estado num cartograma em grade de 9 por 8 feito à mão, com um quadrado do mesmo tamanho pra cada estado. No celular, Sergipe fica tão legível quanto Minas. Os números por trás ficam 10 minutos em cache na borda, o que poupa requisição e ainda impede alguém de vigiar o contador pra ligar um aumento ao momento em que uma pessoa específica apertou "enviar".

O que "anônimo" quer dizer aqui

O site não tem como saber quem mandou um registro. A Cloudflare, que hospeda o site, guarda os próprios registros de acesso, como qualquer hospedagem, e a página de privacidade diz isso. Guardar a data sem a hora dificulta muito cruzar uma linha do banco com uma entrada desses registros.

O que a revisão pegou

Enquanto construía, fiz várias rodadas de revisão de segurança adversarial no meu próprio código, lendo cada endpoint do jeito que um atacante leria. Elas acharam cinco problemas reais, todos corrigidos:

  • um DOM XSS no gerador de imagens Open Graph
  • a condição de corrida no limitador em KV
  • um jeito de contornar o limite de tamanho do corpo da requisição
  • um limitador que se desligava em silêncio quando faltava configuração
  • brechas na validação de origem e de tipo de conteúdo

O quarto ainda me incomoda. Limitador que desliga quando falta uma configuração passa em qualquer teste que não procure por isso, e nada quebra pra te avisar.

Pequeno de propósito

Um build.py de cerca de 200 linhas, só com a biblioteca padrão do Python, gera o HTML localmente, e o resultado vai no commit, então o deploy não tem etapa de build. O JavaScript não tem dependência nenhuma, e back-end mais scripts dão umas 870 linhas. A lista de deputados vem de um script que percorre as páginas da API da Câmara, espera mais a cada erro 429 ou 5xx antes de tentar de novo e aceita a legislatura como parâmetro, pra poder ser atualizada depois da posse do novo Congresso, em 2027.

Roda no plano gratuito da Cloudflare, custa quase nada e qualquer voluntário consegue editar e publicar. O código é MIT, o conteúdo é CC BY 4.0, e já chegou uma contribuição de fora.

O texto usa a Atkinson Hyperlegible, uma fonte desenhada pra quem tem baixa visão. O menu no celular cuida do foco e do aria-expanded, e as atualizações são anunciadas por regiões aria-live. Um commit inteiro foi só pra simplificar a linguagem: formulário mais curto, tela de resultado com os próximos passos e um resumo no topo de cada guia. Quem tá com medo lê menos, então o resumo vem primeiro.

No mesmo mês, trabalhei como voluntário no Tech Contra Bolsonaro e no Radar da Virada. O Voto Livre é o que eu tirei do papel, em Cloudflare Pages, Pages Functions, D1, Turnstile e Web Crypto.

O que o seu banco de dados sabe sobre quem usa o seu sistema que ele não precisava saber?