Analise documentos de clientes sem nunca armazenar nenhum

Como o sistema é construído, onde ele roda e o que ele deliberadamente não faz.

Toda ferramenta gratuita de PDF na internet abre com “seu arquivo nunca sai do navegador”. Esta não abre, porque não pode, e uma página que fugisse disso seria a página errada para ter num site sobre segurança. Então a versão honesta vem primeiro, e todo o resto é o detalhe por trás dela.

Por que isto roda num servidor

Um verificador que roda só no navegador é um desenho legítimo e bom, e ele compra exatamente uma propriedade: os bytes ficam na sua máquina. O que ele custa é profundidade. A análise aqui percorre as tabelas de referência cruzada, reexecuta os fluxos de conteúdo das páginas para reconstruir a ordem de pintura, decodifica o texto coberto pelo mapa /ToUnicode da fonte e compara o documento com perfis estruturais montados a partir de espécimes legítimos de emissores. Isso é um parser com orçamento rígido de CPU e um botão de desligar, não algo para rodar dentro da aba de quem por acaso abriu a página.

A troca é deliberada: o arquivo é enviado por TLS e, em troca, a resposta é uma que nenhum leitor de metadados no navegador consegue produzir. O que devemos a você por essa troca é que a estadia do arquivo seja a mais curta e a mais contida possível, que é o resto desta página.

Se essa troca é errada para o seu documento, não faça a troca. Um processo sob segredo de justiça, um incidente em andamento, uma peça ainda sem tarja antes do protocolo, para esses, a ferramenta certa é uma que rode na sua própria máquina, e hoje esta não é. Preferimos dizer isso aqui a você descobrir depois.

O que acontece com um documento enviado

A vida inteira de um arquivo enviado, em ordem:

Sem conta, um documento enviado só existe em memória, e a única coisa que chega ao disco é uma linha de medição que não guarda documento nenhum. Uma conta acrescenta um registro derivado curto, e o arquivo cifrado só se ativar.
EM MEMÓRIA, DENTRO DA REQUISIÇÃO chega por TLS mantido em memória tipo farejado a partir dos bytes thread de trabalho, uma de duas prazo rígido de 15 segundos relatório devolvido a você buffer descartado EM DISCO Nenhum arquivo temporário, nenhuma fila no endpoint. Com conta: um registro derivado curto, sem texto. O arquivo só se a conta ativar, cifrado. mede a sua cota uma linha de registro id da chave · id da conta · bytes faixa de risco · carimbo de tempo

O texto extraído e o nome do arquivo nunca sobrevivem à resposta, e os bytes só sobrevivem quando uma conta ativou a guarda de arquivos. A política de privacidade é a versão coluna por coluna da linha à direita e do registro derivado.

  1. Ele chega por TLS e é mantido em memória. Não há arquivo temporário, diretório de upload nem fila atrás do endpoint.
  2. O tipo é decidido farejando os bytes, nunca pelo cabeçalho Content-Type nem pelo nome do arquivo: então um chamador hostil não escolhe qual parser roda na entrada dele.
  3. O buffer vai para uma thread de trabalho, uma de duas, com prazo rígido de 15 segundos. Um arquivo que esgota esse prazo tem sua thread morta e substituída, e o chamador recebe 422 analysis_timeout. Quando as duas estão ocupadas e a fila está cheia, a resposta é 503 busy, e não uma latência que só cresce.
  4. O relatório é serializado e devolvido. O buffer é descartado. O texto extraído e o nome do arquivo nunca sobrevivem à resposta. Os bytes só sobrevivem quando a conta ativou a guarda de arquivos: aí são cifrados com uma chave só deles e gravados ao lado do registro, por 7 a 90 dias.
  5. Uma linha de registro é gravada, e ela não guarda documento nenhum: o id da chave, o id da conta, o tamanho em bytes, a faixa de risco e um carimbo de tempo. É isso que mede a sua cota.
  6. Para uma conta com histórico ligado, um registro derivado é gravado: HMACs dos identificadores do arquivo, ids e severidades dos sinais, nota e faixa, nunca texto nem o nome do arquivo. Uma chave com none, ou o cabeçalho X-Tamperlens-Retention: none, pula essa gravação. A política de privacidade lista cada campo.

Os envios têm teto de 10 MB. Todas as outras rotas da origem têm teto de 64 KB, então nada além do único endpoint que precisa de folga pode ser levado a bufferizar megabytes antes de o handler rodar.

