Renderizar links Markdown com segurança em respostas de chat com IA
Índice
Quando um chat com IA responde Veja [Serviços]( /services/ ), o link pode não ser renderizado e o Markdown bruto pode permanecer na tela.
Acecore encontrou esse caso no chat de contato e ajustou o renderizador em o PR que corrigiu a renderização de links Markdown.
Este artigo usa essa correção pequena como ponto de partida para converter respostas de IA em DOM com segurança.
Respostas de IA não são HTML confiável
Trate a saída do modelo como texto.
Links, negrito e listas são úteis, mas inserir a resposta em innerHTML faz o navegador interpretar qualquer string gerada pelo modelo.
Não é necessário implementar Markdown completo. É melhor detectar só os recursos suportados pelo chat e criar apenas nós DOM seguros.
O problema não é só espaço
O bug direto era um link assim:
[Serviços](/services/)
Uma regex rígida costuma assumir que a URL não tem espaços:
;/\[([^\]]+)\]\(([^)\s]+)\)/
[^)\s]+ rejeita espaços, então ( /services/ ) não vira link. A correção é permitir espaços dentro dos parênteses e normalizar depois.
;/\[([^\]]+)\]\(\s*([^)]+?)\s*\)/
Depois disso, a validação continua obrigatória.
Faça trim antes de validar
O fluxo deve ser:
- Extrair label e raw href
- Aplicar
trim()no raw href - Validar o href com allowlist
- Criar
<a>apenas se for permitido
const href = String(rawHref || '').trim()
if (label && isSafeMarkdownHref(href)) {
const link = document.createElement('a')
link.href = href
link.rel = 'noopener noreferrer'
if (/^https?:\/\//i.test(href)) {
link.target = '_blank'
}
link.textContent = label
parent.appendChild(link)
}
Valide o mesmo valor que será renderizado. Caso contrário, a checagem perde valor.
A allowlist depende do site
Cada produto deve decidir quais URLs a IA pode mostrar.
| Tipo | Exemplo | Decisão |
|---|---|---|
| Path interno | /services/ | Permitir |
| Mesmo origin | https://acecore.net/... | Permitir |
| LINE oficial | https://lin.ee/... | Permitir quando for o canal oficial |
| mailto | mailto:[email protected] | Só endereço fixo |
| tel | tel:05088902788 | Só número fixo |
| Outros externos | Qualquer URL | Não linkar por padrão |
function isSafeMarkdownHref(href) {
if (href.startsWith('/')) return true
try {
const url = new URL(href, window.location.origin)
if (url.origin === window.location.origin) return true
if (url.hostname === 'acecore.net') return true
if (url.hostname === 'lin.ee') return true
} catch {
return false
}
return href === 'mailto:[email protected]' || href === 'tel:05088902788'
}
Um site de recrutamento pode liberar portais de vaga; um SaaS pode liberar documentação e status page. A função deve refletir o produto.
Faça fallback para texto
Se um link falha na validação, normalmente é melhor preservar o Markdown como texto do que apagá-lo.
O usuário mantém contexto, e a equipe consegue ver o que o modelo tentou emitir. O renderizador deve criar links seguros e também falhar de forma segura.
Teste os casos errados
Inclua pelo menos:
| Entrada | Resultado esperado |
|---|---|
[Serviços](/services/) | Link interno |
[Serviços]( /services/ ) | Link interno após trim |
[LINE]( https://lin.ee/example ) | Link externo permitido |
[Ruim](javascript:alert(1)) | Não vira link |
[Externo](https://example.com/) | Não vira link se o domínio não for permitido |
[Quebrado](/services/ | Mostra como texto |
No PR #99, as variações com e sem espaços foram confirmadas como a mesma URL pretendida.
Não implemente todo Markdown por padrão
Para chat, geralmente basta:
- Parágrafos
- Listas
- Negrito
- Código inline
- Links
Tabelas, imagens, HTML bruto e notas de rodapé aumentam muito a responsabilidade. Mesmo com uma biblioteca, a política de HTML e links precisa ser definida separadamente.
Resumo
Renderizar links Markdown em respostas de IA parece um ajuste pequeno, mas define o limite de confiança na saída do modelo.
A regra prática é: texto primeiro, subconjunto pequeno, trim antes da validação, allowlist rígida e fallback seguro.
Fluxo de renderização de links
Text
Trate a resposta do modelo primeiro como texto puro.
Parse
Detecte apenas o Markdown que o chat realmente usa.
Validate
Faça trim do href e permita apenas URLs internas ou domínios aprovados.
Render
Crie elementos seguros com DOM API em vez de innerHTML.
Renderização frouxa
- Colocar respostas de IA diretamente em innerHTML
- Tentar implementar Markdown completo de uma vez
- Falhar quando há espaços ao redor da URL
- Tratar URLs externas e javascript: da mesma forma
Renderização pequena e segura
- Receber respostas como texto e converter só o necessário em DOM
- Suportar apenas o subconjunto usado no chat
- Validar URLs depois de trim
- Manter URLs não permitidas como texto
- Concluído: Não confiar em respostas de IA como HTML
- Concluído: Aceitar espaços ao redor de URLs Markdown
- Concluído: Sempre fazer trim de href antes de validar
- Concluído: Permitir apenas paths internos, origin atual e domínios necessários
- Concluído: Definir target e rel em links externos
- Concluído: Preservar links não permitidos como texto
- Concluído: Testar URLs perigosas e Markdown quebrado
Usar markdown-it ou marked basta?
Permitir espaços ao redor da URL é perigoso?
Links não permitidos devem ser removidos?
Comentários
Gui
CEO da Acecore. Lidera sistemas de negócio, web, bancos de dados e infraestrutura, qualidade e adoção de IA da definição de problemas de negócio ao design, implantação e melhoria após o lançamento. Parte de capacidade prática em C#/.NET e também cobre PHP/JavaScript, SQL Server/PostgreSQL/MySQL e Linux/Windows Server, desenhando requisitos, escolhas tecnológicas, padrões de qualidade e operações de desenvolvimento baseadas em GitHub como um fluxo único. Incorpora IA generativa a processos de desenvolvimento, verificação e organização de informações, como uma base prática para que equipes pequenas entreguem mais rápido e com maior confiabilidade.
Quer saber mais sobre nossos serviços?
Oferecemos suporte abrangente em desenvolvimento de sistemas, design web, design gráfico e educação em TI.
Artigos relacionados
Como projetar um site Astro + Cloudflare que cresce por funcionalidade 7 de junho de 2026 às 19:00
Como adicionar comentários a um blog Astro usando apenas Cloudflare 7 de junho de 2026 às 18:00
Guia de instalação do Sveltia CMS 7 de junho de 2026 às 16:00