Blog

Markdown é a fonte, HTML é derivado: sanitização com uma borda só

28 de julho de 2026 · Time Orion

Todo sistema que aceita texto formatado de um humano e o exibe como HTML enfrenta a mesma decisão: onde sanitizar?

Existem duas escolas. Sanitizar na leitura (guarda o original, limpa toda vez que exibe) e sanitizar na escrita (limpa uma vez, guarda o resultado limpo).

Escolhemos a segunda, e a decisão vem com uma regra que é mais importante que a escolha em si.

Como funciona no nosso blog

O autor escreve Markdown. No momento de salvar, o servidor converte para HTML e passa por uma allowlist estrita. As duas versões ficam no banco: o Markdown, que é a fonte editável, e o HTML, que é o derivado servido.

O site público consome o HTML e injeta direto na página. Sem sanitizar de novo.

Essa última frase costuma causar desconforto, então vale explicá-la.

Por que não sanitizar de novo

Parece que sanitizar duas vezes é mais seguro. Não é — é mais frágil, por dois motivos.

Primeiro: duas allowlists divergem. No dia em que alguém adiciona uma tag na origem e esquece do consumidor, o conteúdo legítimo quebra silenciosamente. No dia em que o consumidor é mais permissivo que a origem, a segunda passada não acrescenta nada. Uma das duas está sempre errada, e ninguém sabe qual.

Segundo, e mais importante: duas bordas significam nenhuma dona. Quando a responsabilidade é compartilhada, cada lado presume que o outro cuidou. É assim que se cria um terceiro consumidor — um app, um feed, uma API — que esquece de sanitizar porque "isso já vem limpo".

A regra que adotamos é explícita e está escrita no código:

O HTML que sai do save é confiável. A borda é uma só, e é aqui.

Se algum dia um consumidor novo aparecer, ele herda a garantia — em vez de herdar a obrigação.

A allowlist é o produto

Sanitizar não é rodar uma biblioteca: é decidir o que o seu conteúdo pode conter. A nossa é curta de propósito:

  • Estrutura: títulos de segundo nível para baixo, parágrafos, listas, citação, tabela, código.
  • Ênfase: negrito, itálico, tachado.
  • Link e imagem.

E as decisões que valem mais que a lista:

Sem h1. O título do post já é o H1 da página. Um segundo H1 no corpo é erro de semântica e de SEO.

https e mailto. javascript: óbvio que não; http: também não, porque link inseguro numa página nossa é aviso de conteúdo misto de graça.

Imagem só com URL absoluta https. Aqui mora uma sutileza que quase passou: a opção de esquemas permitidos da biblioteca filtra URLs que têm esquema. Um caminho relativo não tem — e passava. Foi preciso um filtro explícito para descartar imagem cujo src não começa com https://.

Link externo ganha rel de segurança. Aplicado na transformação, não confiado ao autor.

O que a escolha custa

Seria desonesto não dizer.

Reprocessar exige script. Se a allowlist mudar — ou se a conversão melhorar —, o HTML já gravado continua velho. É preciso um comando que rode o Markdown de novo. Guardar a fonte é o que torna isso possível; é por isso que as duas colunas existem.

A prévia tem que usar o mesmo caminho. Se a tela de prévia renderizar o Markdown de um jeito diferente do save, ela mente. No nosso editor, a prévia chama a mesma função do save — nunca uma renderização client-side "aproximada".

O wrapper que não deve ser reusado

Uma armadilha sutil: já existia no sistema uma sanitização de HTML — a do webmail, que exibe e-mail recebido. Reusá-la teria sido natural.

Teria sido errado. Aquele wrapper é permissivo de propósito: e-mail recebido precisa de fidelidade visual, com estilos e atributos que jamais deveriam entrar num post nosso. São dois problemas com a mesma aparência e requisitos opostos.

Sanitização não é uma função utilitária universal. Ela é específica do contexto — e o contexto é "o que este conteúdo, vindo desta origem, tem o direito de fazer nesta página".

O resumo

Escolha uma borda. Coloque-a onde o conteúdo nasce. Escreva no código que ela é a única, para que o próximo consumidor não invente a segunda. E desconfie de reusar a allowlist de outro contexto — a semelhança é aparente.

O mesmo princípio de fronteira única aparece no gate de aprovação da Aura: um caminho, auditado, para a coisa perigosa virar registro.