O texto recuperado, as palavras encontradas embaixo de uma tarja mal aplicada. Volta para você, na resposta, e nunca é guardado como texto. Ele é o achado, então omiti-lo tornaria o relatório inútil; e guardá-lo faria deste o banco de dados mais sensível do produto. Ele não é registrado em log nem gravado no histórico. Um arquivo que a sua conta escolheu guardar continua contendo esse texto, como o original.

A política de privacidade é a versão coluna por coluna do que o banco guarda.

Provar depois o que dissemos a você

Não guardamos o relatório, o que significa que não conseguimos reproduzi-lo. Se você recusa uma proposta com base nos nossos sinais e, seis meses depois, precisa justificar a decisão, "rode o arquivo de novo" não é resposta: o arquivo é do proponente, o motor mudou, o histórico de uma conta guarda a nota e os ids dos sinais em vez do relatório, e um arquivo que a sua conta guardou some em no máximo 90 dias. Analisar de novo um arquivo guardado responde o que o motor diz hoje, não o que ele disse na época.

Por isso toda resposta do /api/v1/inspect carrega um receipt: uma assinatura sobre o corpo exato que devolvemos, com a versão do motor e a hora em que foi emitida. O relatório fica com você. Se uma disputa aparecer, o relatório que você apresentar ou verifica contra aquela assinatura, ou não verifica.

"receipt": {
  "version": "v1",
  "issuedAt": "2026-08-15T19:32:25.147Z",
  "engineVersion": "1.43.0",
  "reportHash": "7543fa3f36bc24d0c5588b87025408b8a84fc7f7259d7dea3d9856a707af76ef",
  "redacted": false,
  "signature": "…"
}

Dois limites, e vale conhecer os dois antes de depender disso. A assinatura é um HMAC, então só nós conseguimos verificá-la: um auditor externo precisa passar por nós e confiar que conferimos com honestidade. Uma assinatura de chave publicada removeria essa objeção e é a v2 pretendida. E o recibo cobre o relatório, não o documento: prova o que dissemos, nunca o que o arquivo era.

A referência da API tem o campo e o endpoint de verificação.

Onde ele roda

  • Um servidor, na Finlândia. Uma instância Hetzner em Helsinque (hel1). Computação e banco de dados ficam na UE.
  • Nenhuma porta de entrada é publicada. A origem não é alcançável pela internet. O tráfego chega por um Cloudflare Tunnel, que abre uma conexão de saída a partir da máquina; não há socket escutando para alguém escanear, nem uma regra de firewall entre você e um erro.
  • TLS em todo o caminho. Terminado na borda da Cloudflare e levado até a origem dentro do túnel. HTTP é redirecionado, e o host canônico é tamperlens.com.
  • O banco é um único arquivo SQLite no disco dessa máquina, com contas, hashes de chaves, sessões, o registro de uso e, para contas que guardam histórico, os registros derivados. Ele nunca guarda um documento: um arquivo que uma conta ativa fica fora dele, cifrado.

Endurecimento da aplicação

  • Uma Content-Security-Policy de verdade. default-src 'self' e nenhum 'unsafe-inline' em lugar algum do site. Toda página que escrevemos carrega script e folha de estilo como arquivos externos, e a CI quebra o build se aparecer script ou estilo embutido em src/web. A única exceção é /docs, onde o Swagger UI injeta o próprio bootstrap e não oferece como desligá-lo; fica restrito a esse único prefixo, que não renderiza conteúdo de usuário.
    A política só permite a nossa própria origem, com exceção da Cloudflare, e só para os dois recursos que precisam dela: challenges.cloudflare.com em script-src e frame-src, para a verificação humana Turnstile, e static.cloudflareinsights.com mais cloudflareinsights.com, para o Web Analytics. Cada entrada só aparece enquanto aquele recurso está ligado, então uma instalação que não rode nenhum dos dois mantém uma política restrita à própria origem, byte a byte. Os dois estão ligados hoje, e a seção 4 da política de privacidade diz o que eles carregam e quando.
  • Envios de outra origem são recusados. Um navegador só pode chamar /inspect a partir das nossas páginas. Requisições sem cabeçalho Origin (curl, SDKs de servidor), não são afetadas, porque gastam o próprio endereço em vez de pegar emprestado o de um visitante.
  • Limites de taxa por rede, não por cabeçalho. Chamadores anônimos têm 10 inspeções por hora e 25 por dia por /64 IPv6 (um cliente é um balde, não 264 deles), sob um teto diário de toda a instalação. Chamadores com chave são medidos pela cota mensal.
  • Outros cabeçalhos: Referrer-Policy: no-referrer, Cross-Origin-Opener-Policy: same-origin, X-Content-Type-Options: nosniff, HSTS em produção e frame-ancestors 'self'.
  • PDFs criptografados são declarados, não atacados. Um arquivo protegido por senha é reportado como “sem análise de conteúdo”, e não varrido como texto cifrado e reportado como limpo. Não tentamos quebrar a proteção de nada que você enviar.

Contas, chaves e segredos

  • Chaves de API são guardadas como hashes SHA-256. A chave em si existe nos nossos sistemas apenas pelo tempo de mostrá-la a você uma vez. É também por isso que não conseguimos reenviá-la: a rotação emite uma nova e invalida a antiga.
  • Senhas são hashes scrypt com sal aleatório por conta. O texto puro nunca é guardado e o hash não é reversível.
  • Sessões são HttpOnly, Secure e SameSite=Lax, expiram em 30 dias e são apagadas no logout. Redefinir a senha invalida todas as outras sessões.
  • Dados de cartão nunca chegam até nós. O checkout e o portal de cobrança são hospedados pela Stripe; guardamos um id opaco de cliente e mais nada.
  • Uma chave, um inquilino. Perfis privados de emissor resolvem apenas contra a conta dona, não existe caminho de código da requisição de uma conta até o perfil de outra.

Disponibilidade e recuperação

Existem sessenta dias de backup do banco, e nenhum deles contém documento: o banco nunca guarda um, e os arquivos que uma conta ativa ficam de fora deles, junto com as chaves.
o documento descartado com a resposta; guardado cifrado só se uma conta ativar uma linha de registro guardada, para medir a cota · id da chave · id da conta · bytes · faixa de risco backups, na máquina 48 cópias, uma a cada seis horas backups, fora dela todo dia para a máquina do operador, e para o Cloudflare R2 cifrados com age 0 12 dias 60 dias Só a cópia no R2 é cifrada. O resto está em “O que não temos”, abaixo.

As barras são retenção, não volume. Perder o servidor não significa perder também os backups dele, que é a razão inteira de a puxada diária existir. Os registros do histórico de uma conta (30, 90 ou 365 dias) e os arquivos guardados (7, 30 ou 90 dias) seguem os próprios prazos; a política de privacidade traz os dois.

  • Deploys são condicionados e reversíveis. Um push para main roda typecheck, build e a suíte de testes inteira; só no verde é que um runner na máquina reconstrói, espera o healthcheck do contêiner e reverte automaticamente se o build novo não estiver saudável.
  • Backups do banco a cada seis horas, 48 cópias retidas (doze dias), gravadas na mesma máquina, em um diretório que só a conta de serviço consegue ler, e puxadas uma vez por dia para uma máquina controlada pelo operador, fora do provedor, mantidas lá por sessenta dias, para que perder o servidor não signifique perder também os backups dele. Uma vez por dia, a cópia mais recente também é cifrada com age no servidor, com uma chave cuja metade privada o servidor nunca tem, e enviada ao Cloudflare R2, onde não pode ser apagada por sessenta dias e é apagada depois disso. Elas não contêm documentos, porque o banco não contém nenhum e os arquivos guardados ficam de fora com as chaves; contêm, sim, os registros do histórico das contas. As cópias no servidor e na máquina do operador não são criptografadas em repouso, o que está listado abaixo entre as lacunas em vez de maquiado aqui.
  • A disponibilidade é sondada de fora em uma agenda de melhor esforço, e os erros vão para o Sentry com o caminho da requisição, nunca com um documento.
  • O que a sonda encontrou é público, em status.tamperlens.com: 90 dias de disponibilidade diária por componente, todos os incidentes e os dados brutos de onde saem as porcentagens. A sondagem vem de fora da nossa rede e a página é servida de fora dela também, então ela continua no ar quando nós não estamos. O compromisso ali é de 98,5% ao mês, e a própria página declara o limite da medição: um único ponto de observação, em um intervalo que ela mede e publica em vez de afirmar, uma queda mais curta que o intervalo entre duas sondagens pode passar despercebida.

Quem mais encosta nisso

Nenhum deles recebe um documento enviado. O único lugar em que um documento sobrevive à requisição é um arquivo que uma conta ativou: cifrado, no servidor da Hetzner, e por isso também nas imagens de disco que a Hetzner faz dele, junto com a chave do servidor. Esta tabela é a mesma lista da seção 6 da política de privacidade, que é a versão que vale, se as duas divergirem, quem obriga é a política.

Nada que saiu do documento é enviado através da linha. O que os seis suboperadores veem é metadado da requisição, não o arquivo que ela carregou; a Hetzner também guarda o disco, e com ele qualquer arquivo que uma conta ativou, cifrado.
DENTRO DO PROCESSO, EM HELSINQUE FORA DELE os bytes enviados o texto extraído deles o texto recuperado sob uma tarja Os três são descartados com a resposta, menos os bytes que uma conta ativa. o relatório volta para você, na resposta a fronteira de confiança metadado da requisição Cloudflare metadado e IP, em trânsito Hetzner a máquina e as imagens de disco Stripe seus dados de cobrança Resend seu endereço de e-mail Sentry stack traces e caminhos GitHub nada sobre você nenhum deles recebe um documento enviado

As mesmas seis linhas da tabela abaixo, que por sua vez é a seção 6 da política de privacidade. A versão que vale se as duas divergirem.

Provedor O que faz O que enxerga
Cloudflare DNS, TLS, entrada por túnel, a verificação humana Turnstile, Web Analytics e o armazenamento (R2) da cópia off-site cifrada dos backups do banco Metadados da requisição e endereço IP, em trânsito; o backup off-site só como texto cifrado pelo age
Hetzner O servidor e o disco dele A máquina física e as imagens de disco dela, que incluem qualquer arquivo que uma conta ativou, cifrado, com a chave do servidor; nenhum acesso em nível de aplicação
Stripe Checkout, assinaturas, portal de cobrança Seus dados de cobrança. Não vemos dados de cartão
Resend E-mail de confirmação e de redefinição de senha Seu endereço de e-mail e a mensagem
Sentry Relato de erros Stack traces e caminhos de requisição
GitHub Código, CI, disparo do deploy Nada sobre você; nenhum dado de produção

Respondendo ao seu questionário de segurança

Se você está avaliando o Tamperlens para uma empresa que revisa seus fornecedores, é quase certo que exista uma planilha para preencher. Duas coisas existem para que você não precise marcar uma call para preenchê-la.

Uma autoavaliação MVSP. O MVSP é uma lista de controles mínimos mantida abertamente por um grupo que inclui Google, Salesforce, Okta, Slack e Netflix. Respondemos todos os controles, inclusive aqueles em que falhamos, e as falhas são as mesmas listadas na seção seguinte, não um conjunto diferente e mais gentil. É uma autoavaliação: ninguém a auditou, e ela não atesta nada.

Uma resposta CAIQ Lite. O questionário curto da Cloud Security Alliance, respondido do mesmo jeito, para quem tem um processo que espera esse formato como arquivo anexável a um chamado.

Os dois estão disponíveis mediante pedido em [email protected], em geral no mesmo dia útil, porque são documentos escritos e não um projeto. Quando uma resposta depende de algo que você mesmo pode conferir, ela aponta para a página que mostra: esta, a política de privacidade ou a página de status.

Se um relatório SOC 2 concluído for requisito obrigatório, não somos compatíveis hoje, e preferimos dizer isso por escrito a gastar um trimestre do seu tempo até você descobrir. As autoavaliações acima são o que um fornecedor de uma pessoa só pode oferecer com honestidade; elas não substituem um relatório, e não as apresentamos como se substituíssem.

O que nunca consultamos

O Tamperlens lê identificadores brasileiros: CPF, CNPJ, a chave de acesso de uma NF-e, a linha digitável de um boleto, um payload PIX, um número de processo no padrão CNJ, e confere cada um do único jeito que sabe: com aritmética. Todos eles carregam dígitos verificadores calculados por uma identidade publicada, módulo 10, módulo 11 ou ISO 7064. Recalculamos esses dígitos a partir dos números já impressos na página e reportamos os que discordam de si mesmos.

A conferência inteira é aritmética sobre dígitos que você já enviou. Não há nela um ramo que chegue a um birô, porque esse ramo nunca foi construído.
dígitos já impressos CPF · CNPJ · chave da NF-e linha digitável · PIX · CNJ recalcular o dígito módulo 10 · módulo 11 · ISO 7064 inteiros, na mesma análise discorda de si mesmo → reportado concorda → fica em silêncio perguntar a um birô o que se sabe da pessoa fotos · endereços · parentes · empresas não existe esse caminho nenhuma requisição sai do processo Um dígito que passa não prova praticamente nada, então só as falhas são reportadas.

Seis famílias de identificador, tudo aritmética de inteiros e nenhuma chamada de rede no módulo. Isto descreve o que o código faz, não uma política que poderia ser afrouxada.

Nada é consultado, porque não há o que consultar. Sem birô, sem base de dossiês, sem busca por CPF e sem nenhuma requisição saindo do processo. A conferência é aritmética de inteiros sobre dígitos que você já nos enviou, e roda dentro da mesma análise em memória de todo o resto desta página. Vale dizer isso com todas as letras, porque o caminho óbvio para montar um produto de documentos no Brasil é o contrário: pegar um identificador e devolver o que se sabe sobre a pessoa por trás dele, fotos, endereços, parentes, empresas. Esse produto carrega a exposição à LGPD de um birô de dados, o custo de um birô de dados e a superfície regulatória de um birô de dados. Este aqui não guarda dado pessoal nenhum porque não obtém nenhum.

Isso compra menos do que uma consulta compraria, e o relatório diz isso. Um dígito verificador que falha é boa evidência de erro de digitação, de erro de OCR ou de número inventado. Um dígito que passa não prova praticamente nada. Um CPF válido são onze dígitos que qualquer um gera em um laço, e todo preenchedor de template já gera, então essa família reporta as falhas e fica calada nos acertos. Não é verificação de identidade, não é KYC e não é consulta cadastral, e não vendemos como se fosse.

O que não temos

Uma página de segurança que lista só qualidades é publicidade. Estas são as perguntas de uma análise de fornecedor cuja resposta hoje é não.

  • Sem SOC 2, ISO 27001 ou atestado HIPAA. A Tamperlens é um produto pequeno e independente, e não foi auditada contra nenhum deles. Se o seu processo exige fornecedor certificado, este ainda não é um.
  • Sem redundância entre regiões. Um servidor, uma região. Uma falha de hardware é indisponibilidade, não perda de dados, mas é indisponibilidade.
  • Sem chaves gerenciadas pelo cliente e sem versão on-premise. Hoje não há como rodar o motor dentro da sua própria rede, e a chave mestra que protege os arquivos que uma conta guarda é nossa e fica no mesmo servidor dos arquivos. Ela protege um disco ou um banco copiado, não um servidor comprometido.
  • Backups na máquina não são criptografados em repouso. Os snapshots de seis em seis horas ficam no disco do mesmo servidor, em um diretório que só a conta de serviço que os grava consegue ler, e dentro dos backups que a própria Hetzner faz da máquina, e a puxada diária os guarda do mesmo jeito na máquina do operador. Isso é permissão de arquivo, não criptografia. Só a cópia off-site no Cloudflare R2 é cifrada (age). Eles não guardam documentos, mas guardam linhas de conta, hashes de chave e os registros do histórico das contas. As imagens de disco da Hetzner também guardam os arquivos cifrados que uma conta ativou, com a chave mestra, por até sete dias depois de apagados.
  • Sem relatório de teste de intrusão para compartilhar. O endurecimento acima é real e boa parte dele é garantida por testes na CI; nada disso foi revisado por avaliador externo.
  • Forense de metadados é indício, não prova. Um PDF regerado do zero lava todos os sinais de nível de bytes, e exiftool -all= remove todo achado de identidade em imagens. Um acerto é indício forte; uma ausência não é indício nenhum, e todo achado diz isso no próprio texto.

Como relatar uma vulnerabilidade

Escreva para [email protected] com detalhe suficiente para reproduzir. Você receberá resposta de uma pessoa. Não há programa de recompensa e não há ameaça jurídica: relate de boa-fé, dê uma janela razoável para a correção, e damos o crédito a você se quiser o crédito.

Por favor, não rode scanners automáticos contra o host de produção. Os limites de taxa acima simplesmente vão recusar você, e o barulho enterra justamente o relato que você queria fazer.